def setupUi(self, MainWindow): MainWindow.setObjectName(_fromUtf8("MainWindow")) MainWindow.resize(1155, 853) self.centralwidget = QWidget(MainWindow) self.centralwidget.setObjectName(_fromUtf8("centralwidget")) self.verticalLayout = QVBoxLayout(self.centralwidget) self.verticalLayout.setObjectName(_fromUtf8("verticalLayout")) self.tabWidget = QTabWidget(self.centralwidget) self.tabWidget.setObjectName(_fromUtf8("tabWidget")) self.tab = QWidget() self.tab.setObjectName(_fromUtf8("tab")) self.tabWidget.addTab(self.tab, _fromUtf8("")) self.tab_2 = QWidget() self.tab_2.setObjectName(_fromUtf8("tab_2")) self.tabWidget.addTab(self.tab_2, _fromUtf8("")) self.verticalLayout.addWidget(self.tabWidget) MainWindow.setCentralWidget(self.centralwidget) self.menubar = QMenuBar(MainWindow) self.menubar.setGeometry(QRect(0, 0, 1155, 20)) self.menubar.setObjectName(_fromUtf8("menubar")) MainWindow.setMenuBar(self.menubar) self.statusbar = QStatusBar(MainWindow) self.statusbar.setObjectName(_fromUtf8("statusbar")) MainWindow.setStatusBar(self.statusbar) self.retranslateUi(MainWindow) self.tabWidget.setCurrentIndex(0) QMetaObject.connectSlotsByName(MainWindow)
def __init__(self, content=None, filename=None, parent=None): """ :param content: An optional string of content to pass to the editor :param filename: The file path where the content was read. :param parent: An optional parent QWidget """ super(PythonFileInterpreter, self).__init__(parent) # layout self.editor = CodeEditor("AlternateCSPythonLexer", self) # Clear QsciScintilla key bindings that may override PyQt's bindings self.clear_key_binding("Ctrl+/") self.status = QStatusBar(self) self.layout = QVBoxLayout() self.layout.addWidget(self.editor) self.layout.addWidget(self.status) self.setLayout(self.layout) self.layout.setContentsMargins(0, 0, 0, 0) self._setup_editor(content, filename) self.setAttribute(Qt.WA_DeleteOnClose, True) self._presenter = PythonFileInterpreterPresenter( self, PythonCodeExecution(content)) self.editor.modificationChanged.connect(self.sig_editor_modified) self.editor.fileNameChanged.connect(self.sig_filename_modified) self.find_replace_dialog = None self.find_replace_dialog_shown = False
def __init__(self, font=None, content=None, filename=None, parent=None): """ :param font: A reference to the font to be used by the editor. If not supplied use the system default :param content: An optional string of content to pass to the editor :param filename: The file path where the content was read. :param parent: An optional parent QWidget """ super(PythonFileInterpreter, self).__init__(parent) self.parent = parent # layout font = font if font is not None else QFont() self.editor = CodeEditor("AlternateCSPython", font, self) self.find_replace_dialog = None self.status = QStatusBar(self) self.layout = QVBoxLayout() self.layout.setContentsMargins(0, 0, 0, 0) self.layout.addWidget(self.editor) self.layout.addWidget(self.status) self.setLayout(self.layout) self._setup_editor(content, filename) self._presenter = PythonFileInterpreterPresenter(self, PythonCodeExecution(content)) self.code_commenter = CodeCommenter(self.editor) self.editor.modificationChanged.connect(self.sig_editor_modified) self.editor.fileNameChanged.connect(self.sig_filename_modified) self.setAttribute(Qt.WA_DeleteOnClose, True) # Connect the model signals to the view's signals so they can be accessed from outside the MVP self._presenter.model.sig_exec_progress.connect(self.sig_progress) self._presenter.model.sig_exec_error.connect(self.sig_exec_error) self._presenter.model.sig_exec_success.connect(self.sig_exec_success)
def __init__(self, qt_widget, *, show): self.qt_widget = qt_widget self._qt_window = QMainWindow() self._qt_window.setAttribute(Qt.WA_DeleteOnClose) self._qt_window.setUnifiedTitleAndToolBarOnMac(True) self._qt_center = QWidget(self._qt_window) self._qt_window.setCentralWidget(self._qt_center) if hasattr(self.qt_widget.model, "title"): self._qt_window.setWindowTitle(self.qt_widget.model.title) self._qt_center.setLayout(QHBoxLayout()) self._status_bar = QStatusBar() self._qt_window.setStatusBar(self._status_bar) self._status_bar.showMessage("Ready") self._help = QLabel("") self._status_bar.addPermanentWidget(self._help) self._qt_center.layout().addWidget(self.qt_widget) self._qt_center.layout().setContentsMargins(4, 0, 4, 0) # self._add_viewer_dock_widget(self.qt_widget.dockConsole) # self._add_viewer_dock_widget(self.qt_widget.dockLayerControls) # self._add_viewer_dock_widget(self.qt_widget.dockLayerList) # self.qt_widget.viewer.events.status.connect(self._status_changed) # self.qt_widget.viewer.events.help.connect(self._help_changed) # self.qt_widget.viewer.events.title.connect(self._title_changed) # self.qt_widget.viewer.events.palette.connect(self._update_palette) if show: self.show()
def __init__(self, qt_viewer: QtViewer, *, show: bool = True): self.qt_viewer = qt_viewer self._qt_window = QMainWindow() self._qt_window.setAttribute(Qt.WA_DeleteOnClose) self._qt_window.setUnifiedTitleAndToolBarOnMac(True) # since we initialize canvas before window, we need to manually connect them again. if self._qt_window.windowHandle() is not None: self._qt_window.windowHandle().screenChanged.connect( self.qt_viewer.canvas._backend.screen_changed) self._qt_center = QWidget(self._qt_window) self._qt_window.setCentralWidget(self._qt_center) self._qt_window.setWindowTitle(self.qt_viewer.viewer.title) self._qt_center.setLayout(QHBoxLayout()) self._status_bar = QStatusBar() self._qt_window.setStatusBar(self._status_bar) self._add_menubar() self._add_file_menu() self._add_view_menu() self._add_window_menu() self._add_plugins_menu() self._add_help_menu() self._status_bar.showMessage('Ready') self._help = QLabel('') self._status_bar.addPermanentWidget(self._help) self._qt_center.layout().addWidget(self.qt_viewer) self._qt_center.layout().setContentsMargins(4, 0, 4, 0) self._update_palette() self._add_viewer_dock_widget(self.qt_viewer.dockConsole) self._add_viewer_dock_widget(self.qt_viewer.dockLayerControls) self._add_viewer_dock_widget(self.qt_viewer.dockLayerList) self.qt_viewer.viewer.events.status.connect(self._status_changed) self.qt_viewer.viewer.events.help.connect(self._help_changed) self.qt_viewer.viewer.events.title.connect(self._title_changed) self.qt_viewer.viewer.events.palette.connect(self._update_palette) if perf.USE_PERFMON: # Add DebugMenu and dockPerformance if using perfmon. self._debug_menu = DebugMenu(self) self._add_viewer_dock_widget(self.qt_viewer.dockPerformance) else: self._debug_menu = None if self.qt_viewer.dockRender is not None: self._add_viewer_dock_widget(self.qt_viewer.dockRender) if show: self.show()
def setup_status_bar(self): """Set up status bar.""" status_bar = QStatusBar(self) status_bar.addPermanentWidget(QLabel("Clicked", self)) status_bar.addPermanentWidget(self.clicked_label) status_bar.addPermanentWidget(QLabel("Solution", self)) status_bar.addPermanentWidget(self.n_solution_1_label) self.setStatusBar(status_bar)
def __init__(self, qt_viewer: QtViewer, *, show: bool = True): self.qt_viewer = qt_viewer self._qt_window = QMainWindow() self._qt_window.setAttribute(Qt.WA_DeleteOnClose) self._qt_window.setUnifiedTitleAndToolBarOnMac(True) self._qt_center = QWidget(self._qt_window) self._qt_window.setCentralWidget(self._qt_center) self._qt_window.setWindowTitle(self.qt_viewer.viewer.title) self._qt_center.setLayout(QHBoxLayout()) self._status_bar = QStatusBar() self._qt_window.setStatusBar(self._status_bar) self._add_menubar() self._add_file_menu() self._add_view_menu() self._add_window_menu() self._add_plugins_menu() self._add_help_menu() self._status_bar.showMessage('Ready') self._help = QLabel('') self._status_bar.addPermanentWidget(self._help) self._qt_center.layout().addWidget(self.qt_viewer) self._qt_center.layout().setContentsMargins(4, 0, 4, 0) self._update_palette() self._add_viewer_dock_widget(self.qt_viewer.dockConsole) self._add_viewer_dock_widget(self.qt_viewer.dockLayerControls) self._add_viewer_dock_widget(self.qt_viewer.dockLayerList) self.qt_viewer.viewer.events.status.connect(self._status_changed) self.qt_viewer.viewer.events.help.connect(self._help_changed) self.qt_viewer.viewer.events.title.connect(self._title_changed) self.qt_viewer.viewer.events.palette.connect(self._update_palette) if perf.USE_PERFMON: # Add DebugMenu if using perfmon. The DebugMenu is intended to # contain non-perfmon stuff as well. When it does we will want # a separate env variable for it. self._debug_menu = DebugMenu(self) # The QtPerformance widget only exists if we are using perfmon. self._add_viewer_dock_widget(self.qt_viewer.dockPerformance) else: self._debug_menu = None if show: self.show()
def __init__(self, font=None, content=None, filename=None, parent=None, completion_enabled=True): """ :param font: A reference to the font to be used by the editor. If not supplied use the system default :param content: An optional string of content to pass to the editor :param filename: The file path where the content was read. :param parent: An optional parent QWidget :param completion_enabled: Optional parameter to control code auto-completion suggestions """ super(PythonFileInterpreter, self).__init__(parent) self.parent = parent self.completion_enabled = completion_enabled # layout font = font if font is not None else QFont() self.editor = CodeEditor("AlternateCSPython", font, self) self.find_replace_dialog = None self.status = QStatusBar(self) self.layout = QVBoxLayout() self.layout.setContentsMargins(0, 0, 0, 0) self.layout.addWidget(self.editor) self.layout.addWidget(self.status) self.setLayout(self.layout) self._setup_editor(content, filename) self._presenter = PythonFileInterpreterPresenter( self, PythonCodeExecution(self.editor)) self.code_commenter = CodeCommenter(self.editor) if self.completion_enabled: self.code_completer = CodeCompleter( self.editor, self._presenter.model.globals_ns) self.editor.modificationChanged.connect(self.sig_editor_modified) self.editor.fileNameChanged.connect(self.sig_filename_modified) self.setAttribute(Qt.WA_DeleteOnClose, True) # Connect the model signals to the view's signals so they can be accessed from outside the MVP self._presenter.model.sig_exec_error.connect(self.sig_exec_error) self._presenter.model.sig_exec_success.connect(self.sig_exec_success) if self.completion_enabled: # Re-populate the completion API after execution success self._presenter.model.sig_exec_success.connect( self.code_completer.update_completion_api) # Only load the simpleapi completions if the code editor starts being modified. self.sig_editor_modified.connect( self.code_completer.add_simpleapi_to_completions_if_required)
class WorkspacesView(QMainWindow): """ class """ def __init__(self, parent=None): """ Init :param parent: """ from .ui.workspaceviewwidget import WorkspaceViewWidget QMainWindow.__init__(self) # set up self.setObjectName(_fromUtf8("MainWindow")) self.resize(1600, 1200) self.centralwidget = QWidget(self) self.centralwidget.setObjectName(_fromUtf8("centralwidget")) self.gridLayout = QGridLayout(self.centralwidget) self.gridLayout.setObjectName(_fromUtf8("gridLayout")) self.widget = WorkspaceViewWidget(self) sizePolicy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.widget.sizePolicy().hasHeightForWidth()) self.widget.setSizePolicy(sizePolicy) self.widget.setObjectName(_fromUtf8("widget")) self.gridLayout.addWidget(self.widget, 1, 0, 1, 1) self.label = QLabel(self.centralwidget) self.label.setObjectName(_fromUtf8("label")) self.gridLayout.addWidget(self.label, 0, 0, 1, 1) self.setCentralWidget(self.centralwidget) self.menubar = QMenuBar(self) self.menubar.setGeometry(QtCore.QRect(0, 0, 1005, 25)) self.menubar.setObjectName(_fromUtf8("menubar")) self.setMenuBar(self.menubar) self.statusbar = QStatusBar(self) self.statusbar.setObjectName(_fromUtf8("statusbar")) self.setStatusBar(self.statusbar) self.toolBar = QToolBar(self) self.toolBar.setObjectName(_fromUtf8("toolBar")) self.addToolBar(QtCore.Qt.TopToolBarArea, self.toolBar) # self.retranslateUi(self) QtCore.QMetaObject.connectSlotsByName(self)
def __init__(self, qt_viewer, *, show=True): self.qt_viewer = qt_viewer self._qt_window = QMainWindow() self._qt_window.setUnifiedTitleAndToolBarOnMac(True) self._qt_center = QWidget(self._qt_window) self._qt_window.setCentralWidget(self._qt_center) self._qt_window.setWindowTitle(self.qt_viewer.viewer.title) self._qt_center.setLayout(QHBoxLayout()) self._status_bar = QStatusBar() self._qt_window.setStatusBar(self._status_bar) self._qt_window.closeEvent = self.closeEvent self.close = self._qt_window.close self._add_menubar() self._add_file_menu() self._add_view_menu() self._add_window_menu() self._add_help_menu() self._status_bar.showMessage('Ready') self._help = QLabel('') self._status_bar.addPermanentWidget(self._help) self._qt_center.layout().addWidget(self.qt_viewer) self._qt_center.layout().setContentsMargins(4, 0, 4, 0) self._update_palette(qt_viewer.viewer.palette) if self.qt_viewer.console.shell is not None: self._add_viewer_dock_widget(self.qt_viewer.dockConsole) self._add_viewer_dock_widget(self.qt_viewer.dockLayerControls) self._add_viewer_dock_widget(self.qt_viewer.dockLayerList) self.qt_viewer.viewer.events.status.connect(self._status_changed) self.qt_viewer.viewer.events.help.connect(self._help_changed) self.qt_viewer.viewer.events.title.connect(self._title_changed) self.qt_viewer.viewer.events.palette.connect( lambda event: self._update_palette(event.palette)) if show: self.show()
def __init__(self, qt_viewer: QtViewer, *, show: bool = True): self.qt_viewer = qt_viewer self._qt_window = QMainWindow() self._qt_window.setAttribute(Qt.WA_DeleteOnClose) self._qt_window.setUnifiedTitleAndToolBarOnMac(True) self._qt_center = QWidget(self._qt_window) self._qt_window.setCentralWidget(self._qt_center) self._qt_window.setWindowTitle(self.qt_viewer.viewer.title) self._qt_center.setLayout(QHBoxLayout()) self._status_bar = QStatusBar() self._qt_window.setStatusBar(self._status_bar) self._add_menubar() self._add_file_menu() self._add_view_menu() self._add_window_menu() self._add_plugins_menu() self._add_help_menu() self._status_bar.showMessage('Ready') self._help = QLabel('') self._status_bar.addPermanentWidget(self._help) self._qt_center.layout().addWidget(self.qt_viewer) self._qt_center.layout().setContentsMargins(4, 0, 4, 0) self._update_palette() self._add_viewer_dock_widget(self.qt_viewer.dockConsole) self._add_viewer_dock_widget(self.qt_viewer.dockLayerControls) self._add_viewer_dock_widget(self.qt_viewer.dockLayerList) self.qt_viewer.viewer.events.status.connect(self._status_changed) self.qt_viewer.viewer.events.help.connect(self._help_changed) self.qt_viewer.viewer.events.title.connect(self._title_changed) self.qt_viewer.viewer.events.palette.connect(self._update_palette) if show: self.show()
def __init__(self, parent, central_widget, name, window_width=600, window_height=400, presenter=None): super(StatusBarView, self).__init__(parent) self.presenter = presenter self.setCentralWidget(central_widget) self.setWindowTitle("{} - Mantid".format(name)) self.setAttribute(Qt.WA_DeleteOnClose, True) self.status_bar = QStatusBar(self) self.setStatusBar(self.status_bar) self.resize(window_width, window_height) self.close_signal.connect(self._run_close) self.rename_signal.connect(self._run_rename)
def __init__(self, content=None, filename=None, parent=None): """ :param content: An optional string of content to pass to the editor :param filename: The file path where the content was read. :param parent: An optional parent QWidget """ super(PythonFileInterpreter, self).__init__(parent) # layout self.editor = CodeEditor("AlternateCSPythonLexer", self) self.status = QStatusBar(self) layout = QVBoxLayout() layout.addWidget(self.editor) layout.addWidget(self.status) self.setLayout(layout) layout.setContentsMargins(0, 0, 0, 0) self._setup_editor(content, filename) self._presenter = PythonFileInterpreterPresenter( self, PythonCodeExecution(content)) self.editor.modificationChanged.connect(self.sig_editor_modified) self.editor.fileNameChanged.connect(self.sig_filename_modified)
def __init__(self, parent, preferences, signal_model, default_signal=None, is_remux=False): super(ExtractAudioDialog, self).__init__(parent) self.setupUi(self) for f in COMPRESS_FORMAT_OPTIONS: self.audioFormat.addItem(f) self.showProbeButton.setIcon(qta.icon('fa5s.info')) self.showRemuxCommand.setIcon(qta.icon('fa5s.info')) self.inputFilePicker.setIcon(qta.icon('fa5s.folder-open')) self.targetDirPicker.setIcon(qta.icon('fa5s.folder-open')) self.calculateGainAdjustment.setIcon(qta.icon('fa5s.sliders-h')) self.limitRange.setIcon(qta.icon('fa5s.cut')) self.statusBar = QStatusBar() self.statusBar.setSizeGripEnabled(False) self.boxLayout.addWidget(self.statusBar) self.__preferences = preferences self.__signal_model = signal_model self.__default_signal = default_signal self.__executor = None self.__sound = None self.__extracted = False self.__stream_duration_micros = [] self.__is_remux = is_remux if self.__is_remux: self.setWindowTitle('Remux Audio') self.showRemuxCommand.setVisible(self.__is_remux) defaultOutputDir = self.__preferences.get(EXTRACTION_OUTPUT_DIR) if os.path.isdir(defaultOutputDir): self.targetDir.setText(defaultOutputDir) self.__reinit_fields() self.filterMapping.itemDoubleClicked.connect(self.show_mapping_dialog) self.inputDrop.callback = self.__handle_drop
def __init__(self, parent=None): super(UiMainWindow, self).__init__(parent) DispatchHandle.setup(self) self.showMaximized() self.setMinimumSize(QSize(640, 480)) self.setDockOptions(QMainWindow.AnimatedDocks) self.setWindowTitle("SpecViz") self.widget_central = QWidget(self) self.setCentralWidget(self.widget_central) # Toolbar self.layout_vertical = QVBoxLayout(self.widget_central) # MDI area setup self.mdi_area = MdiArea(self.widget_central) self.mdi_area.setFrameShape(QFrame.StyledPanel) self.mdi_area.setFrameShadow(QFrame.Plain) self.mdi_area.setLineWidth(2) brush = QBrush(QColor(200, 200, 200)) brush.setStyle(Qt.SolidPattern) self.mdi_area.setBackground(brush) self.mdi_area.setAcceptDrops(True) self.layout_vertical.addWidget(self.mdi_area) # Menu bar setup self.menu_bar = QMenuBar(self) self.menu_file = QMenu(self.menu_bar) self.menu_file.setTitle("File") self.menu_edit = QMenu(self.menu_bar) self.menu_edit.setTitle("Edit") self.menu_view = QMenu(self.menu_bar) self.menu_edit.setTitle("View") self.menu_docks = QMenu(self.menu_bar) self.setMenuBar(self.menu_bar) # Status bar setup self.status_bar = QStatusBar(self) self.setStatusBar(self.status_bar)
def __init__(self, parent, preferences): super(BatchExtractDialog, self).__init__(parent) self.setupUi(self) self.setWindowFlags(self.windowFlags() | Qt.WindowSystemMenuHint | Qt.WindowMinMaxButtonsHint) self.__candidates = None self.__preferences = preferences self.__search_spinner = None default_output_dir = self.__preferences.get(EXTRACTION_OUTPUT_DIR) if os.path.isdir(default_output_dir): self.outputDir.setText(default_output_dir) filt = self.__preferences.get(EXTRACTION_BATCH_FILTER) if filt is not None: self.filter.setText(filt) self.outputDirPicker.setIcon(qta.icon('fa5s.folder-open')) self.statusBar = QStatusBar() self.verticalLayout.addWidget(self.statusBar) try: core_count = QThreadPool.globalInstance().maxThreadCount() self.threads.setMaximum(core_count) self.threads.setValue(core_count) except Exception as e: logger.warning(f"Unable to get cpu_count()", e)
def __init__(self, content=None, filename=None, parent=None): """ :param content: An optional string of content to pass to the editor :param filename: The file path where the content was read. :param parent: An optional parent QWidget """ super(PythonFileInterpreter, self).__init__(parent) # layout self.editor = CodeEditor("AlternateCSPythonLexer", self) self.status = QStatusBar(self) layout = QVBoxLayout() layout.addWidget(self.editor) layout.addWidget(self.status) self.setLayout(layout) layout.setContentsMargins(0, 0, 0, 0) self._setup_editor(content, filename) self._presenter = PythonFileInterpreterPresenter(self, PythonCodeExecution(content)) self.editor.modificationChanged.connect(self.sig_editor_modified) self.editor.fileNameChanged.connect(self.sig_filename_modified)
def setupUi(self): self.resize(800, 600) self.setWindowTitle('cutelog') self.loggerTabWidget = QTabWidget(self) self.loggerTabWidget.setTabsClosable(True) self.loggerTabWidget.setMovable(True) self.loggerTabWidget.setTabBarAutoHide(True) self.loggerTabWidget.currentChanged.connect(self.change_actions_state) self.setCentralWidget(self.loggerTabWidget) self.statusbar = QStatusBar(self) self.setStatusBar(self.statusbar) self.setup_menubar() self.setup_action_triggers() self.setup_shortcuts() self.loggerTabWidget.tabCloseRequested.connect(self.close_tab) self.reload_stylesheet() self.restore_geometry() self.show()
def __init__(self, presenter: IDataViewSubscriber, dims_info, can_normalise, parent=None, conf=None): super().__init__(parent) self.presenter = presenter self.image = None self.line_plots_active = False self.can_normalise = can_normalise self.nonortho_transform = None self.conf = conf self._line_plots = None self._image_info_tracker = None self._region_selection_on = False self._orig_lims = None # Dimension widget self.dimensions_layout = QGridLayout() self.dimensions = DimensionWidget(dims_info, parent=self) self.dimensions.dimensionsChanged.connect(self.presenter.dimensions_changed) self.dimensions.valueChanged.connect(self.presenter.slicepoint_changed) self.dimensions_layout.addWidget(self.dimensions, 1, 0, 1, 1) self.colorbar_layout = QVBoxLayout() self.colorbar_layout.setContentsMargins(0, 0, 0, 0) self.colorbar_layout.setSpacing(0) self.image_info_widget = ImageInfoWidget(self) self.image_info_widget.setToolTip("Information about the selected pixel") self.track_cursor = QCheckBox("Track Cursor", self) self.track_cursor.setToolTip( "Update the image readout table when the cursor is over the plot. " "If unticked the table will update only when the plot is clicked") self.dimensions_layout.setHorizontalSpacing(10) self.dimensions_layout.addWidget(self.track_cursor, 0, 1, Qt.AlignRight) self.dimensions_layout.addWidget(self.image_info_widget, 1, 1) self.track_cursor.setChecked(True) self.track_cursor.stateChanged.connect(self.on_track_cursor_state_change) # normalization options if can_normalise: self.norm_label = QLabel("Normalization") self.colorbar_layout.addWidget(self.norm_label) self.norm_opts = QComboBox() self.norm_opts.addItems(["None", "By bin width"]) self.norm_opts.setToolTip("Normalization options") self.colorbar_layout.addWidget(self.norm_opts) # MPL figure + colorbar self.fig = Figure() self.ax = None self.image = None self._grid_on = False self.fig.set_facecolor(self.palette().window().color().getRgbF()) self.canvas = SliceViewerCanvas(self.fig) self.canvas.mpl_connect('button_release_event', self.mouse_release) self.canvas.mpl_connect('button_press_event', self.presenter.canvas_clicked) self.colorbar_label = QLabel("Colormap") self.colorbar_layout.addWidget(self.colorbar_label) norm_scale = self.get_default_scale_norm() self.colorbar = ColorbarWidget(self, norm_scale) self.colorbar.cmap.setToolTip("Colormap options") self.colorbar.crev.setToolTip("Reverse colormap") self.colorbar.norm.setToolTip("Colormap normalisation options") self.colorbar.powerscale.setToolTip("Power colormap scale") self.colorbar.cmax.setToolTip("Colormap maximum limit") self.colorbar.cmin.setToolTip("Colormap minimum limit") self.colorbar.autoscale.setToolTip("Automatically changes colormap limits when zooming on the plot") self.colorbar_layout.addWidget(self.colorbar) self.colorbar.colorbarChanged.connect(self.update_data_clim) self.colorbar.scaleNormChanged.connect(self.scale_norm_changed) # make width larger to fit image readout table self.colorbar.setMaximumWidth(200) # MPL toolbar self.toolbar_layout = QHBoxLayout() self.mpl_toolbar = SliceViewerNavigationToolbar(self.canvas, self, False) self.mpl_toolbar.gridClicked.connect(self.toggle_grid) self.mpl_toolbar.linePlotsClicked.connect(self.on_line_plots_toggle) self.mpl_toolbar.regionSelectionClicked.connect(self.on_region_selection_toggle) self.mpl_toolbar.homeClicked.connect(self.on_home_clicked) self.mpl_toolbar.nonOrthogonalClicked.connect(self.on_non_orthogonal_axes_toggle) self.mpl_toolbar.zoomPanClicked.connect(self.presenter.zoom_pan_clicked) self.mpl_toolbar.zoomPanFinished.connect(self.on_data_limits_changed) self.toolbar_layout.addWidget(self.mpl_toolbar) # Status bar self.status_bar = QStatusBar(parent=self) self.status_bar.setStyleSheet('QStatusBar::item {border: None;}') # Hide spacers between button and label self.status_bar_label = QLabel() self.help_button = QToolButton() self.help_button.setText("?") self.status_bar.addWidget(self.help_button) self.status_bar.addWidget(self.status_bar_label) # layout layout = QGridLayout(self) layout.setSpacing(1) layout.addLayout(self.dimensions_layout, 0, 0, 1, 2) layout.addLayout(self.toolbar_layout, 1, 0, 1, 1) layout.addLayout(self.colorbar_layout, 1, 1, 3, 1) layout.addWidget(self.canvas, 2, 0, 1, 1) layout.addWidget(self.status_bar, 3, 0, 1, 1) layout.setRowStretch(2, 1)
class Window: """Application window that contains the menu bar and viewer. Parameters ---------- qt_widget : QtViewer Contained viewer widget. Attributes ---------- file_menu : qtpy.QtWidgets.QMenu File menu. help_menu : qtpy.QtWidgets.QMenu Help menu. main_menu : qtpy.QtWidgets.QMainWindow.menuBar Main menubar. qt_widget : QtViewer Contained viewer widget. view_menu : qtpy.QtWidgets.QMenu View menu. window_menu : qtpy.QtWidgets.QMenu Window menu. """ def __init__(self, qt_widget, *, show): self.qt_widget = qt_widget self._qt_window = QMainWindow() self._qt_window.setAttribute(Qt.WA_DeleteOnClose) self._qt_window.setUnifiedTitleAndToolBarOnMac(True) self._qt_center = QWidget(self._qt_window) self._qt_window.setCentralWidget(self._qt_center) if hasattr(self.qt_widget.model, "title"): self._qt_window.setWindowTitle(self.qt_widget.model.title) self._qt_center.setLayout(QHBoxLayout()) self._status_bar = QStatusBar() self._qt_window.setStatusBar(self._status_bar) self._status_bar.showMessage("Ready") self._help = QLabel("") self._status_bar.addPermanentWidget(self._help) self._qt_center.layout().addWidget(self.qt_widget) self._qt_center.layout().setContentsMargins(4, 0, 4, 0) # self._add_viewer_dock_widget(self.qt_widget.dockConsole) # self._add_viewer_dock_widget(self.qt_widget.dockLayerControls) # self._add_viewer_dock_widget(self.qt_widget.dockLayerList) # self.qt_widget.viewer.events.status.connect(self._status_changed) # self.qt_widget.viewer.events.help.connect(self._help_changed) # self.qt_widget.viewer.events.title.connect(self._title_changed) # self.qt_widget.viewer.events.palette.connect(self._update_palette) if show: self.show() def resize(self, width, height): """Resize the window. Parameters ---------- width : int Width in logical pixels. height : int Height in logical pixels. """ self._qt_window.resize(width, height) def show(self): """Resize, show, and bring forward the window.""" self._qt_window.resize(self._qt_window.layout().sizeHint()) self._qt_window.show() # We want to call Window._qt_window.raise_() in every case *except* # when instantiating a viewer within a gui_qt() context for the # _first_ time within the Qt app's lifecycle. # # `app_name` will be ours iff the application was instantiated in # gui_qt(). isActiveWindow() will be True if it is the second time a # _qt_window has been created. See #732 app_name = QApplication.instance().applicationName() if app_name != get_our_app_name() or self._qt_window.isActiveWindow(): self._qt_window.raise_() # for macOS self._qt_window.activateWindow() # for Windows def _status_changed(self, event): """Update status bar. Parameters ---------- event : qtpy.QtCore.QEvent Event from the Qt context. """ self._status_bar.showMessage(event.text) def _title_changed(self, event): """Update window title. Parameters ---------- event : qtpy.QtCore.QEvent Event from the Qt context. """ self._qt_window.setWindowTitle(event.text) def _help_changed(self, event): """Update help message on status bar. Parameters ---------- event : qtpy.QtCore.QEvent Event from the Qt context. """ self._help.setText(event.text) def _screenshot_dialog(self): """Save screenshot of current display with viewer, default .png""" filename, _ = QFileDialog.getSaveFileName( parent=self.qt_widget, caption="Save screenshot with viewer", directory=self.qt_widget._last_visited_dir, # home dir by default filter="Image files (*.png *.bmp *.gif *.tif *.tiff)", # first one used by default # jpg and jpeg not included as they don't support an alpha channel ) if (filename != "") and (filename is not None): # double check that an appropriate extension has been added as the # filter option does not always add an extension on linux and windows # see https://bugreports.qt.io/browse/QTBUG-27186 image_extensions = (".bmp", ".gif", ".png", ".tif", ".tiff") if not filename.endswith(image_extensions): filename = filename + ".png" self.screenshot(path=filename) def screenshot(self, path=None): """Take currently displayed viewer and convert to an image array. Parameters ---------- path : str Filename for saving screenshot image. Returns ------- image : array Numpy array of type ubyte and shape (h, w, 4). Index [0, 0] is the upper-left corner of the rendered region. """ img = self._qt_window.grab().toImage() if path is not None: from skimage.io import imsave from .utils import QImg2array # noqa: E402 imsave(path, QImg2array(img)) return QImg2array(img) def close(self): """Close the viewer window and cleanup sub-widgets.""" # on some versions of Darwin, exiting while fullscreen seems to tickle # some bug deep in NSWindow. This forces the fullscreen keybinding # test to complete its draw cycle, then pop back out of fullscreen. if self._qt_window.isFullScreen(): self._qt_window.showNormal() for i in range(8): time.sleep(0.1) QApplication.processEvents() self.qt_widget.close() self._qt_window.close() wait_for_workers_to_quit() del self._qt_window
class PythonFileInterpreter(QWidget): sig_editor_modified = Signal(bool) sig_filename_modified = Signal(str) sig_progress = Signal(int) sig_exec_error = Signal(object) sig_exec_success = Signal(object) def __init__(self, font=None, content=None, filename=None, parent=None): """ :param font: A reference to the font to be used by the editor. If not supplied use the system default :param content: An optional string of content to pass to the editor :param filename: The file path where the content was read. :param parent: An optional parent QWidget """ super(PythonFileInterpreter, self).__init__(parent) self.parent = parent # layout font = font if font is not None else QFont() self.editor = CodeEditor("AlternateCSPython", font, self) self.find_replace_dialog = None self.status = QStatusBar(self) self.layout = QVBoxLayout() self.layout.setContentsMargins(0, 0, 0, 0) self.layout.addWidget(self.editor) self.layout.addWidget(self.status) self.setLayout(self.layout) self._setup_editor(content, filename) self._presenter = PythonFileInterpreterPresenter(self, PythonCodeExecution(content)) self.code_commenter = CodeCommenter(self.editor) self.editor.modificationChanged.connect(self.sig_editor_modified) self.editor.fileNameChanged.connect(self.sig_filename_modified) self.setAttribute(Qt.WA_DeleteOnClose, True) # Connect the model signals to the view's signals so they can be accessed from outside the MVP self._presenter.model.sig_exec_progress.connect(self.sig_progress) self._presenter.model.sig_exec_error.connect(self.sig_exec_error) self._presenter.model.sig_exec_success.connect(self.sig_exec_success) def closeEvent(self, event): self.deleteLater() if self.find_replace_dialog: self.find_replace_dialog.close() super(PythonFileInterpreter, self).closeEvent(event) def show_find_replace_dialog(self): if self.find_replace_dialog is None: self.find_replace_dialog = EmbeddedFindReplaceDialog(self, self.editor) self.layout.insertWidget(0, self.find_replace_dialog.view) self.find_replace_dialog.show() def hide_find_replace_dialog(self): if self.find_replace_dialog is not None: self.find_replace_dialog.hide() @property def filename(self): return self.editor.fileName() def confirm_close(self): """Confirm the widget can be closed. If the editor contents are modified then a user can interject and cancel closing. :return: True if closing was considered successful, false otherwise """ return self.save(prompt_for_confirmation=self.parent.confirm_on_save) def abort(self): self._presenter.req_abort() def execute_async(self, ignore_selection=False): return self._presenter.req_execute_async(ignore_selection) def execute_async_blocking(self): self._presenter.req_execute_async_blocking() def save(self, prompt_for_confirmation=False, force_save=False): if self.editor.isModified(): io = EditorIO(self.editor) return io.save_if_required(prompt_for_confirmation, force_save) else: return True def save_as(self): io = EditorIO(self.editor) new_filename = io.ask_for_filename() if new_filename: return io.write(save_as=new_filename), new_filename else: return False, None def set_editor_readonly(self, ro): self.editor.setReadOnly(ro) def set_status_message(self, msg): self.status.showMessage(msg) def replace_tabs_with_spaces(self): self.replace_text(TAB_CHAR, SPACE_CHAR * TAB_WIDTH) def replace_text(self, match_text, replace_text): if self.editor.selectedText() == '': self.editor.selectAll() new_text = self.editor.selectedText().replace(match_text, replace_text) self.editor.replaceSelectedText(new_text) def replace_spaces_with_tabs(self): self.replace_text(SPACE_CHAR * TAB_WIDTH, TAB_CHAR) def set_whitespace_visible(self): self.editor.setWhitespaceVisibility(CodeEditor.WsVisible) def set_whitespace_invisible(self): self.editor.setWhitespaceVisibility(CodeEditor.WsInvisible) def toggle_comment(self): self.code_commenter.toggle_comment() def _setup_editor(self, default_content, filename): editor = self.editor # Clear default QsciScintilla key bindings that we want to allow # to be users of this class self.clear_key_binding("Ctrl+/") # use tabs not spaces for indentation editor.setIndentationsUseTabs(False) editor.setTabWidth(TAB_WIDTH) # show current editing line but in a softer color editor.setCaretLineBackgroundColor(CURRENTLINE_BKGD_COLOR) editor.setCaretLineVisible(True) # set a margin large enough for sensible file sizes < 1000 lines # and the progress marker font_metrics = QFontMetrics(self.font()) editor.setMarginWidth(1, font_metrics.averageCharWidth() * 3 + 20) # fill with content if supplied and set source filename if default_content is not None: editor.setText(default_content) if filename is not None: editor.setFileName(filename) # Default content does not count as a modification editor.setModified(False) editor.enableAutoCompletion(CodeEditor.AcsAll) def clear_key_binding(self, key_str): """Clear a keyboard shortcut bound to a Scintilla command""" self.editor.clearKeyBinding(key_str)
class Window: """Application window that contains the menu bar and viewer. Parameters ---------- qt_viewer : QtViewer Contained viewer widget. Attributes ---------- qt_viewer : QtViewer Contained viewer widget. """ with open(os.path.join(resources_dir, 'stylesheet.qss'), 'r') as f: raw_stylesheet = f.read() def __init__(self, qt_viewer, *, show=True): self.qt_viewer = qt_viewer self._qt_window = QMainWindow() self._qt_window.setUnifiedTitleAndToolBarOnMac(True) self._qt_center = QWidget(self._qt_window) self._qt_window.setCentralWidget(self._qt_center) self._qt_window.setWindowTitle(self.qt_viewer.viewer.title) self._qt_center.setLayout(QHBoxLayout()) self._status_bar = QStatusBar() self._qt_window.setStatusBar(self._status_bar) self._qt_window.closeEvent = self.closeEvent self.close = self._qt_window.close self._add_menubar() self._add_file_menu() self._add_view_menu() self._add_window_menu() self._add_help_menu() self._status_bar.showMessage('Ready') self._help = QLabel('') self._status_bar.addPermanentWidget(self._help) self._qt_center.layout().addWidget(self.qt_viewer) self._qt_center.layout().setContentsMargins(4, 0, 4, 0) self._update_palette(qt_viewer.viewer.palette) if self.qt_viewer.console.shell is not None: self._add_viewer_dock_widget(self.qt_viewer.dockConsole) self._add_viewer_dock_widget(self.qt_viewer.dockLayerControls) self._add_viewer_dock_widget(self.qt_viewer.dockLayerList) self.qt_viewer.viewer.events.status.connect(self._status_changed) self.qt_viewer.viewer.events.help.connect(self._help_changed) self.qt_viewer.viewer.events.title.connect(self._title_changed) self.qt_viewer.viewer.events.palette.connect( lambda event: self._update_palette(event.palette)) if show: self.show() def _add_menubar(self): self.main_menu = self._qt_window.menuBar() # Menubar shortcuts are only active when the menubar is visible. # Therefore, we set a global shortcut not associated with the menubar # to toggle visibility, *but*, in order to not shadow the menubar # shortcut, we disable it, and only enable it when the menubar is # hidden. See this stackoverflow link for details: # https://stackoverflow.com/questions/50537642/how-to-keep-the-shortcuts-of-a-hidden-widget-in-pyqt5 self._main_menu_shortcut = QShortcut(QKeySequence('Ctrl+M'), self._qt_window) self._main_menu_shortcut.activated.connect( self._toggle_menubar_visible) self._main_menu_shortcut.setEnabled(False) def _toggle_menubar_visible(self): """Toggle visibility of app menubar. This function also disables or enables a global keyboard shortcut to show the menubar, since menubar shortcuts are only available while the menubar is visible. """ if self.main_menu.isVisible(): self.main_menu.setVisible(False) self._main_menu_shortcut.setEnabled(True) else: self.main_menu.setVisible(True) self._main_menu_shortcut.setEnabled(False) def _add_file_menu(self): open_images = QAction('Open image(s)...', self._qt_window) open_images.setShortcut('Ctrl+O') open_images.setStatusTip('Open image file(s)') open_images.triggered.connect(self.qt_viewer._open_images) open_folder = QAction('Open Folder...', self._qt_window) open_folder.setShortcut('Ctrl-Shift-O') open_folder.setStatusTip( 'Open a folder of image file(s) or a zarr file') open_folder.triggered.connect(self.qt_viewer._open_folder) self.file_menu = self.main_menu.addMenu('&File') self.file_menu.addAction(open_images) self.file_menu.addAction(open_folder) def _add_view_menu(self): toggle_visible = QAction('Toggle menubar visibility', self._qt_window) toggle_visible.setShortcut('Ctrl+M') toggle_visible.setStatusTip('Hide Menubar') toggle_visible.triggered.connect(self._toggle_menubar_visible) self.view_menu = self.main_menu.addMenu('&View') self.view_menu.addAction(toggle_visible) def _add_window_menu(self): exit_action = QAction("Close window", self._qt_window) exit_action.setShortcut("Ctrl+W") exit_action.setStatusTip('Close napari window') exit_action.triggered.connect(self._qt_window.close) self.window_menu = self.main_menu.addMenu('&Window') self.window_menu.addAction(exit_action) def _add_help_menu(self): self.help_menu = self.main_menu.addMenu('&Help') about_action = QAction("napari info", self._qt_window) about_action.setShortcut("Ctrl+/") about_action.setStatusTip('About napari') about_action.triggered.connect( lambda e: QtAbout.showAbout(self.qt_viewer)) self.help_menu.addAction(about_action) about_keybindings = QAction("keybindings", self._qt_window) about_keybindings.setShortcut("Ctrl+Alt+/") about_keybindings.setShortcutContext(Qt.ApplicationShortcut) about_keybindings.setStatusTip('keybindings') about_keybindings.triggered.connect( self.qt_viewer.aboutKeybindings.toggle_visible) self.help_menu.addAction(about_keybindings) def add_dock_widget( self, widget: QWidget, *, name: str = '', area: str = 'bottom', allowed_areas=None, shortcut=None, ): """Convenience method to add a QDockWidget to the main window Parameters ---------- widget : QWidget `widget` will be added as QDockWidget's main widget. name : str, optional Name of dock widget to appear in window menu. area : str Side of the main window to which the new dock widget will be added. Must be in {'left', 'right', 'top', 'bottom'} allowed_areas : list[str], optional Areas, relative to main window, that the widget is allowed dock. Each item in list must be in {'left', 'right', 'top', 'bottom'} By default, all areas are allowed. shortcut : str, optional Keyboard shortcut to appear in dropdown menu. Returns ------- dock_widget : QtViewerDockWidget `dock_widget` that can pass viewer events. """ dock_widget = QtViewerDockWidget( self.qt_viewer, widget, name=name, area=area, allowed_areas=allowed_areas, shortcut=shortcut, ) self._add_viewer_dock_widget(dock_widget) return dock_widget def _add_viewer_dock_widget(self, dock_widget: QtViewerDockWidget): """Add a QtViewerDockWidget to the main window Parameters ---------- dock_widget : QtViewerDockWidget `dock_widget` will be added to the main window. """ dock_widget.setParent(self._qt_window) self._qt_window.addDockWidget(dock_widget.qt_area, dock_widget) action = dock_widget.toggleViewAction() action.setStatusTip(dock_widget.name) action.setText(dock_widget.name) if dock_widget.shortcut is not None: action.setShortcut(dock_widget.shortcut) self.window_menu.addAction(action) def remove_dock_widget(self, widget): """Removes specified dock widget. Parameters ---------- widget : QWidget | str If widget == 'all', all docked widgets will be removed. """ if widget == 'all': for dw in self._qt_window.findChildren(QDockWidget): self._qt_window.removeDockWidget(dw) else: self._qt_window.removeDockWidget(widget) def resize(self, width, height): """Resize the window. Parameters ---------- width : int Width in logical pixels. height : int Height in logical pixels. """ self._qt_window.resize(width, height) def show(self): """Resize, show, and bring forward the window.""" self._qt_window.resize(self._qt_window.layout().sizeHint()) self._qt_window.show() # We want to call Window._qt_window.raise_() in every case *except* # when instantiating a viewer within a gui_qt() context for the # _first_ time within the Qt app's lifecycle. # # `app_name` will be "napari" iff the application was instantiated in # gui_qt(). isActiveWindow() will be True if it is the second time a # _qt_window has been created. See #732 app_name = QApplication.instance().applicationName() if app_name != 'napari' or self._qt_window.isActiveWindow(): self._qt_window.raise_() # for macOS self._qt_window.activateWindow() # for Windows def _update_palette(self, palette): # set window styles which don't use the primary stylesheet # FIXME: this is a problem with the stylesheet not using properties self._status_bar.setStyleSheet( template( 'QStatusBar { background: {{ background }}; ' 'color: {{ text }}; }', **palette, )) self._qt_center.setStyleSheet( template('QWidget { background: {{ background }}; }', **palette)) self._qt_window.setStyleSheet(template(self.raw_stylesheet, **palette)) def _status_changed(self, event): """Update status bar. """ self._status_bar.showMessage(event.text) def _title_changed(self, event): """Update window title. """ self._qt_window.setWindowTitle(event.text) def _help_changed(self, event): """Update help message on status bar. """ self._help.setText(event.text) def screenshot(self): """Take currently displayed viewer and convert to an image array. Returns ------- image : array Numpy array of type ubyte and shape (h, w, 4). Index [0, 0] is the upper-left corner of the rendered region. """ img = self._qt_window.grab().toImage() return QImg2array(img) def closeEvent(self, event): # Forward close event to the console to trigger proper shutdown self.qt_viewer.console.shutdown() # if the viewer.QtDims object is playing an axis, we need to terminate the # AnimationThread before close, otherwise it will cauyse a segFault or Abort trap. # (calling stop() when no animation is occuring is also not a problem) self.qt_viewer.dims.stop() event.accept()
class PythonFileInterpreter(QWidget): sig_editor_modified = Signal(bool) sig_filename_modified = Signal(str) def __init__(self, content=None, filename=None, parent=None): """ :param content: An optional string of content to pass to the editor :param filename: The file path where the content was read. :param parent: An optional parent QWidget """ super(PythonFileInterpreter, self).__init__(parent) # layout self.editor = CodeEditor("AlternateCSPythonLexer", self) self.status = QStatusBar(self) layout = QVBoxLayout() layout.addWidget(self.editor) layout.addWidget(self.status) self.setLayout(layout) layout.setContentsMargins(0, 0, 0, 0) self._setup_editor(content, filename) self._presenter = PythonFileInterpreterPresenter(self, PythonCodeExecution(content)) self.editor.modificationChanged.connect(self.sig_editor_modified) self.editor.fileNameChanged.connect(self.sig_filename_modified) @property def filename(self): return self.editor.fileName() def confirm_close(self): """Confirm the widget can be closed. If the editor contents are modified then a user can interject and cancel closing. :return: True if closing was considered successful, false otherwise """ return self.save(confirm=True) def abort(self): self._presenter.req_abort() def execute_async(self): self._presenter.req_execute_async() def save(self, confirm=False): if self.editor.isModified(): io = EditorIO(self.editor) return io.save_if_required(confirm) else: return True def set_editor_readonly(self, ro): self.editor.setReadOnly(ro) def set_status_message(self, msg): self.status.showMessage(msg) def _setup_editor(self, default_content, filename): editor = self.editor # use tabs not spaces for indentation editor.setIndentationsUseTabs(False) editor.setTabWidth(TAB_WIDTH) # show current editing line but in a softer color editor.setCaretLineBackgroundColor(CURRENTLINE_BKGD_COLOR) editor.setCaretLineVisible(True) # set a margin large enough for sensible file sizes < 1000 lines # and the progress marker font_metrics = QFontMetrics(self.font()) editor.setMarginWidth(1, font_metrics.averageCharWidth() * 3 + 20) # fill with content if supplied and set source filename if default_content is not None: editor.setText(default_content) if filename is not None: editor.setFileName(filename) # Default content does not count as a modification editor.setModified(False) editor.enableAutoCompletion(CodeEditor.AcsAll)
class Window: """Application window that contains the menu bar and viewer. Parameters ---------- qt_viewer : QtViewer Contained viewer widget. Attributes ---------- file_menu : qtpy.QtWidgets.QMenu File menu. help_menu : qtpy.QtWidgets.QMenu Help menu. main_menu : qtpy.QtWidgets.QMainWindow.menuBar Main menubar. qt_viewer : QtViewer Contained viewer widget. view_menu : qtpy.QtWidgets.QMenu View menu. window_menu : qtpy.QtWidgets.QMenu Window menu. """ raw_stylesheet = get_stylesheet() def __init__(self, qt_viewer: QtViewer, *, show: bool = True): self.qt_viewer = qt_viewer self._qt_window = QMainWindow() self._qt_window.setAttribute(Qt.WA_DeleteOnClose) self._qt_window.setUnifiedTitleAndToolBarOnMac(True) self._qt_center = QWidget(self._qt_window) self._qt_window.setCentralWidget(self._qt_center) self._qt_window.setWindowTitle(self.qt_viewer.viewer.title) self._qt_center.setLayout(QHBoxLayout()) self._status_bar = QStatusBar() self._qt_window.setStatusBar(self._status_bar) self._add_menubar() self._add_file_menu() self._add_view_menu() self._add_window_menu() self._add_plugins_menu() self._add_help_menu() self._status_bar.showMessage('Ready') self._help = QLabel('') self._status_bar.addPermanentWidget(self._help) self._qt_center.layout().addWidget(self.qt_viewer) self._qt_center.layout().setContentsMargins(4, 0, 4, 0) self._update_palette() self._add_viewer_dock_widget(self.qt_viewer.dockConsole) self._add_viewer_dock_widget(self.qt_viewer.dockLayerControls) self._add_viewer_dock_widget(self.qt_viewer.dockLayerList) self.qt_viewer.viewer.events.status.connect(self._status_changed) self.qt_viewer.viewer.events.help.connect(self._help_changed) self.qt_viewer.viewer.events.title.connect(self._title_changed) self.qt_viewer.viewer.events.palette.connect(self._update_palette) if perf.USE_PERFMON: # Add DebugMenu if using perfmon. The DebugMenu is intended to # contain non-perfmon stuff as well. When it does we will want # a separate env variable for it. self._debug_menu = DebugMenu(self) # The QtPerformance widget only exists if we are using perfmon. self._add_viewer_dock_widget(self.qt_viewer.dockPerformance) else: self._debug_menu = None if show: self.show() def _add_menubar(self): """Add menubar to napari app.""" self.main_menu = self._qt_window.menuBar() # Menubar shortcuts are only active when the menubar is visible. # Therefore, we set a global shortcut not associated with the menubar # to toggle visibility, *but*, in order to not shadow the menubar # shortcut, we disable it, and only enable it when the menubar is # hidden. See this stackoverflow link for details: # https://stackoverflow.com/questions/50537642/how-to-keep-the-shortcuts-of-a-hidden-widget-in-pyqt5 self._main_menu_shortcut = QShortcut(QKeySequence('Ctrl+M'), self._qt_window) self._main_menu_shortcut.activated.connect( self._toggle_menubar_visible) self._main_menu_shortcut.setEnabled(False) def _toggle_menubar_visible(self): """Toggle visibility of app menubar. This function also disables or enables a global keyboard shortcut to show the menubar, since menubar shortcuts are only available while the menubar is visible. """ if self.main_menu.isVisible(): self.main_menu.setVisible(False) self._main_menu_shortcut.setEnabled(True) else: self.main_menu.setVisible(True) self._main_menu_shortcut.setEnabled(False) def _add_file_menu(self): """Add 'File' menu to app menubar.""" open_images = QAction('Open File(s)...', self._qt_window) open_images.setShortcut('Ctrl+O') open_images.setStatusTip('Open file(s)') open_images.triggered.connect(self.qt_viewer._open_files_dialog) open_stack = QAction('Open Files as Stack...', self._qt_window) open_stack.setShortcut('Ctrl+Alt+O') open_stack.setStatusTip('Open files') open_stack.triggered.connect( self.qt_viewer._open_files_dialog_as_stack_dialog) open_folder = QAction('Open Folder...', self._qt_window) open_folder.setShortcut('Ctrl+Shift+O') open_folder.setStatusTip('Open a folder') open_folder.triggered.connect(self.qt_viewer._open_folder_dialog) save_selected_layers = QAction('Save Selected Layer(s)...', self._qt_window) save_selected_layers.setShortcut('Ctrl+S') save_selected_layers.setStatusTip('Save selected layers') save_selected_layers.triggered.connect( lambda: self.qt_viewer._save_layers_dialog(selected=True)) save_all_layers = QAction('Save All Layers...', self._qt_window) save_all_layers.setShortcut('Ctrl+Shift+S') save_all_layers.setStatusTip('Save all layers') save_all_layers.triggered.connect( lambda: self.qt_viewer._save_layers_dialog(selected=False)) screenshot = QAction('Save Screenshot...', self._qt_window) screenshot.setShortcut('Alt+S') screenshot.setStatusTip( 'Save screenshot of current display, default .png') screenshot.triggered.connect(self.qt_viewer._screenshot_dialog) screenshot_wv = QAction('Save Screenshot with Viewer...', self._qt_window) screenshot_wv.setShortcut('Alt+Shift+S') screenshot_wv.setStatusTip( 'Save screenshot of current display with the viewer, default .png') screenshot_wv.triggered.connect(self._screenshot_dialog) # OS X will rename this to Quit and put it in the app menu. exitAction = QAction('Exit', self._qt_window) exitAction.setShortcut('Ctrl+Q') exitAction.setMenuRole(QAction.QuitRole) def handle_exit(): # if the event loop was started in gui_qt() then the app will be # named 'napari'. Since the Qapp was started by us, just close it. if QApplication.applicationName() == 'napari': QApplication.closeAllWindows() QApplication.quit() # otherwise, something else created the QApp before us (such as # %gui qt IPython magic). If we quit the app in this case, then # *later* attempts to instantiate a napari viewer won't work until # the event loop is restarted with app.exec_(). So rather than # quit just close all the windows (and clear our app icon). else: QApplication.setWindowIcon(QIcon()) self.close() if perf.USE_PERFMON: # Write trace file before exit, if we were writing one. # Is there a better place to make sure this is done on exit? perf.timers.stop_trace_file() exitAction.triggered.connect(handle_exit) self.file_menu = self.main_menu.addMenu('&File') self.file_menu.addAction(open_images) self.file_menu.addAction(open_stack) self.file_menu.addAction(open_folder) self.file_menu.addSeparator() self.file_menu.addAction(save_selected_layers) self.file_menu.addAction(save_all_layers) self.file_menu.addAction(screenshot) self.file_menu.addAction(screenshot_wv) self.file_menu.addSeparator() self.file_menu.addAction(exitAction) def _add_view_menu(self): """Add 'View' menu to app menubar.""" toggle_visible = QAction('Toggle Menubar Visibility', self._qt_window) toggle_visible.setShortcut('Ctrl+M') toggle_visible.setStatusTip('Hide Menubar') toggle_visible.triggered.connect(self._toggle_menubar_visible) toggle_theme = QAction('Toggle Theme', self._qt_window) toggle_theme.setShortcut('Ctrl+Shift+T') toggle_theme.setStatusTip('Toggle theme') toggle_theme.triggered.connect(self.qt_viewer.viewer._toggle_theme) self.view_menu = self.main_menu.addMenu('&View') self.view_menu.addAction(toggle_visible) self.view_menu.addAction(toggle_theme) def _add_window_menu(self): """Add 'Window' menu to app menubar.""" exit_action = QAction("Close Window", self._qt_window) exit_action.setShortcut("Ctrl+W") exit_action.setStatusTip('Close napari window') exit_action.triggered.connect(self._qt_window.close) self.window_menu = self.main_menu.addMenu('&Window') self.window_menu.addAction(exit_action) def _add_plugins_menu(self): """Add 'Plugins' menu to app menubar.""" self.plugins_menu = self.main_menu.addMenu('&Plugins') list_plugins_action = QAction("List Installed Plugins...", self._qt_window) list_plugins_action.setStatusTip('List installed plugins') list_plugins_action.triggered.connect(self._show_plugin_list) self.plugins_menu.addAction(list_plugins_action) order_plugin_action = QAction("Plugin Call Order...", self._qt_window) order_plugin_action.setStatusTip('Change call order for plugins') order_plugin_action.triggered.connect(self._show_plugin_sorter) self.plugins_menu.addAction(order_plugin_action) report_plugin_action = QAction("Plugin Errors...", self._qt_window) report_plugin_action.setStatusTip( 'Review stack traces for plugin exceptions and notify developers') report_plugin_action.triggered.connect(self._show_plugin_err_reporter) self.plugins_menu.addAction(report_plugin_action) def _show_plugin_list(self, plugin_manager=None): """Show dialog with a table of installed plugins and metadata.""" QtPluginTable(self._qt_window).exec_() def _show_plugin_sorter(self): """Show dialog that allows users to sort the call order of plugins.""" plugin_sorter = QtPluginSorter(parent=self._qt_window) dock_widget = self.add_dock_widget(plugin_sorter, name='Plugin Sorter', area="right") plugin_sorter.finished.connect(dock_widget.close) plugin_sorter.finished.connect(plugin_sorter.deleteLater) plugin_sorter.finished.connect(dock_widget.deleteLater) def _show_plugin_err_reporter(self): """Show dialog that allows users to review and report plugin errors.""" plugin_sorter = QtPluginErrReporter(parent=self._qt_window) plugin_sorter.exec_() def _add_help_menu(self): """Add 'Help' menu to app menubar.""" self.help_menu = self.main_menu.addMenu('&Help') about_action = QAction("napari Info", self._qt_window) about_action.setShortcut("Ctrl+/") about_action.setStatusTip('About napari') about_action.triggered.connect( lambda e: QtAbout.showAbout(self.qt_viewer)) self.help_menu.addAction(about_action) about_key_bindings = QAction("Show Key Bindings", self._qt_window) about_key_bindings.setShortcut("Ctrl+Alt+/") about_key_bindings.setShortcutContext(Qt.ApplicationShortcut) about_key_bindings.setStatusTip('key_bindings') about_key_bindings.triggered.connect( self.qt_viewer.show_key_bindings_dialog) self.help_menu.addAction(about_key_bindings) def add_dock_widget( self, widget: QWidget, *, name: str = '', area: str = 'bottom', allowed_areas=None, shortcut=None, ): """Convenience method to add a QDockWidget to the main window Parameters ---------- widget : QWidget `widget` will be added as QDockWidget's main widget. name : str, optional Name of dock widget to appear in window menu. area : str Side of the main window to which the new dock widget will be added. Must be in {'left', 'right', 'top', 'bottom'} allowed_areas : list[str], optional Areas, relative to main window, that the widget is allowed dock. Each item in list must be in {'left', 'right', 'top', 'bottom'} By default, all areas are allowed. shortcut : str, optional Keyboard shortcut to appear in dropdown menu. Returns ------- dock_widget : QtViewerDockWidget `dock_widget` that can pass viewer events. """ dock_widget = QtViewerDockWidget( self.qt_viewer, widget, name=name, area=area, allowed_areas=allowed_areas, shortcut=shortcut, ) self._add_viewer_dock_widget(dock_widget) return dock_widget def _add_viewer_dock_widget(self, dock_widget: QtViewerDockWidget): """Add a QtViewerDockWidget to the main window Parameters ---------- dock_widget : QtViewerDockWidget `dock_widget` will be added to the main window. """ dock_widget.setParent(self._qt_window) self._qt_window.addDockWidget(dock_widget.qt_area, dock_widget) action = dock_widget.toggleViewAction() action.setStatusTip(dock_widget.name) action.setText(dock_widget.name) if dock_widget.shortcut is not None: action.setShortcut(dock_widget.shortcut) self.window_menu.addAction(action) def remove_dock_widget(self, widget): """Removes specified dock widget. Parameters ---------- widget : QWidget | str If widget == 'all', all docked widgets will be removed. """ if widget == 'all': for dw in self._qt_window.findChildren(QDockWidget): self._qt_window.removeDockWidget(dw) else: self._qt_window.removeDockWidget(widget) def resize(self, width, height): """Resize the window. Parameters ---------- width : int Width in logical pixels. height : int Height in logical pixels. """ self._qt_window.resize(width, height) def show(self): """Resize, show, and bring forward the window.""" self._qt_window.resize(self._qt_window.layout().sizeHint()) self._qt_window.show() # Resize axis labels now that window is shown self.qt_viewer.dims._resize_axis_labels() # We want to call Window._qt_window.raise_() in every case *except* # when instantiating a viewer within a gui_qt() context for the # _first_ time within the Qt app's lifecycle. # # `app_name` will be "napari" iff the application was instantiated in # gui_qt(). isActiveWindow() will be True if it is the second time a # _qt_window has been created. See #732 app_name = QApplication.instance().applicationName() if app_name != 'napari' or self._qt_window.isActiveWindow(): self._qt_window.raise_() # for macOS self._qt_window.activateWindow() # for Windows def _update_palette(self, event=None): """Update widget color palette.""" # set window styles which don't use the primary stylesheet # FIXME: this is a problem with the stylesheet not using properties palette = self.qt_viewer.viewer.palette self._status_bar.setStyleSheet( template( 'QStatusBar { background: {{ background }}; ' 'color: {{ text }}; }', **palette, )) self._qt_center.setStyleSheet( template('QWidget { background: {{ background }}; }', **palette)) self._qt_window.setStyleSheet(template(self.raw_stylesheet, **palette)) def _status_changed(self, event): """Update status bar. Parameters ---------- event : napari.utils.event.Event The napari event that triggered this method. """ self._status_bar.showMessage(event.text) def _title_changed(self, event): """Update window title. Parameters ---------- event : napari.utils.event.Event The napari event that triggered this method. """ self._qt_window.setWindowTitle(event.text) def _help_changed(self, event): """Update help message on status bar. Parameters ---------- event : napari.utils.event.Event The napari event that triggered this method. """ self._help.setText(event.text) def _screenshot_dialog(self): """Save screenshot of current display with viewer, default .png""" filename, _ = QFileDialog.getSaveFileName( parent=self.qt_viewer, caption='Save screenshot with viewer', directory=self.qt_viewer._last_visited_dir, # home dir by default filter= "Image files (*.png *.bmp *.gif *.tif *.tiff)", # first one used by default # jpg and jpeg not included as they don't support an alpha channel ) if (filename != '') and (filename is not None): # double check that an appropriate extension has been added as the # filter option does not always add an extension on linux and windows # see https://bugreports.qt.io/browse/QTBUG-27186 image_extensions = ('.bmp', '.gif', '.png', '.tif', '.tiff') if not filename.endswith(image_extensions): filename = filename + '.png' self.screenshot(path=filename) def screenshot(self, path=None): """Take currently displayed viewer and convert to an image array. Parameters ---------- path : str Filename for saving screenshot image. Returns ------- image : array Numpy array of type ubyte and shape (h, w, 4). Index [0, 0] is the upper-left corner of the rendered region. """ img = self._qt_window.grab().toImage() if path is not None: imsave(path, QImg2array(img)) # scikit-image imsave method return QImg2array(img) def close(self): """Close the viewer window and cleanup sub-widgets.""" # on some versions of Darwin, exiting while fullscreen seems to tickle # some bug deep in NSWindow. This forces the fullscreen keybinding # test to complete its draw cycle, then pop back out of fullscreen. if self._qt_window.isFullScreen(): self._qt_window.showNormal() for i in range(8): time.sleep(0.1) QApplication.processEvents() self.qt_viewer.close() self._qt_window.close() del self._qt_window
def __init__(self): super(XicamMainWindow, self).__init__() # Set icon self.setWindowIcon(QIcon(QPixmap(str(path("icons/xicam.gif"))))) # Set size and position self.setGeometry(0, 0, 1000, 600) frameGm = self.frameGeometry() screen = QApplication.desktop().screenNumber( QApplication.desktop().cursor().pos()) centerPoint = QApplication.desktop().screenGeometry(screen).center() frameGm.moveCenter(centerPoint) self.move(frameGm.topLeft()) # Init child widgets to None self.topwidget = (self.leftwidget) = (self.rightwidget) = ( self.bottomwidget ) = self.lefttopwidget = self.righttopwidget = self.leftbottomwidget = self.rightbottomwidget = None # Setup appearance self.setWindowTitle("Xi-cam") self.load_style() # Attach an object to restore config when loaded self._config_restorer = ConfigRestorer() # Load plugins pluginmanager.qt_is_safe = True pluginmanager.initialize_types() pluginmanager.collect_plugins() pluginmanager.collect_user_plugins() # Setup center/toolbar/statusbar/progressbar self.pluginmodewidget = pluginModeWidget() self.pluginmodewidget.sigSetStage.connect(self.setStage) self.pluginmodewidget.sigSetGUIPlugin.connect(self.setGUIPlugin) self.addToolBar(self.pluginmodewidget) self.setStatusBar(QStatusBar(self)) msg.progressbar = QProgressBar(self) msg.progressbar.hide() msg.statusbar = self.statusBar() self.statusBar().addPermanentWidget(msg.progressbar) self.setCentralWidget(QStackedWidget()) # NOTE: CentralWidgets are force-deleted when replaced, even if the object is still referenced; # To avoid this, a QStackedWidget is used for the central widget. # Setup menubar menubar = DebuggableMenuBar() self.setMenuBar(menubar) file = QMenu("&File", parent=menubar) plugins = QMenu("&Plugins", parent=menubar) menubar.addMenu(file) file.addAction("Se&ttings", self.showSettings, shortcut=QKeySequence(Qt.CTRL + Qt.ALT + Qt.Key_S)) file.addAction("E&xit", self.close) menubar.addMenu(plugins) plugins.addAction("Open User &Plugin Directory", self.openUserPluginDir, shortcut=QKeySequence(Qt.CTRL + Qt.ALT + Qt.Key_P)) # Set up help help = QMenu("&Help", parent=menubar) documentation_link = QUrl("https://xi-cam.readthedocs.io/en/latest/") help.addAction("Xi-CAM &Help", lambda: QDesktopServices.openUrl(documentation_link)) slack_link = QUrl("https://nikea.slack.com") help.addAction("Chat on &Slack", lambda: QDesktopServices.openUrl(slack_link)) help.addSeparator() about_title = "About Xi-CAM" version_text = f"""Version: <strong>{version.get_versions()['version']}</strong>""" copyright_text = f"""<small>Copyright (c) 2016, The Regents of the University of California, \ through Lawrence Berkeley National Laboratory \ (subject to receipt of any required approvals from the U.S. Dept. of Energy). \ All rights reserved.</small>""" funding_text = f"""Funding for this research was provided by: \ Lawrence Berkeley National Laboratory (grant No. TReXS LDRD to AH); \ US Department of Energy (award No. Early Career Award to AH; \ contract No. DE-SC0012704; contract No. DE-AC02-06CH11357; \ contract No. DE-AC02-76SF00515; contract No. DE-AC02-05CH11231); \ Center for Advanced Mathematics in Energy Research Applications; \ Light Source Directors Data Solution Task Force Pilot Project.""" about_text = version_text + "<br><br>" + funding_text + "<br><hr>" + copyright_text about_box = QMessageBox(QMessageBox.NoIcon, about_title, about_text) about_box.setTextFormat(Qt.RichText) about_box.setWindowModality(Qt.NonModal) help.addAction("&About Xi-CAM", lambda: about_box.show()) help.addSeparator() help.addAction(QWhatsThis.createAction(help)) menubar.addMenu(help) # Initialize layout with first plugin self._currentGUIPlugin = None self.build_layout() # self._currentGUIPlugin = pluginmanager.getPluginsOfCategory("GUIPlugin")[0] self.populate_layout() # Make F key bindings fkeys = [ Qt.Key_F1, Qt.Key_F2, Qt.Key_F3, Qt.Key_F4, Qt.Key_F5, Qt.Key_F6, Qt.Key_F7, Qt.Key_F8, Qt.Key_F9, Qt.Key_F10, Qt.Key_F11, Qt.Key_F12, ] self.Fshortcuts = [QShortcut(QKeySequence(key), self) for key in fkeys] for i in range(12): self.Fshortcuts[i].activated.connect(partial(self.setStage, i)) self.readSettings() # Wireup default widgets get_default_stage()["left"].sigOpen.connect(self.open) get_default_stage()["left"].sigOpen.connect(print) get_default_stage()["left"].sigPreview.connect( get_default_stage()["lefttop"].preview)
def __init__(self): super(XicamMainWindow, self).__init__() # Set icon self.setWindowIcon(QIcon(QPixmap(str(path("icons/xicam.gif"))))) # Set size and position self.setGeometry(0, 0, 1000, 600) frameGm = self.frameGeometry() screen = QApplication.desktop().screenNumber( QApplication.desktop().cursor().pos()) centerPoint = QApplication.desktop().screenGeometry(screen).center() frameGm.moveCenter(centerPoint) self.move(frameGm.topLeft()) # Init child widgets to None self.topwidget = (self.leftwidget) = (self.rightwidget) = ( self.bottomwidget ) = self.lefttopwidget = self.righttopwidget = self.leftbottomwidget = self.rightbottomwidget = None # Setup appearance self.setWindowTitle("Xi-cam") self._configdialog = ConfigDialog() # Load plugins pluginmanager.collect_plugins() # Setup center/toolbar/statusbar/progressbar self.pluginmodewidget = pluginModeWidget() self.pluginmodewidget.sigSetStage.connect(self.setStage) self.pluginmodewidget.sigSetGUIPlugin.connect(self.setGUIPlugin) self.addToolBar(self.pluginmodewidget) self.setStatusBar(QStatusBar(self)) msg.progressbar = QProgressBar(self) msg.progressbar.hide() msg.statusbar = self.statusBar() self.statusBar().addPermanentWidget(msg.progressbar) self.setCentralWidget(QStackedWidget()) # NOTE: CentralWidgets are force-deleted when replaced, even if the object is still referenced; # To avoid this, a QStackedWidget is used for the central widget. # Setup menubar menubar = DebuggableMenuBar() self.setMenuBar(menubar) file = QMenu("&File", parent=menubar) menubar.addMenu(file) file.addAction("Se&ttings", self.showSettings, shortcut=QKeySequence(Qt.CTRL + Qt.ALT + Qt.Key_S)) file.addAction("E&xit", self.close) help = QMenu("&Help", parent=menubar) menubar.addMenu(help) # Initialize layout with first plugin self._currentGUIPlugin = None self.build_layout() # self._currentGUIPlugin = pluginmanager.getPluginsOfCategory("GUIPlugin")[0] self.populate_layout() # Make F key bindings fkeys = [ Qt.Key_F1, Qt.Key_F2, Qt.Key_F3, Qt.Key_F4, Qt.Key_F5, Qt.Key_F6, Qt.Key_F7, Qt.Key_F8, Qt.Key_F9, Qt.Key_F10, Qt.Key_F11, Qt.Key_F12, ] self.Fshortcuts = [QShortcut(QKeySequence(key), self) for key in fkeys] for i in range(12): self.Fshortcuts[i].activated.connect(partial(self.setStage, i)) self.readSettings() # Wireup default widgets defaultstage["left"].sigOpen.connect(self.open) defaultstage["left"].sigOpen.connect(print) defaultstage["left"].sigPreview.connect( defaultstage["lefttop"].preview)
class SliceViewerDataView(QWidget): """The view for the data portion of the sliceviewer""" def __init__(self, presenter: IDataViewSubscriber, dims_info, can_normalise, parent=None, conf=None): super().__init__(parent) self.presenter = presenter self.image = None self.line_plots_active = False self.can_normalise = can_normalise self.nonortho_transform = None self.conf = conf self._line_plots = None self._image_info_tracker = None self._region_selection_on = False self._orig_lims = None # Dimension widget self.dimensions_layout = QGridLayout() self.dimensions = DimensionWidget(dims_info, parent=self) self.dimensions.dimensionsChanged.connect(self.presenter.dimensions_changed) self.dimensions.valueChanged.connect(self.presenter.slicepoint_changed) self.dimensions_layout.addWidget(self.dimensions, 1, 0, 1, 1) self.colorbar_layout = QVBoxLayout() self.colorbar_layout.setContentsMargins(0, 0, 0, 0) self.colorbar_layout.setSpacing(0) self.image_info_widget = ImageInfoWidget(self) self.image_info_widget.setToolTip("Information about the selected pixel") self.track_cursor = QCheckBox("Track Cursor", self) self.track_cursor.setToolTip( "Update the image readout table when the cursor is over the plot. " "If unticked the table will update only when the plot is clicked") self.dimensions_layout.setHorizontalSpacing(10) self.dimensions_layout.addWidget(self.track_cursor, 0, 1, Qt.AlignRight) self.dimensions_layout.addWidget(self.image_info_widget, 1, 1) self.track_cursor.setChecked(True) self.track_cursor.stateChanged.connect(self.on_track_cursor_state_change) # normalization options if can_normalise: self.norm_label = QLabel("Normalization") self.colorbar_layout.addWidget(self.norm_label) self.norm_opts = QComboBox() self.norm_opts.addItems(["None", "By bin width"]) self.norm_opts.setToolTip("Normalization options") self.colorbar_layout.addWidget(self.norm_opts) # MPL figure + colorbar self.fig = Figure() self.ax = None self.image = None self._grid_on = False self.fig.set_facecolor(self.palette().window().color().getRgbF()) self.canvas = SliceViewerCanvas(self.fig) self.canvas.mpl_connect('button_release_event', self.mouse_release) self.canvas.mpl_connect('button_press_event', self.presenter.canvas_clicked) self.colorbar_label = QLabel("Colormap") self.colorbar_layout.addWidget(self.colorbar_label) norm_scale = self.get_default_scale_norm() self.colorbar = ColorbarWidget(self, norm_scale) self.colorbar.cmap.setToolTip("Colormap options") self.colorbar.crev.setToolTip("Reverse colormap") self.colorbar.norm.setToolTip("Colormap normalisation options") self.colorbar.powerscale.setToolTip("Power colormap scale") self.colorbar.cmax.setToolTip("Colormap maximum limit") self.colorbar.cmin.setToolTip("Colormap minimum limit") self.colorbar.autoscale.setToolTip("Automatically changes colormap limits when zooming on the plot") self.colorbar_layout.addWidget(self.colorbar) self.colorbar.colorbarChanged.connect(self.update_data_clim) self.colorbar.scaleNormChanged.connect(self.scale_norm_changed) # make width larger to fit image readout table self.colorbar.setMaximumWidth(200) # MPL toolbar self.toolbar_layout = QHBoxLayout() self.mpl_toolbar = SliceViewerNavigationToolbar(self.canvas, self, False) self.mpl_toolbar.gridClicked.connect(self.toggle_grid) self.mpl_toolbar.linePlotsClicked.connect(self.on_line_plots_toggle) self.mpl_toolbar.regionSelectionClicked.connect(self.on_region_selection_toggle) self.mpl_toolbar.homeClicked.connect(self.on_home_clicked) self.mpl_toolbar.nonOrthogonalClicked.connect(self.on_non_orthogonal_axes_toggle) self.mpl_toolbar.zoomPanClicked.connect(self.presenter.zoom_pan_clicked) self.mpl_toolbar.zoomPanFinished.connect(self.on_data_limits_changed) self.toolbar_layout.addWidget(self.mpl_toolbar) # Status bar self.status_bar = QStatusBar(parent=self) self.status_bar.setStyleSheet('QStatusBar::item {border: None;}') # Hide spacers between button and label self.status_bar_label = QLabel() self.help_button = QToolButton() self.help_button.setText("?") self.status_bar.addWidget(self.help_button) self.status_bar.addWidget(self.status_bar_label) # layout layout = QGridLayout(self) layout.setSpacing(1) layout.addLayout(self.dimensions_layout, 0, 0, 1, 2) layout.addLayout(self.toolbar_layout, 1, 0, 1, 1) layout.addLayout(self.colorbar_layout, 1, 1, 3, 1) layout.addWidget(self.canvas, 2, 0, 1, 1) layout.addWidget(self.status_bar, 3, 0, 1, 1) layout.setRowStretch(2, 1) @property def grid_on(self): return self._grid_on @property def line_plotter(self): return self._line_plots @property def nonorthogonal_mode(self): return self.nonortho_transform is not None def create_axes_orthogonal(self, redraw_on_zoom=False): """Create a standard set of orthogonal axes :param redraw_on_zoom: If True then when scroll zooming the canvas is redrawn immediately """ self.clear_figure() self.nonortho_transform = None self.ax = self.fig.add_subplot(111, projection='mantid') self.enable_zoom_on_mouse_scroll(redraw_on_zoom) if self.grid_on: self.ax.grid(self.grid_on) if self.line_plots_active: self.add_line_plots() self.plot_MDH = self.plot_MDH_orthogonal self.canvas.draw_idle() def create_axes_nonorthogonal(self, transform): self.clear_figure() self.set_nonorthogonal_transform(transform) self.ax = CurveLinearSubPlot(self.fig, 1, 1, 1, grid_helper=GridHelperCurveLinear( (transform.tr, transform.inv_tr))) # don't redraw on zoom as the data is rebinned and has to be redrawn again anyway self.enable_zoom_on_mouse_scroll(redraw=False) self.set_grid_on() self.fig.add_subplot(self.ax) self.plot_MDH = self.plot_MDH_nonorthogonal self.canvas.draw_idle() def enable_zoom_on_mouse_scroll(self, redraw): """Enable zoom on scroll the mouse wheel for the created axes :param redraw: Pass through to redraw option in enable_zoom_on_scroll """ self.canvas.enable_zoom_on_scroll(self.ax, redraw=redraw, toolbar=self.mpl_toolbar, callback=self.on_data_limits_changed) def add_line_plots(self, toolcls, exporter): """Assuming line plots are currently disabled, enable them on the current figure The image axes must have been created first. :param toolcls: Use this class to handle creating the plots :param exporter: Object defining methods to export cuts/roi """ if self.line_plots_active: return self.line_plots_active = True self._line_plots = toolcls(LinePlots(self.ax, self.colorbar), exporter) self.status_bar_label.setText(self._line_plots.status_message()) self.canvas.setFocus() self.mpl_toolbar.set_action_checked(ToolItemText.LINEPLOTS, True, trigger=False) def switch_line_plots_tool(self, toolcls, exporter): """Assuming line plots are currently enabled then switch the tool used to generate the plot curves. :param toolcls: Use this class to handle creating the plots """ if not self.line_plots_active: return # Keep the same set of line plots axes but swap the selection tool plotter = self._line_plots.plotter plotter.delete_line_plot_lines() self._line_plots.disconnect() self._line_plots = toolcls(plotter, exporter) self.status_bar_label.setText(self._line_plots.status_message()) self.canvas.setFocus() self.canvas.draw_idle() def remove_line_plots(self): """Assuming line plots are currently enabled, remove them from the current figure """ if not self.line_plots_active: return self._line_plots.plotter.close() self.status_bar_label.clear() self._line_plots = None self.line_plots_active = False def plot_MDH_orthogonal(self, ws, **kwargs): """ clears the plot and creates a new one using a MDHistoWorkspace """ self.clear_image() self.image = self.ax.imshow(ws, origin='lower', aspect='auto', transpose=self.dimensions.transpose, norm=self.colorbar.get_norm(), **kwargs) # ensure the axes data limits are updated to match the # image. For example if the axes were zoomed and the # swap dimensions was clicked we need to restore the # appropriate extents to see the image in the correct place extent = self.image.get_extent() self.ax.set_xlim(extent[0], extent[1]) self.ax.set_ylim(extent[2], extent[3]) # Set the original data limits which get passed to the ImageInfoWidget so that # the mouse projection to data space is correct for MDH workspaces when zoomed/changing slices self._orig_lims = self.get_axes_limits() self.on_track_cursor_state_change(self.track_cursor_checked()) self.draw_plot() def plot_MDH_nonorthogonal(self, ws, **kwargs): self.clear_image() self.image = pcolormesh_nonorthogonal(self.ax, ws, self.nonortho_transform.tr, transpose=self.dimensions.transpose, norm=self.colorbar.get_norm(), **kwargs) self.on_track_cursor_state_change(self.track_cursor_checked()) # swapping dimensions in nonorthogonal mode currently resets back to the # full data limits as the whole axes has been recreated so we don't have # access to the original limits # pcolormesh clears any grid that was previously visible if self.grid_on: self.ax.grid(self.grid_on) self.draw_plot() def plot_matrix(self, ws, **kwargs): """ clears the plot and creates a new one using a MatrixWorkspace keeping the axes limits that have already been set """ # ensure view is correct if zoomed in while swapping dimensions # compute required extent and just have resampling imshow deal with it old_extent = None if self.image is not None: old_extent = self.image.get_extent() if self.image.transpose != self.dimensions.transpose: e1, e2, e3, e4 = old_extent old_extent = e3, e4, e1, e2 self.clear_image() self.image = self.ax.imshow(ws, origin='lower', aspect='auto', interpolation='none', transpose=self.dimensions.transpose, norm=self.colorbar.get_norm(), extent=old_extent, **kwargs) self.on_track_cursor_state_change(self.track_cursor_checked()) self.draw_plot() def clear_image(self): """Removes any image from the axes""" if self.image is not None: if self.line_plots_active: self._line_plots.plotter.delete_line_plot_lines() self.image_info_widget.cursorAt(DBLMAX, DBLMAX, DBLMAX) if hasattr(self.ax, "remove_artists_if"): self.ax.remove_artists_if(lambda art: art == self.image) else: self.image.remove() self.image = None def clear_figure(self): """Removes everything from the figure""" if self.line_plots_active: self._line_plots.plotter.close() self.line_plots_active = False self.image = None self.canvas.disable_zoom_on_scroll() self.fig.clf() self.ax = None def draw_plot(self): self.ax.set_title('') self.canvas.draw() if self.image: self.colorbar.set_mappable(self.image) self.colorbar.update_clim() self.mpl_toolbar.update() # clear nav stack if self.line_plots_active: self._line_plots.plotter.delete_line_plot_lines() self._line_plots.plotter.update_line_plot_labels() def export_region(self, limits, cut): """ React to a region selection that should be exported :param limits: 2-tuple of ((left, right), (bottom, top)) :param cut: A str denoting which cuts to export. """ self.presenter.export_region(limits, cut) def update_plot_data(self, data): """ This just updates the plot data without creating a new plot. The extents can change if the data has been rebinned. """ if self.nonortho_transform: self.image.set_array(data.T.ravel()) else: self.image.set_data(data.T) self.colorbar.update_clim() def track_cursor_checked(self): return self.track_cursor.isChecked() if self.track_cursor else False def on_track_cursor_state_change(self, state): """ Called to notify the current state of the track cursor box """ if self._image_info_tracker is not None: self._image_info_tracker.disconnect() if self._line_plots is not None and not self._region_selection_on: self._line_plots.disconnect() self._image_info_tracker = ImageInfoTracker(image=self.image, transform=self.nonortho_transform, do_transform=self.nonorthogonal_mode, widget=self.image_info_widget, cursor_transform=self._orig_lims) if state: self._image_info_tracker.connect() if self._line_plots and not self._region_selection_on: self._line_plots.connect() else: self._image_info_tracker.disconnect() if self._line_plots and not self._region_selection_on: self._line_plots.disconnect() def on_home_clicked(self): """Reset the view to encompass all of the data""" self.presenter.show_all_data_clicked() def on_line_plots_toggle(self, state): """Switch state of the line plots""" self.presenter.line_plots(state) def on_region_selection_toggle(self, state): """Switch state of the region selection""" self.presenter.region_selection(state) self._region_selection_on = state # If state is off and track cursor is on, make sure line plots are re-connected to move cursor if not state and self.track_cursor_checked(): if self._line_plots: self._line_plots.connect() def on_non_orthogonal_axes_toggle(self, state): """ Switch state of the non-orthognal axes on/off """ self.presenter.nonorthogonal_axes(state) def on_data_limits_changed(self): """ React to when the data limits have changed """ self.presenter.data_limits_changed() def deactivate_and_disable_tool(self, tool_text): """Deactivate a tool as if the control had been pressed and disable the functionality""" self.deactivate_tool(tool_text) self.disable_tool_button(tool_text) def activate_tool(self, tool_text): """Activate a given tool as if the control had been pressed""" self.mpl_toolbar.set_action_checked(tool_text, True) def deactivate_tool(self, tool_text): """Deactivate a given tool as if the tool button had been pressed""" self.mpl_toolbar.set_action_checked(tool_text, False) def enable_tool_button(self, tool_text): """Set a given tool button enabled so it can be interacted with""" self.mpl_toolbar.set_action_enabled(tool_text, True) def disable_tool_button(self, tool_text): """Set a given tool button disabled so it cannot be interacted with""" self.mpl_toolbar.set_action_enabled(tool_text, False) def get_axes_limits(self): """ Return the limits on the image axes transformed into the nonorthogonal frame if appropriate """ if self.image is None: return None else: xlim, ylim = self.ax.get_xlim(), self.ax.get_ylim() if self.nonorthogonal_mode: inv_tr = self.nonortho_transform.inv_tr # viewing axis y not aligned with plot axis xmin_p, ymax_p = inv_tr(xlim[0], ylim[1]) xmax_p, ymin_p = inv_tr(xlim[1], ylim[0]) xlim, ylim = (xmin_p, xmax_p), (ymin_p, ymax_p) return xlim, ylim def get_full_extent(self): """ Return the full extent of image - only applicable for plots of matrix workspaces """ if self.image and isinstance(self.image, samplingimage.SamplingImage): return self.image.get_full_extent() else: return None def set_axes_limits(self, xlim, ylim): """ Set the view limits on the image axes to the given extents. Assume the limits are in the orthogonal frame. :param xlim: 2-tuple of (xmin, xmax) :param ylim: 2-tuple of (ymin, ymax) """ self.ax.set_xlim(xlim) self.ax.set_ylim(ylim) def set_grid_on(self): """ If not visible sets the grid visibility """ if not self._grid_on: self._grid_on = True self.mpl_toolbar.set_action_checked(ToolItemText.GRID, state=self._grid_on) def set_nonorthogonal_transform(self, transform): """ Set the transform for nonorthogonal axes mode :param transform: An object with a tr method to transform from nonorthognal coordinates to display coordinates """ self.nonortho_transform = transform def show_temporary_status_message(self, msg, timeout_ms): """ Show a message in the status bar that disappears after a set period :param msg: A str message to display :param timeout_ms: Timeout in milliseconds to display the message for """ self.status_bar.showMessage(msg, timeout_ms) def toggle_grid(self, state): """ Toggle the visibility of the grid on the axes """ self._grid_on = state self.ax.grid(self._grid_on) self.canvas.draw_idle() def mouse_release(self, event): if event.inaxes != self.ax: return self.canvas.setFocus() if event.button == 1: self._image_info_tracker.on_cursor_at(event.xdata, event.ydata) if self.line_plots_active and not self._region_selection_on: self._line_plots.on_cursor_at(event.xdata, event.ydata) if event.button == 3: self.on_home_clicked() def deactivate_zoom_pan(self): self.deactivate_tool(ToolItemText.PAN) self.deactivate_tool(ToolItemText.ZOOM) def update_data_clim(self): self.image.set_clim(self.colorbar.colorbar.mappable.get_clim()) if self.line_plots_active: self._line_plots.plotter.update_line_plot_limits() self.canvas.draw_idle() def set_normalization(self, ws, **kwargs): normalize_by_bin_width, _ = get_normalize_by_bin_width(ws, self.ax, **kwargs) is_normalized = normalize_by_bin_width or ws.isDistribution() self.presenter.normalization = is_normalized if is_normalized: self.norm_opts.setCurrentIndex(1) else: self.norm_opts.setCurrentIndex(0) def get_default_scale_norm(self): scale = 'Linear' if self.conf is None: return scale if self.conf.has(SCALENORM): scale = self.conf.get(SCALENORM) if scale == 'Power' and self.conf.has(POWERSCALE): exponent = self.conf.get(POWERSCALE) scale = (scale, exponent) scale = "SymmetricLog10" if scale == 'Log' else scale return scale def scale_norm_changed(self): if self.conf is None: return scale = self.colorbar.norm.currentText() self.conf.set(SCALENORM, scale) if scale == 'Power': exponent = self.colorbar.powerscale_value self.conf.set(POWERSCALE, exponent)
class PythonFileInterpreter(QWidget): sig_editor_modified = Signal(bool) sig_filename_modified = Signal(str) sig_progress = Signal(int) sig_exec_error = Signal(object) sig_exec_success = Signal(object) def __init__(self, font=None, content=None, filename=None, parent=None): """ :param font: A reference to the font to be used by the editor. If not supplied use the system default :param content: An optional string of content to pass to the editor :param filename: The file path where the content was read. :param parent: An optional parent QWidget """ super(PythonFileInterpreter, self).__init__(parent) self.parent = parent # layout font = font if font is not None else QFont() self.editor = CodeEditor("AlternateCSPython", font, self) self.find_replace_dialog = None self.status = QStatusBar(self) self.layout = QVBoxLayout() self.layout.setContentsMargins(0, 0, 0, 0) self.layout.addWidget(self.editor) self.layout.addWidget(self.status) self.setLayout(self.layout) self._setup_editor(content, filename) self._presenter = PythonFileInterpreterPresenter(self, PythonCodeExecution(self.editor)) self.code_commenter = CodeCommenter(self.editor) self.code_completer = CodeCompleter(self.editor, self._presenter.model.globals_ns) self.editor.modificationChanged.connect(self.sig_editor_modified) self.editor.fileNameChanged.connect(self.sig_filename_modified) self.setAttribute(Qt.WA_DeleteOnClose, True) # Connect the model signals to the view's signals so they can be accessed from outside the MVP self._presenter.model.sig_exec_error.connect(self.sig_exec_error) self._presenter.model.sig_exec_success.connect(self.sig_exec_success) # Re-populate the completion API after execution success self._presenter.model.sig_exec_success.connect(self.code_completer.update_completion_api) def closeEvent(self, event): self.deleteLater() if self.find_replace_dialog: self.find_replace_dialog.close() super(PythonFileInterpreter, self).closeEvent(event) def show_find_replace_dialog(self): if self.find_replace_dialog is None: self.find_replace_dialog = EmbeddedFindReplaceDialog(self, self.editor) self.layout.insertWidget(0, self.find_replace_dialog.view) self.find_replace_dialog.show() def hide_find_replace_dialog(self): if self.find_replace_dialog is not None: self.find_replace_dialog.hide() @property def filename(self): return self.editor.fileName() def confirm_close(self): """Confirm the widget can be closed. If the editor contents are modified then a user can interject and cancel closing. :return: True if closing was considered successful, false otherwise """ return self.save(prompt_for_confirmation=self.parent.confirm_on_save) def abort(self): self._presenter.req_abort() def execute_async(self, ignore_selection=False): return self._presenter.req_execute_async(ignore_selection) def execute_async_blocking(self): self._presenter.req_execute_async_blocking() def save(self, prompt_for_confirmation=False, force_save=False): if self.editor.isModified(): io = EditorIO(self.editor) return io.save_if_required(prompt_for_confirmation, force_save) else: return True def save_as(self): io = EditorIO(self.editor) new_filename = io.ask_for_filename() if new_filename: return io.write(save_as=new_filename), new_filename else: return False, None def set_editor_readonly(self, ro): self.editor.setReadOnly(ro) def set_status_message(self, msg): self.status.showMessage(msg) def replace_tabs_with_spaces(self): self.replace_text(TAB_CHAR, SPACE_CHAR * TAB_WIDTH) def replace_text(self, match_text, replace_text): if self.editor.selectedText() == '': self.editor.selectAll() new_text = self.editor.selectedText().replace(match_text, replace_text) self.editor.replaceSelectedText(new_text) def replace_spaces_with_tabs(self): self.replace_text(SPACE_CHAR * TAB_WIDTH, TAB_CHAR) def set_whitespace_visible(self): self.editor.setEolVisibility(True) self.editor.setWhitespaceVisibility(CodeEditor.WsVisible) def set_whitespace_invisible(self): self.editor.setEolVisibility(False) self.editor.setWhitespaceVisibility(CodeEditor.WsInvisible) def toggle_comment(self): self.code_commenter.toggle_comment() def _setup_editor(self, default_content, filename): editor = self.editor # Clear default QsciScintilla key bindings that we want to allow # to be users of this class self.clear_key_binding("Ctrl+/") # use tabs not spaces for indentation editor.setIndentationsUseTabs(False) editor.setTabWidth(TAB_WIDTH) # show current editing line but in a softer color editor.setCaretLineBackgroundColor(CURRENTLINE_BKGD_COLOR) editor.setCaretLineVisible(True) # set a margin large enough for sensible file sizes < 1000 lines # and the progress marker font_metrics = QFontMetrics(self.font()) editor.setMarginWidth(1, font_metrics.averageCharWidth() * 3 + 20) # fill with content if supplied and set source filename if default_content is not None: editor.setText(default_content) if filename is not None: editor.setFileName(filename) # Default content does not count as a modification editor.setModified(False) def clear_key_binding(self, key_str): """Clear a keyboard shortcut bound to a Scintilla command""" self.editor.clearKeyBinding(key_str)
class PythonFileInterpreter(QWidget): sig_editor_modified = Signal(bool) sig_filename_modified = Signal(str) def __init__(self, content=None, filename=None, parent=None): """ :param content: An optional string of content to pass to the editor :param filename: The file path where the content was read. :param parent: An optional parent QWidget """ super(PythonFileInterpreter, self).__init__(parent) # layout self.editor = CodeEditor("AlternateCSPythonLexer", self) self.status = QStatusBar(self) layout = QVBoxLayout() layout.addWidget(self.editor) layout.addWidget(self.status) self.setLayout(layout) layout.setContentsMargins(0, 0, 0, 0) self._setup_editor(content, filename) self._presenter = PythonFileInterpreterPresenter( self, PythonCodeExecution(content)) self.editor.modificationChanged.connect(self.sig_editor_modified) self.editor.fileNameChanged.connect(self.sig_filename_modified) @property def filename(self): return self.editor.fileName() def confirm_close(self): """Confirm the widget can be closed. If the editor contents are modified then a user can interject and cancel closing. :return: True if closing was considered successful, false otherwise """ return self.save(confirm=True) def abort(self): self._presenter.req_abort() def execute_async(self): self._presenter.req_execute_async() def save(self, confirm=False): if self.editor.isModified(): io = EditorIO(self.editor) return io.save_if_required(confirm) else: return True def set_editor_readonly(self, ro): self.editor.setReadOnly(ro) def set_status_message(self, msg): self.status.showMessage(msg) def _setup_editor(self, default_content, filename): editor = self.editor # use tabs not spaces for indentation editor.setIndentationsUseTabs(False) editor.setTabWidth(TAB_WIDTH) # show current editing line but in a softer color editor.setCaretLineBackgroundColor(CURRENTLINE_BKGD_COLOR) editor.setCaretLineVisible(True) # set a margin large enough for sensible file sizes < 1000 lines # and the progress marker font_metrics = QFontMetrics(self.font()) editor.setMarginWidth(1, font_metrics.averageCharWidth() * 3 + 20) # fill with content if supplied and set source filename if default_content is not None: editor.setText(default_content) if filename is not None: editor.setFileName(filename) # Default content does not count as a modification editor.setModified(False) editor.enableAutoCompletion(CodeEditor.AcsAll)
def create_statusbar(): self.statusbar = QStatusBar(self) self.statusbar.showMessage("Ready", 0)
def __init__(self, viewer, *, show: bool = True): # Check there is a running app # instance() returns the singleton instance if it exists, or None app = QApplication.instance() # if None, raise a RuntimeError with the appropriate message if app is None: message = ( "napari requires a Qt event loop to run. To create one, " "try one of the following: \n" " - use the `napari.gui_qt()` context manager. See " "https://github.com/napari/napari/tree/master/examples for" " usage examples.\n" " - In IPython or a local Jupyter instance, use the " "`%gui qt` magic command.\n" " - Launch IPython with the option `--gui=qt`.\n" " - (recommended) in your IPython configuration file, add" " or uncomment the line `c.TerminalIPythonApp.gui = 'qt'`." " Then, restart IPython." ) raise RuntimeError(message) if perf_config: if perf_config.trace_qt_events: from .tracing.qt_event_tracing import convert_app_for_tracing # For tracing Qt events we need a special QApplication. If # using `gui_qt` we already have the special one, and no # conversion is done here. However when running inside # IPython or Jupyter this is where we switch out the # QApplication. app = convert_app_for_tracing(app) # Will patch based on config file. perf_config.patch_callables() _napari_app_id = getattr( viewer, "_napari_app_id", 'napari.napari.viewer.' + str(__version__), ) if ( platform.system() == "Windows" and not getattr(sys, 'frozen', False) and _napari_app_id ): import ctypes ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID( _napari_app_id ) logopath = os.path.join( os.path.dirname(__file__), '..', 'resources', 'logo.png' ) if getattr(viewer, "_napari_global_logo", True): app = QApplication.instance() app.setWindowIcon(QIcon(logopath)) # see docstring of `wait_for_workers_to_quit` for caveats on killing # workers at shutdown. app.aboutToQuit.connect(wait_for_workers_to_quit) # Connect the Viewer and create the Main Window self.qt_viewer = QtViewer(viewer) self._qt_window = QMainWindow() self._qt_window.setWindowIcon(QIcon(logopath)) self._qt_window.setAttribute(Qt.WA_DeleteOnClose) self._qt_window.setUnifiedTitleAndToolBarOnMac(True) # since we initialize canvas before window, we need to manually connect them again. if self._qt_window.windowHandle() is not None: self._qt_window.windowHandle().screenChanged.connect( self.qt_viewer.canvas._backend.screen_changed ) self._qt_center = QWidget(self._qt_window) self._qt_window.setCentralWidget(self._qt_center) self._qt_window.setWindowTitle(self.qt_viewer.viewer.title) self._qt_center.setLayout(QHBoxLayout()) self._status_bar = QStatusBar() self._qt_window.setStatusBar(self._status_bar) self._add_menubar() self._add_file_menu() self._add_view_menu() self._add_window_menu() if not os.getenv("DISABLE_ALL_PLUGINS"): self._add_plugins_menu() self._add_help_menu() self._status_bar.showMessage('Ready') self._help = QLabel('') self._status_bar.addPermanentWidget(self._help) self._qt_center.layout().addWidget(self.qt_viewer) self._qt_center.layout().setContentsMargins(4, 0, 4, 0) self._update_palette() self._add_viewer_dock_widget(self.qt_viewer.dockConsole) self._add_viewer_dock_widget(self.qt_viewer.dockLayerControls) self._add_viewer_dock_widget(self.qt_viewer.dockLayerList) self.qt_viewer.viewer.events.status.connect(self._status_changed) self.qt_viewer.viewer.events.help.connect(self._help_changed) self.qt_viewer.viewer.events.title.connect(self._title_changed) self.qt_viewer.viewer.events.palette.connect(self._update_palette) if perf.USE_PERFMON: # Add DebugMenu and dockPerformance if using perfmon. self._debug_menu = DebugMenu(self) self._add_viewer_dock_widget(self.qt_viewer.dockPerformance) else: self._debug_menu = None if show: self.show()
class Window: """Application window that contains the menu bar and viewer. Parameters ---------- viewer : napari.components.ViewerModel Contained viewer widget. Attributes ---------- file_menu : qtpy.QtWidgets.QMenu File menu. help_menu : qtpy.QtWidgets.QMenu Help menu. main_menu : qtpy.QtWidgets.QMainWindow.menuBar Main menubar. qt_viewer : QtViewer Contained viewer widget. view_menu : qtpy.QtWidgets.QMenu View menu. window_menu : qtpy.QtWidgets.QMenu Window menu. """ raw_stylesheet = get_stylesheet() def __init__(self, viewer, *, show: bool = True): # Check there is a running app # instance() returns the singleton instance if it exists, or None app = QApplication.instance() # if None, raise a RuntimeError with the appropriate message if app is None: message = ( "napari requires a Qt event loop to run. To create one, " "try one of the following: \n" " - use the `napari.gui_qt()` context manager. See " "https://github.com/napari/napari/tree/master/examples for" " usage examples.\n" " - In IPython or a local Jupyter instance, use the " "`%gui qt` magic command.\n" " - Launch IPython with the option `--gui=qt`.\n" " - (recommended) in your IPython configuration file, add" " or uncomment the line `c.TerminalIPythonApp.gui = 'qt'`." " Then, restart IPython." ) raise RuntimeError(message) if perf_config: if perf_config.trace_qt_events: from .tracing.qt_event_tracing import convert_app_for_tracing # For tracing Qt events we need a special QApplication. If # using `gui_qt` we already have the special one, and no # conversion is done here. However when running inside # IPython or Jupyter this is where we switch out the # QApplication. app = convert_app_for_tracing(app) # Will patch based on config file. perf_config.patch_callables() _napari_app_id = getattr( viewer, "_napari_app_id", 'napari.napari.viewer.' + str(__version__), ) if ( platform.system() == "Windows" and not getattr(sys, 'frozen', False) and _napari_app_id ): import ctypes ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID( _napari_app_id ) logopath = os.path.join( os.path.dirname(__file__), '..', 'resources', 'logo.png' ) if getattr(viewer, "_napari_global_logo", True): app = QApplication.instance() app.setWindowIcon(QIcon(logopath)) # see docstring of `wait_for_workers_to_quit` for caveats on killing # workers at shutdown. app.aboutToQuit.connect(wait_for_workers_to_quit) # Connect the Viewer and create the Main Window self.qt_viewer = QtViewer(viewer) self._qt_window = QMainWindow() self._qt_window.setWindowIcon(QIcon(logopath)) self._qt_window.setAttribute(Qt.WA_DeleteOnClose) self._qt_window.setUnifiedTitleAndToolBarOnMac(True) # since we initialize canvas before window, we need to manually connect them again. if self._qt_window.windowHandle() is not None: self._qt_window.windowHandle().screenChanged.connect( self.qt_viewer.canvas._backend.screen_changed ) self._qt_center = QWidget(self._qt_window) self._qt_window.setCentralWidget(self._qt_center) self._qt_window.setWindowTitle(self.qt_viewer.viewer.title) self._qt_center.setLayout(QHBoxLayout()) self._status_bar = QStatusBar() self._qt_window.setStatusBar(self._status_bar) self._add_menubar() self._add_file_menu() self._add_view_menu() self._add_window_menu() if not os.getenv("DISABLE_ALL_PLUGINS"): self._add_plugins_menu() self._add_help_menu() self._status_bar.showMessage('Ready') self._help = QLabel('') self._status_bar.addPermanentWidget(self._help) self._qt_center.layout().addWidget(self.qt_viewer) self._qt_center.layout().setContentsMargins(4, 0, 4, 0) self._update_palette() self._add_viewer_dock_widget(self.qt_viewer.dockConsole) self._add_viewer_dock_widget(self.qt_viewer.dockLayerControls) self._add_viewer_dock_widget(self.qt_viewer.dockLayerList) self.qt_viewer.viewer.events.status.connect(self._status_changed) self.qt_viewer.viewer.events.help.connect(self._help_changed) self.qt_viewer.viewer.events.title.connect(self._title_changed) self.qt_viewer.viewer.events.palette.connect(self._update_palette) if perf.USE_PERFMON: # Add DebugMenu and dockPerformance if using perfmon. self._debug_menu = DebugMenu(self) self._add_viewer_dock_widget(self.qt_viewer.dockPerformance) else: self._debug_menu = None if show: self.show() def _add_menubar(self): """Add menubar to napari app.""" self.main_menu = self._qt_window.menuBar() # Menubar shortcuts are only active when the menubar is visible. # Therefore, we set a global shortcut not associated with the menubar # to toggle visibility, *but*, in order to not shadow the menubar # shortcut, we disable it, and only enable it when the menubar is # hidden. See this stackoverflow link for details: # https://stackoverflow.com/questions/50537642/how-to-keep-the-shortcuts-of-a-hidden-widget-in-pyqt5 self._main_menu_shortcut = QShortcut( QKeySequence('Ctrl+M'), self._qt_window ) self._main_menu_shortcut.activated.connect( self._toggle_menubar_visible ) self._main_menu_shortcut.setEnabled(False) def _toggle_menubar_visible(self): """Toggle visibility of app menubar. This function also disables or enables a global keyboard shortcut to show the menubar, since menubar shortcuts are only available while the menubar is visible. """ if self.main_menu.isVisible(): self.main_menu.setVisible(False) self._main_menu_shortcut.setEnabled(True) else: self.main_menu.setVisible(True) self._main_menu_shortcut.setEnabled(False) def _add_file_menu(self): """Add 'File' menu to app menubar.""" open_images = QAction('Open File(s)...', self._qt_window) open_images.setShortcut('Ctrl+O') open_images.setStatusTip('Open file(s)') open_images.triggered.connect(self.qt_viewer._open_files_dialog) open_stack = QAction('Open Files as Stack...', self._qt_window) open_stack.setShortcut('Ctrl+Alt+O') open_stack.setStatusTip('Open files') open_stack.triggered.connect( self.qt_viewer._open_files_dialog_as_stack_dialog ) open_folder = QAction('Open Folder...', self._qt_window) open_folder.setShortcut('Ctrl+Shift+O') open_folder.setStatusTip('Open a folder') open_folder.triggered.connect(self.qt_viewer._open_folder_dialog) save_selected_layers = QAction( 'Save Selected Layer(s)...', self._qt_window ) save_selected_layers.setShortcut('Ctrl+S') save_selected_layers.setStatusTip('Save selected layers') save_selected_layers.triggered.connect( lambda: self.qt_viewer._save_layers_dialog(selected=True) ) save_all_layers = QAction('Save All Layers...', self._qt_window) save_all_layers.setShortcut('Ctrl+Shift+S') save_all_layers.setStatusTip('Save all layers') save_all_layers.triggered.connect( lambda: self.qt_viewer._save_layers_dialog(selected=False) ) screenshot = QAction('Save Screenshot...', self._qt_window) screenshot.setShortcut('Alt+S') screenshot.setStatusTip( 'Save screenshot of current display, default .png' ) screenshot.triggered.connect(self.qt_viewer._screenshot_dialog) screenshot_wv = QAction( 'Save Screenshot with Viewer...', self._qt_window ) screenshot_wv.setShortcut('Alt+Shift+S') screenshot_wv.setStatusTip( 'Save screenshot of current display with the viewer, default .png' ) screenshot_wv.triggered.connect(self._screenshot_dialog) # OS X will rename this to Quit and put it in the app menu. exitAction = QAction('Exit', self._qt_window) exitAction.setShortcut('Ctrl+Q') exitAction.setMenuRole(QAction.QuitRole) def handle_exit(): # if the event loop was started in gui_qt() then the app will be # named 'napari'. Since the Qapp was started by us, just close it. if QApplication.applicationName() == 'napari': QApplication.closeAllWindows() QApplication.quit() # otherwise, something else created the QApp before us (such as # %gui qt IPython magic). If we quit the app in this case, then # *later* attempts to instantiate a napari viewer won't work until # the event loop is restarted with app.exec_(). So rather than # quit just close all the windows (and clear our app icon). else: QApplication.setWindowIcon(QIcon()) self.close() if perf.USE_PERFMON: # Write trace file before exit, if we were writing one. # Is there a better place to make sure this is done on exit? perf.timers.stop_trace_file() _stop_monitor() exitAction.triggered.connect(handle_exit) self.file_menu = self.main_menu.addMenu('&File') self.file_menu.addAction(open_images) self.file_menu.addAction(open_stack) self.file_menu.addAction(open_folder) self.file_menu.addSeparator() self.file_menu.addAction(save_selected_layers) self.file_menu.addAction(save_all_layers) self.file_menu.addAction(screenshot) self.file_menu.addAction(screenshot_wv) self.file_menu.addSeparator() self.file_menu.addAction(exitAction) def _add_view_menu(self): """Add 'View' menu to app menubar.""" toggle_visible = QAction('Toggle Menubar Visibility', self._qt_window) toggle_visible.setShortcut('Ctrl+M') toggle_visible.setStatusTip('Hide Menubar') toggle_visible.triggered.connect(self._toggle_menubar_visible) toggle_theme = QAction('Toggle Theme', self._qt_window) toggle_theme.setShortcut('Ctrl+Shift+T') toggle_theme.setStatusTip('Toggle theme') toggle_theme.triggered.connect(self.qt_viewer.viewer._toggle_theme) toggle_fullscreen = QAction('Toggle Full Screen', self._qt_window) toggle_fullscreen.setShortcut('Ctrl+F') toggle_fullscreen.setStatusTip('Toggle full screen') toggle_fullscreen.triggered.connect(self._toggle_fullscreen) toggle_play = QAction('Toggle Play', self._qt_window) toggle_play.triggered.connect(self._toggle_play) toggle_play.setShortcut('Ctrl+Alt+P') toggle_play.setStatusTip('Toggle Play') self.view_menu = self.main_menu.addMenu('&View') self.view_menu.addAction(toggle_fullscreen) self.view_menu.addAction(toggle_visible) self.view_menu.addAction(toggle_theme) self.view_menu.addAction(toggle_play) self.view_menu.addSeparator() # Add octree actions. if config.async_octree: toggle_outline = QAction('Toggle Chunk Outlines', self._qt_window) toggle_outline.triggered.connect( self.qt_viewer._toggle_chunk_outlines ) toggle_outline.setShortcut('Ctrl+Alt+O') toggle_outline.setStatusTip('Toggle Chunk Outlines') self.view_menu.addAction(toggle_outline) # Add axes menu axes_menu = QMenu('Axes', parent=self._qt_window) axes_visible_action = QAction( 'Visible', parent=self._qt_window, checkable=True, checked=self.qt_viewer.viewer.axes.visible, ) axes_visible_action.triggered.connect(self._toggle_axes_visible) axes_colored_action = QAction( 'Colored', parent=self._qt_window, checkable=True, checked=self.qt_viewer.viewer.axes.colored, ) axes_colored_action.triggered.connect(self._toggle_axes_colored) axes_labels_action = QAction( 'Labels', parent=self._qt_window, checkable=True, checked=self.qt_viewer.viewer.axes.labels, ) axes_labels_action.triggered.connect(self._toggle_axes_labels) axes_dashed_action = QAction( 'Dashed', parent=self._qt_window, checkable=True, checked=self.qt_viewer.viewer.axes.dashed, ) axes_dashed_action.triggered.connect(self._toggle_axes_dashed) axes_arrows_action = QAction( 'Arrows', parent=self._qt_window, checkable=True, checked=self.qt_viewer.viewer.axes.arrows, ) axes_arrows_action.triggered.connect(self._toggle_axes_arrows) axes_menu.addAction(axes_visible_action) axes_menu.addAction(axes_colored_action) axes_menu.addAction(axes_labels_action) axes_menu.addAction(axes_dashed_action) axes_menu.addAction(axes_arrows_action) self.view_menu.addMenu(axes_menu) # Add scale bar menu scale_bar_menu = QMenu('Scale Bar', parent=self._qt_window) scale_bar_visible_action = QAction( 'Visible', parent=self._qt_window, checkable=True, checked=self.qt_viewer.viewer.scale_bar.visible, ) scale_bar_visible_action.triggered.connect( self._toggle_scale_bar_visible ) scale_bar_colored_action = QAction( 'Colored', parent=self._qt_window, checkable=True, checked=self.qt_viewer.viewer.scale_bar.colored, ) scale_bar_colored_action.triggered.connect( self._toggle_scale_bar_colored ) scale_bar_ticks_action = QAction( 'Ticks', parent=self._qt_window, checkable=True, checked=self.qt_viewer.viewer.scale_bar.ticks, ) scale_bar_ticks_action.triggered.connect(self._toggle_scale_bar_ticks) scale_bar_menu.addAction(scale_bar_visible_action) scale_bar_menu.addAction(scale_bar_colored_action) scale_bar_menu.addAction(scale_bar_ticks_action) self.view_menu.addMenu(scale_bar_menu) self.view_menu.addSeparator() def _add_window_menu(self): """Add 'Window' menu to app menubar.""" exit_action = QAction("Close Window", self._qt_window) exit_action.setShortcut("Ctrl+W") exit_action.setStatusTip('Close napari window') exit_action.triggered.connect(self._qt_window.close) self.window_menu = self.main_menu.addMenu('&Window') self.window_menu.addAction(exit_action) def _add_plugins_menu(self): """Add 'Plugins' menu to app menubar.""" self.plugins_menu = self.main_menu.addMenu('&Plugins') pip_install_action = QAction( "Install/Uninstall Package(s)...", self._qt_window ) pip_install_action.triggered.connect(self._show_plugin_install_dialog) self.plugins_menu.addAction(pip_install_action) order_plugin_action = QAction("Plugin Call Order...", self._qt_window) order_plugin_action.setStatusTip('Change call order for plugins') order_plugin_action.triggered.connect(self._show_plugin_sorter) self.plugins_menu.addAction(order_plugin_action) report_plugin_action = QAction("Plugin Errors...", self._qt_window) report_plugin_action.setStatusTip( 'Review stack traces for plugin exceptions and notify developers' ) report_plugin_action.triggered.connect(self._show_plugin_err_reporter) self.plugins_menu.addAction(report_plugin_action) def _show_plugin_sorter(self): """Show dialog that allows users to sort the call order of plugins.""" plugin_sorter = QtPluginSorter(parent=self._qt_window) if hasattr(self, 'plugin_sorter_widget'): self.plugin_sorter_widget.show() else: self.plugin_sorter_widget = self.add_dock_widget( plugin_sorter, name='Plugin Sorter', area="right" ) def _show_plugin_install_dialog(self): """Show dialog that allows users to sort the call order of plugins.""" self.plugin_dialog = QtPluginDialog(self._qt_window) self.plugin_dialog.exec_() def _show_plugin_err_reporter(self): """Show dialog that allows users to review and report plugin errors.""" QtPluginErrReporter(parent=self._qt_window).exec_() def _add_help_menu(self): """Add 'Help' menu to app menubar.""" self.help_menu = self.main_menu.addMenu('&Help') about_action = QAction("napari Info", self._qt_window) about_action.setShortcut("Ctrl+/") about_action.setStatusTip('About napari') about_action.triggered.connect( lambda e: QtAbout.showAbout(self.qt_viewer) ) self.help_menu.addAction(about_action) about_key_bindings = QAction("Show Key Bindings", self._qt_window) about_key_bindings.setShortcut("Ctrl+Alt+/") about_key_bindings.setShortcutContext(Qt.ApplicationShortcut) about_key_bindings.setStatusTip('key_bindings') about_key_bindings.triggered.connect( self.qt_viewer.show_key_bindings_dialog ) self.help_menu.addAction(about_key_bindings) def _toggle_scale_bar_visible(self, state): self.qt_viewer.viewer.scale_bar.visible = state def _toggle_scale_bar_colored(self, state): self.qt_viewer.viewer.scale_bar.colored = state def _toggle_scale_bar_ticks(self, state): self.qt_viewer.viewer.scale_bar.ticks = state def _toggle_axes_visible(self, state): self.qt_viewer.viewer.axes.visible = state def _toggle_axes_colored(self, state): self.qt_viewer.viewer.axes.colored = state def _toggle_axes_labels(self, state): self.qt_viewer.viewer.axes.labels = state def _toggle_axes_dashed(self, state): self.qt_viewer.viewer.axes.dashed = state def _toggle_axes_arrows(self, state): self.qt_viewer.viewer.axes.arrows = state def _toggle_fullscreen(self, event): """Toggle fullscreen mode.""" if self._qt_window.isFullScreen(): self._qt_window.showNormal() else: self._qt_window.showFullScreen() def _toggle_play(self, state): """Toggle play.""" if self.qt_viewer.dims.is_playing: self.qt_viewer.dims.stop() else: axis = self.qt_viewer.viewer.dims.last_used or 0 self.qt_viewer.dims.play(axis) def add_dock_widget( self, widget: QWidget, *, name: str = '', area: str = 'bottom', allowed_areas=None, shortcut=None, ): """Convenience method to add a QDockWidget to the main window Parameters ---------- widget : QWidget `widget` will be added as QDockWidget's main widget. name : str, optional Name of dock widget to appear in window menu. area : str Side of the main window to which the new dock widget will be added. Must be in {'left', 'right', 'top', 'bottom'} allowed_areas : list[str], optional Areas, relative to main window, that the widget is allowed dock. Each item in list must be in {'left', 'right', 'top', 'bottom'} By default, all areas are allowed. shortcut : str, optional Keyboard shortcut to appear in dropdown menu. Returns ------- dock_widget : QtViewerDockWidget `dock_widget` that can pass viewer events. """ dock_widget = QtViewerDockWidget( self.qt_viewer, widget, name=name, area=area, allowed_areas=allowed_areas, shortcut=shortcut, ) self._add_viewer_dock_widget(dock_widget) return dock_widget def _add_viewer_dock_widget(self, dock_widget: QtViewerDockWidget): """Add a QtViewerDockWidget to the main window Parameters ---------- dock_widget : QtViewerDockWidget `dock_widget` will be added to the main window. """ dock_widget.setParent(self._qt_window) self._qt_window.addDockWidget(dock_widget.qt_area, dock_widget) action = dock_widget.toggleViewAction() action.setStatusTip(dock_widget.name) action.setText(dock_widget.name) if dock_widget.shortcut is not None: action.setShortcut(dock_widget.shortcut) self.window_menu.addAction(action) def remove_dock_widget(self, widget): """Removes specified dock widget. Parameters ---------- widget : QWidget | str If widget == 'all', all docked widgets will be removed. """ if widget == 'all': for dw in self._qt_window.findChildren(QDockWidget): self._qt_window.removeDockWidget(dw) else: self._qt_window.removeDockWidget(widget) def resize(self, width, height): """Resize the window. Parameters ---------- width : int Width in logical pixels. height : int Height in logical pixels. """ self._qt_window.resize(width, height) def show(self): """Resize, show, and bring forward the window.""" self._qt_window.resize(self._qt_window.layout().sizeHint()) self._qt_window.show() # Resize axis labels now that window is shown self.qt_viewer.dims._resize_axis_labels() # We want to bring the viewer to the front when # A) it is our own (gui_qt) event loop OR we are running in jupyter # B) it is not the first time a QMainWindow is being created # `app_name` will be "napari" iff the application was instantiated in # gui_qt(). isActiveWindow() will be True if it is the second time a # _qt_window has been created. # See #721, #732, #735, #795, #1594 app_name = QApplication.instance().applicationName() if ( app_name == 'napari' or in_jupyter() ) and self._qt_window.isActiveWindow(): self.activate() def activate(self): """Make the viewer the currently active window.""" self._qt_window.raise_() # for macOS self._qt_window.activateWindow() # for Windows def _update_palette(self, event=None): """Update widget color palette.""" # set window styles which don't use the primary stylesheet # FIXME: this is a problem with the stylesheet not using properties palette = self.qt_viewer.viewer.palette self._status_bar.setStyleSheet( template( 'QStatusBar { background: {{ background }}; ' 'color: {{ text }}; }', **palette, ) ) self._qt_center.setStyleSheet( template('QWidget { background: {{ background }}; }', **palette) ) self._qt_window.setStyleSheet(template(self.raw_stylesheet, **palette)) def _status_changed(self, event): """Update status bar. Parameters ---------- event : napari.utils.event.Event The napari event that triggered this method. """ self._status_bar.showMessage(event.text) def _title_changed(self, event): """Update window title. Parameters ---------- event : napari.utils.event.Event The napari event that triggered this method. """ self._qt_window.setWindowTitle(event.text) def _help_changed(self, event): """Update help message on status bar. Parameters ---------- event : napari.utils.event.Event The napari event that triggered this method. """ self._help.setText(event.text) def _screenshot_dialog(self): """Save screenshot of current display with viewer, default .png""" dial = ScreenshotDialog( self.screenshot, self.qt_viewer, self.qt_viewer._last_visited_dir ) if dial.exec_(): self._last_visited_dir = os.path.dirname(dial.selectedFiles()[0]) def screenshot(self, path=None): """Take currently displayed viewer and convert to an image array. Parameters ---------- path : str Filename for saving screenshot image. Returns ------- image : array Numpy array of type ubyte and shape (h, w, 4). Index [0, 0] is the upper-left corner of the rendered region. """ img = self._qt_window.grab().toImage() if path is not None: imsave(path, QImg2array(img)) # scikit-image imsave method return QImg2array(img) def close(self): """Close the viewer window and cleanup sub-widgets.""" # Someone is closing us twice? Only try to delete self._qt_window # if we still have one. if hasattr(self, '_qt_window'): self._delete_qt_window() def _delete_qt_window(self): """Delete our self._qt_window.""" # On some versions of Darwin, exiting while fullscreen seems to tickle # some bug deep in NSWindow. This forces the fullscreen keybinding # test to complete its draw cycle, then pop back out of fullscreen. if self._qt_window.isFullScreen(): self._qt_window.showNormal() for i in range(8): time.sleep(0.1) QApplication.processEvents() self.qt_viewer.close() self._qt_window.close() del self._qt_window
def main(): """Execute QDarkStyle example.""" parser = argparse.ArgumentParser( description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) parser.add_argument( '--qt_from', default='qtpy', type=str, choices=[ 'pyqt5', 'pyqt', 'pyside2', 'pyside', 'qtpy', 'pyqtgraph', 'qt.py' ], help= "Choose which binding and/or abstraction is to be used to run the example." ) parser.add_argument( '--no_dark', action='store_true', help="Exihibts the original window (without jam_darkstyle).") parser.add_argument('--test', action='store_true', help="Auto close window after 2s.") parser.add_argument('--reset', action='store_true', help="Reset GUI settings (position, size) then opens.") parser.add_argument('--screenshots', action='store_true', help="Generate screenshots on images folder.") # Parsing arguments from command line args = parser.parse_args() # To avoid problems when testing without screen if args.test or args.screenshots: os.environ['QT_QPA_PLATFORM'] = 'offscreen' # Set QT_API variable before importing QtPy if args.qt_from in ['pyqt', 'pyqt5', 'pyside', 'pyside2']: os.environ['QT_API'] = args.qt_from elif args.qt_from == 'pyqtgraph': os.environ['QT_API'] = os.environ['PYQTGRAPH_QT_LIB'] elif args.qt_from in ['qt.py', 'qt']: try: import Qt except ImportError: print('Could not import Qt (Qt.Py)') else: os.environ['QT_API'] = Qt.__binding__ # QtPy imports from qtpy import API_NAME, QT_VERSION, PYQT_VERSION, PYSIDE_VERSION from qtpy import __version__ as QTPY_VERSION from qtpy.QtWidgets import (QApplication, QMainWindow, QDockWidget, QStatusBar, QLabel, QPushButton, QMenu) from qtpy.QtCore import QTimer, Qt, QSettings # Set API_VERSION variable API_VERSION = '' if PYQT_VERSION: API_VERSION = PYQT_VERSION elif PYSIDE_VERSION: API_VERSION = PYSIDE_VERSION else: API_VERSION = 'Not found' # Import examples UI from mw_menus_ui import Ui_MainWindow as ui_main from dw_buttons_ui import Ui_DockWidget as ui_buttons from dw_displays_ui import Ui_DockWidget as ui_displays from dw_inputs_fields_ui import Ui_DockWidget as ui_inputs_fields from dw_inputs_no_fields_ui import Ui_DockWidget as ui_inputs_no_fields from dw_widgets_ui import Ui_DockWidget as ui_widgets from dw_views_ui import Ui_DockWidget as ui_views from dw_containers_tabs_ui import Ui_DockWidget as ui_containers_tabs from dw_containers_no_tabs_ui import Ui_DockWidget as ui_containers_no_tabs # create the application app = QApplication(sys.argv) app.setOrganizationName('QDarkStyle') app.setApplicationName('QDarkStyle Example') style = '' if not args.no_dark: style = jam_darkstyle.load_stylesheet() app.setStyleSheet(style) # create main window window = QMainWindow() window.setObjectName('mainwindow') ui = ui_main() ui.setupUi(window) title = ("QDarkStyle Example - " + "(QDarkStyle=v" + jam_darkstyle.__version__ + ", QtPy=v" + QTPY_VERSION + ", " + API_NAME + "=v" + API_VERSION + ", Qt=v" + QT_VERSION + ", Python=v" + platform.python_version() + ")") _logger.info(title) window.setWindowTitle(title) # Create docks for buttons dw_buttons = QDockWidget() dw_buttons.setObjectName('buttons') ui_buttons = ui_buttons() ui_buttons.setupUi(dw_buttons) window.addDockWidget(Qt.RightDockWidgetArea, dw_buttons) # Add actions on popup toolbuttons menu = QMenu() for action in ['Action A', 'Action B', 'Action C']: menu.addAction(action) ui_buttons.toolButtonDelayedPopup.setMenu(menu) ui_buttons.toolButtonInstantPopup.setMenu(menu) ui_buttons.toolButtonMenuButtonPopup.setMenu(menu) # Create docks for buttons dw_displays = QDockWidget() dw_displays.setObjectName('displays') ui_displays = ui_displays() ui_displays.setupUi(dw_displays) window.addDockWidget(Qt.RightDockWidgetArea, dw_displays) # Create docks for inputs - no fields dw_inputs_no_fields = QDockWidget() dw_inputs_no_fields.setObjectName('inputs_no_fields') ui_inputs_no_fields = ui_inputs_no_fields() ui_inputs_no_fields.setupUi(dw_inputs_no_fields) window.addDockWidget(Qt.RightDockWidgetArea, dw_inputs_no_fields) # Create docks for inputs - fields dw_inputs_fields = QDockWidget() dw_inputs_fields.setObjectName('inputs_fields') ui_inputs_fields = ui_inputs_fields() ui_inputs_fields.setupUi(dw_inputs_fields) window.addDockWidget(Qt.RightDockWidgetArea, dw_inputs_fields) # Create docks for widgets dw_widgets = QDockWidget() dw_widgets.setObjectName('widgets') ui_widgets = ui_widgets() ui_widgets.setupUi(dw_widgets) window.addDockWidget(Qt.LeftDockWidgetArea, dw_widgets) # Create docks for views dw_views = QDockWidget() dw_views.setObjectName('views') ui_views = ui_views() ui_views.setupUi(dw_views) window.addDockWidget(Qt.LeftDockWidgetArea, dw_views) # Create docks for containers - no tabs dw_containers_no_tabs = QDockWidget() dw_containers_no_tabs.setObjectName('containers_no_tabs') ui_containers_no_tabs = ui_containers_no_tabs() ui_containers_no_tabs.setupUi(dw_containers_no_tabs) window.addDockWidget(Qt.LeftDockWidgetArea, dw_containers_no_tabs) # Create docks for containters - tabs dw_containers_tabs = QDockWidget() dw_containers_tabs.setObjectName('containers_tabs') ui_containers_tabs = ui_containers_tabs() ui_containers_tabs.setupUi(dw_containers_tabs) window.addDockWidget(Qt.LeftDockWidgetArea, dw_containers_tabs) # Tabify right docks window.tabifyDockWidget(dw_buttons, dw_displays) window.tabifyDockWidget(dw_displays, dw_inputs_fields) window.tabifyDockWidget(dw_inputs_fields, dw_inputs_no_fields) # Tabify left docks window.tabifyDockWidget(dw_containers_no_tabs, dw_containers_tabs) window.tabifyDockWidget(dw_containers_tabs, dw_widgets) window.tabifyDockWidget(dw_widgets, dw_views) # Issues #9120, #9121 on Spyder qstatusbar = QStatusBar() qstatusbar.addWidget( QLabel('Issue Spyder #9120, #9121 - background not matching.')) qstatusbar.addWidget(QPushButton('OK')) # Add info also in status bar for screenshots get it qstatusbar.addWidget(QLabel('INFO: ' + title)) window.setStatusBar(qstatusbar) # Todo: add report info and other info in HELP graphical # Auto quit after 2s when in test mode if args.test: QTimer.singleShot(2000, app.exit) # Save screenshots for different displays and quit if args.screenshots: window.showFullScreen() create_screenshots(app, window, args.no_dark) # Do not read settings when taking screenshots - like reset else: _read_settings(window, args.reset, QSettings) window.showMaximized() app.exec_() _write_settings(window, QSettings) return window
class Window: """Application window that contains the menu bar and viewer. Parameters ---------- qt_viewer : QtViewer Contained viewer widget. Attributes ---------- file_menu : qtpy.QtWidgets.QMenu File menu. help_menu : qtpy.QtWidgets.QMenu Help menu. main_menu : qtpy.QtWidgets.QMainWindow.menuBar Main menubar. qt_viewer : QtViewer Contained viewer widget. view_menu : qtpy.QtWidgets.QMenu View menu. window_menu : qtpy.QtWidgets.QMenu Window menu. """ raw_stylesheet = combine_stylesheets() def __init__(self, qt_viewer, *, show=True): self.qt_viewer = qt_viewer self._qt_window = QMainWindow() self._qt_window.setAttribute(Qt.WA_DeleteOnClose) self._qt_window.setUnifiedTitleAndToolBarOnMac(True) self._qt_center = QWidget(self._qt_window) self._qt_window.setCentralWidget(self._qt_center) self._qt_window.setWindowTitle(self.qt_viewer.viewer.title) self._qt_center.setLayout(QHBoxLayout()) self._status_bar = QStatusBar() self._qt_window.setStatusBar(self._status_bar) self._add_menubar() self._add_file_menu() self._add_view_menu() self._add_window_menu() self._add_help_menu() self._status_bar.showMessage('Ready') self._help = QLabel('') self._status_bar.addPermanentWidget(self._help) self._qt_center.layout().addWidget(self.qt_viewer) self._qt_center.layout().setContentsMargins(4, 0, 4, 0) self._update_palette(qt_viewer.viewer.palette) if self.qt_viewer.console.shell is not None: self._add_viewer_dock_widget(self.qt_viewer.dockConsole) self._add_viewer_dock_widget(self.qt_viewer.dockLayerControls) self._add_viewer_dock_widget(self.qt_viewer.dockLayerList) self.qt_viewer.viewer.events.status.connect(self._status_changed) self.qt_viewer.viewer.events.help.connect(self._help_changed) self.qt_viewer.viewer.events.title.connect(self._title_changed) self.qt_viewer.viewer.events.palette.connect( lambda event: self._update_palette(event.palette)) if show: self.show() def _add_menubar(self): """Add menubar to napari app.""" self.main_menu = self._qt_window.menuBar() # Menubar shortcuts are only active when the menubar is visible. # Therefore, we set a global shortcut not associated with the menubar # to toggle visibility, *but*, in order to not shadow the menubar # shortcut, we disable it, and only enable it when the menubar is # hidden. See this stackoverflow link for details: # https://stackoverflow.com/questions/50537642/how-to-keep-the-shortcuts-of-a-hidden-widget-in-pyqt5 self._main_menu_shortcut = QShortcut(QKeySequence('Ctrl+M'), self._qt_window) self._main_menu_shortcut.activated.connect( self._toggle_menubar_visible) self._main_menu_shortcut.setEnabled(False) def _toggle_menubar_visible(self): """Toggle visibility of app menubar. This function also disables or enables a global keyboard shortcut to show the menubar, since menubar shortcuts are only available while the menubar is visible. """ if self.main_menu.isVisible(): self.main_menu.setVisible(False) self._main_menu_shortcut.setEnabled(True) else: self.main_menu.setVisible(True) self._main_menu_shortcut.setEnabled(False) def _add_file_menu(self): """Add 'File' menu to app menubar.""" open_images = QAction('Open image(s)...', self._qt_window) open_images.setShortcut('Ctrl+O') open_images.setStatusTip('Open image file(s)') open_images.triggered.connect(self.qt_viewer._open_images) open_folder = QAction('Open Folder...', self._qt_window) open_folder.setShortcut('Ctrl+Shift+O') open_folder.setStatusTip( 'Open a folder of image file(s) or a zarr file') open_folder.triggered.connect(self.qt_viewer._open_folder) screenshot = QAction('Screenshot', self._qt_window) screenshot.setShortcut('Ctrl+Alt+S') screenshot.setStatusTip( 'Save screenshot of current display, default .png') screenshot.triggered.connect(self.qt_viewer._save_screenshot) self.file_menu = self.main_menu.addMenu('&File') self.file_menu.addAction(open_images) self.file_menu.addAction(open_folder) self.file_menu.addAction(screenshot) def _add_view_menu(self): """Add 'View' menu to app menubar.""" toggle_visible = QAction('Toggle menubar visibility', self._qt_window) toggle_visible.setShortcut('Ctrl+M') toggle_visible.setStatusTip('Hide Menubar') toggle_visible.triggered.connect(self._toggle_menubar_visible) toggle_theme = QAction('Toggle theme', self._qt_window) toggle_theme.setShortcut('Ctrl+Shift+T') toggle_theme.setStatusTip('Toggle theme') toggle_theme.triggered.connect(self.qt_viewer.viewer._toggle_theme) self.view_menu = self.main_menu.addMenu('&View') self.view_menu.addAction(toggle_visible) self.view_menu.addAction(toggle_theme) def _add_window_menu(self): """Add 'Window' menu to app menubar.""" exit_action = QAction("Close window", self._qt_window) exit_action.setShortcut("Ctrl+W") exit_action.setStatusTip('Close napari window') exit_action.triggered.connect(self._qt_window.close) self.window_menu = self.main_menu.addMenu('&Window') self.window_menu.addAction(exit_action) def _add_help_menu(self): """Add 'Help' menu to app menubar.""" self.help_menu = self.main_menu.addMenu('&Help') about_action = QAction("napari info", self._qt_window) about_action.setShortcut("Ctrl+/") about_action.setStatusTip('About napari') about_action.triggered.connect( lambda e: QtAbout.showAbout(self.qt_viewer)) self.help_menu.addAction(about_action) about_keybindings = QAction("keybindings", self._qt_window) about_keybindings.setShortcut("Ctrl+Alt+/") about_keybindings.setShortcutContext(Qt.ApplicationShortcut) about_keybindings.setStatusTip('keybindings') about_keybindings.triggered.connect( self.qt_viewer.show_keybindings_dialog) self.help_menu.addAction(about_keybindings) def add_dock_widget( self, widget: QWidget, *, name: str = '', area: str = 'bottom', allowed_areas=None, shortcut=None, ): """Convenience method to add a QDockWidget to the main window Parameters ---------- widget : QWidget `widget` will be added as QDockWidget's main widget. name : str, optional Name of dock widget to appear in window menu. area : str Side of the main window to which the new dock widget will be added. Must be in {'left', 'right', 'top', 'bottom'} allowed_areas : list[str], optional Areas, relative to main window, that the widget is allowed dock. Each item in list must be in {'left', 'right', 'top', 'bottom'} By default, all areas are allowed. shortcut : str, optional Keyboard shortcut to appear in dropdown menu. Returns ------- dock_widget : QtViewerDockWidget `dock_widget` that can pass viewer events. """ dock_widget = QtViewerDockWidget( self.qt_viewer, widget, name=name, area=area, allowed_areas=allowed_areas, shortcut=shortcut, ) self._add_viewer_dock_widget(dock_widget) return dock_widget def _add_viewer_dock_widget(self, dock_widget: QtViewerDockWidget): """Add a QtViewerDockWidget to the main window Parameters ---------- dock_widget : QtViewerDockWidget `dock_widget` will be added to the main window. """ dock_widget.setParent(self._qt_window) self._qt_window.addDockWidget(dock_widget.qt_area, dock_widget) action = dock_widget.toggleViewAction() action.setStatusTip(dock_widget.name) action.setText(dock_widget.name) if dock_widget.shortcut is not None: action.setShortcut(dock_widget.shortcut) self.window_menu.addAction(action) def remove_dock_widget(self, widget): """Removes specified dock widget. Parameters ---------- widget : QWidget | str If widget == 'all', all docked widgets will be removed. """ if widget == 'all': for dw in self._qt_window.findChildren(QDockWidget): self._qt_window.removeDockWidget(dw) else: self._qt_window.removeDockWidget(widget) def resize(self, width, height): """Resize the window. Parameters ---------- width : int Width in logical pixels. height : int Height in logical pixels. """ self._qt_window.resize(width, height) def show(self): """Resize, show, and bring forward the window.""" self._qt_window.resize(self._qt_window.layout().sizeHint()) self._qt_window.show() # We want to call Window._qt_window.raise_() in every case *except* # when instantiating a viewer within a gui_qt() context for the # _first_ time within the Qt app's lifecycle. # # `app_name` will be "napari" iff the application was instantiated in # gui_qt(). isActiveWindow() will be True if it is the second time a # _qt_window has been created. See #732 app_name = QApplication.instance().applicationName() if app_name != 'napari' or self._qt_window.isActiveWindow(): self._qt_window.raise_() # for macOS self._qt_window.activateWindow() # for Windows def _update_palette(self, palette): """Update widget color palette. Parameters ---------- palette : qtpy.QtGui.QPalette Color palette for each widget state (Active, Disabled, Inactive). """ # set window styles which don't use the primary stylesheet # FIXME: this is a problem with the stylesheet not using properties self._status_bar.setStyleSheet( template( 'QStatusBar { background: {{ background }}; ' 'color: {{ text }}; }', **palette, )) self._qt_center.setStyleSheet( template('QWidget { background: {{ background }}; }', **palette)) self._qt_window.setStyleSheet(template(self.raw_stylesheet, **palette)) def _status_changed(self, event): """Update status bar. Parameters ---------- event : qtpy.QtCore.QEvent Event from the Qt context. """ self._status_bar.showMessage(event.text) def _title_changed(self, event): """Update window title. Parameters ---------- event : qtpy.QtCore.QEvent Event from the Qt context. """ self._qt_window.setWindowTitle(event.text) def _help_changed(self, event): """Update help message on status bar. Parameters ---------- event : qtpy.QtCore.QEvent Event from the Qt context. """ self._help.setText(event.text) def screenshot(self, path=None): """Take currently displayed viewer and convert to an image array. Parameters ---------- path : str Filename for saving screenshot image. Returns ------- image : array Numpy array of type ubyte and shape (h, w, 4). Index [0, 0] is the upper-left corner of the rendered region. """ img = self._qt_window.grab().toImage() if path is not None: imsave(path, QImg2array(img)) # scikit-image imsave method return QImg2array(img) def close(self): """Close the viewer window and cleanup sub-widgets.""" # on some versions of Darwin, exiting while fullscreen seems to tickle # some bug deep in NSWindow. This forces the fullscreen keybinding # test to complete its draw cycle, then pop back out of fullscreen. if self._qt_window.isFullScreen(): self._qt_window.showNormal() for i in range(8): time.sleep(0.1) QApplication.processEvents() self.qt_viewer.close() self._qt_window.close() del self._qt_window
class MainWindow(QMainWindow): """OpenBurn's main window""" title = "OpenBurn" def __init__(self): super(MainWindow, self).__init__() self.setWindowTitle(self.title) self.setGeometry(100, 100, 800, 600) self.setWindowIcon(QIcon(RESOURCE_PATH + "icons/nakka-finocyl.gif")) self.create_default_widgets() self.setup_ui() def create_default_widgets(self): """Creates static widgets such as menubar and statusbar""" def create_menubar(): """Create menu bar and populate it with sub menu actions""" def file_menu(): """Create a file submenu""" self.file_sub_menu = self.menubar.addMenu('File') self.open_action = QAction('Open File', self) self.open_action.setStatusTip('Open a new design') self.open_action.setShortcut('CTRL+O') # self.open_action.triggered.connect(self.open_file) self.exit_action = QAction('Exit', self) self.exit_action.setStatusTip('Exit the application.') self.exit_action.setShortcut('CTRL+Q') self.exit_action.triggered.connect(QApplication.quit) self.file_sub_menu.addAction(self.open_action) self.file_sub_menu.addAction(self.exit_action) def edit_menu(): self.edit_dub_menu = self.menubar.addMenu('Edit') def tools_menu(): self.edit_dub_menu = self.menubar.addMenu('Tools') def help_menu(): """Create help submenu""" self.help_sub_menu = self.menubar.addMenu('Help') self.about_action = QAction('About', self) self.about_action.setStatusTip('About the application.') self.about_action.setShortcut('CTRL+H') self.about_action.triggered.connect(self.about_dialog.exec_) self.help_sub_menu.addAction(self.about_action) self.menubar = QMenuBar(self) file_menu() edit_menu() tools_menu() help_menu() def create_statusbar(): self.statusbar = QStatusBar(self) self.statusbar.showMessage("Ready", 0) self.about_dialog = AboutDialog(self) create_menubar() self.setMenuBar(self.menubar) create_statusbar() self.setStatusBar(self.statusbar) def setup_ui(self): """setup the tab widget UI""" self.tab_widget = QTabWidget() self.tab_widget.addTab(DesignTab(), "Design") self.tab_widget.addTab(QWidget(), "Simulation") self.tab_widget.addTab(QWidget(), "Propellants") self.layout = QVBoxLayout() self.layout.addWidget(self.tab_widget) self.frame = QFrame() self.frame.setLayout(self.layout) self.setCentralWidget(self.frame)