Beispiel #1
0
    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)
Beispiel #2
0
    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
Beispiel #3
0
    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)
Beispiel #4
0
    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()
Beispiel #5
0
    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)
Beispiel #7
0
    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()
Beispiel #8
0
    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)
Beispiel #9
0
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)
Beispiel #10
0
    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()
Beispiel #11
0
    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()
Beispiel #12
0
    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)
Beispiel #13
0
    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)
Beispiel #14
0
 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
Beispiel #15
0
    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)
Beispiel #16
0
 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)
Beispiel #17
0
    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)
Beispiel #18
0
    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()
Beispiel #19
0
    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)
Beispiel #20
0
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
Beispiel #21
0
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)
Beispiel #22
0
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()
Beispiel #23
0
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)
Beispiel #24
0
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
Beispiel #25
0
    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)
Beispiel #26
0
    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)
Beispiel #27
0
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)
Beispiel #28
0
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)
Beispiel #29
0
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)
Beispiel #30
0
 def create_statusbar():
     self.statusbar = QStatusBar(self)
     self.statusbar.showMessage("Ready", 0)
Beispiel #31
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()
Beispiel #32
0
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
Beispiel #33
0
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
Beispiel #34
0
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
Beispiel #35
0
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)