Beispiel #1
0
    def __init__(self, parent=None):
        super(MainWindowBase, self).__init__(parent)

        # Do the import here to update the splash

        import hyperspyui.mdi_mpl_backend
        from hyperspyui.settings import Settings

        # Setup settings:
        self.settings = Settings(self, 'General')
        # Default setting values:
        self.settings.set_default('toolbar_button_size', 24)
        self.settings.set_default('default_widget_floating', False)
        self.settings.set_default('working_directory', "")
        self.settings.set_default('low_process_priority', False)
        # Override any possible invalid stored values, which could prevent load
        if 'toolbar_button_size' not in self.settings or \
                not isinstance(self.settings['toolbar_button_size'], int):
            self.settings['toolbar_button_size'] = 24
        if self.low_process_priority:
            lowpriority()

        # State varaibles
        self.should_capture_traits = None
        self.active_tool = None

        # Collections
        self.widgets = []   # Widgets in widget bar
        self.figures = []   # Matplotlib figures
        self.editors = []   # EditorWidgets
        self.traits_dialogs = []
        self.actions = {}
        self._action_selection_cbs = {}
        self.toolbars = {}
        self.menus = {}
        self.tools = []
        self.plugin_manager = None

        # MPL backend bindings
        hyperspyui.mdi_mpl_backend.connect_on_new_figure(self.on_new_figure)
        hyperspyui.mdi_mpl_backend.connect_on_destroy(self.on_destroy_figure)

        # Create UI
        self.windowmenu = None
        self.create_ui()

        # Connect figure management functions
        self.main_frame.subWindowActivated.connect(self.on_subwin_activated)

        # Save standard layout/state
        self.settings.set_default('_geometry', self.saveGeometry())
        self.settings.set_default('_windowState', self.saveState())

        # Restore layout/state if saved
        geometry = self.settings['_geometry']
        state = self.settings['_windowState']
        if geometry:
            self.restoreGeometry(geometry)
        if state:
            self.restoreState(state)
Beispiel #2
0
def main():
    from python_qt_binding import QtGui, QtCore, QT_BINDING
    import hyperspyui.info
    from hyperspyui.singleapplication import get_app
    from hyperspyui.settings import Settings
    from hyperspyui.util import dummy_context_manager

    # Need to set early to make QSettings accessible
    QtCore.QCoreApplication.setApplicationName("HyperSpyUI")
    QtCore.QCoreApplication.setOrganizationName("Hyperspy")
    QtCore.QCoreApplication.setApplicationVersion(hyperspyui.info.version)

    # First, clear all default settings!
    Settings.clear_defaults()
    # Setup default for single/multi-instance
    settings = Settings(group="General")
    settings.set_default('allow_multiple_instances', False)
    if settings['allow_multiple_instances', bool]:
        # Using multiple instances, get a new application
        app = QtGui.QApplication(sys.argv)
    else:
        # Make sure we only have a single instance
        app = get_app('hyperspyui')

    log_file = _get_logfile()
    if log_file:
        sys.stdout = sys.stderr = log_file
    else:
        log_file = dummy_context_manager()

    with log_file:
        # Create and display the splash screen
        splash_pix = QtGui.QPixmap(
            os.path.dirname(__file__) + './images/splash.png')
        splash = QtGui.QSplashScreen(splash_pix,
                                     QtCore.Qt.WindowStaysOnTopHint)
        splash.setMask(splash_pix.mask())
        splash.show()
        app.processEvents()

        # Need to have import here, as it can take some time, so should happen
        # after displaying splash
        from hyperspyui.mainwindow import MainWindow

        form = MainWindow()
        if not settings['allow_multiple_instances', bool]:
            if QT_BINDING == 'pyqt':
                app.messageAvailable.connect(form.handleSecondInstance)
            elif QT_BINDING == 'pyside':
                app.messageReceived.connect(form.handleSecondInstance)
        form.showMaximized()

        splash.finish(form)
        form.load_complete.emit()
        # Ensure logging is OK
        import hyperspy.api as hs
        hs.set_log_level(LOGLEVEL)

        app.exec_()
Beispiel #3
0
def main():
    from qtpy.QtCore import Qt, QCoreApplication
    from qtpy.QtWidgets import QApplication
    from qtpy import API

    import hyperspyui.info
    from hyperspyui.settings import Settings

    QCoreApplication.setAttribute(Qt.AA_ShareOpenGLContexts)
    # Need to set early to make QSettings accessible
    QCoreApplication.setApplicationName("HyperSpyUI")
    QCoreApplication.setOrganizationName("Hyperspy")
    QCoreApplication.setApplicationVersion(hyperspyui.info.__version__)

    # First, clear all default settings!
    # TODO: This will cause a concurrency issue with multiple launch
    Settings.clear_defaults()
    # Setup default for single/multi-instance
    settings = Settings(group="General")
    settings.set_default('allow_multiple_instances', False)
    if settings['allow_multiple_instances', bool]:
        # Using multiple instances, get a new application
        app = QApplication(sys.argv)
    else:
        # Make sure we only have a single instance
        from hyperspyui.singleapplication import get_app
        app = get_app('hyperspyui')

    splash = get_splash()

    log_file = _get_logfile()
    if log_file:
        sys.stdout = sys.stderr = log_file
    else:
        @contextmanager
        def dummy_context_manager(*args, **kwargs):
            yield
        log_file = dummy_context_manager()

    with log_file:
        # Need to have import here, since QApplication needs to be called first
        from hyperspyui.mainwindow import MainWindow

        form = MainWindow(splash=splash)
        if not settings['allow_multiple_instances', bool]:
            if "pyqt" in API:
                app.messageAvailable.connect(form.handleSecondInstance)
            elif API == 'pyside':
                app.messageReceived.connect(form.handleSecondInstance)
        form.showMaximized()

        form.splash.hide()
        form.load_complete.emit()
        # Ensure logging is OK
        import hyperspy.api as hs
        hs.set_log_level(LOGLEVEL)

        app.exec_()
Beispiel #4
0
 def _create_settings_widgets(self, settings):
     """
     Create a widget for a settings instance, containing label/editor pairs
     for each setting in the current level of the passed QSettings instance.
     The key of the setting is used as the label, but it's capitalized and
     underscores are replaced by spaces.
     """
     wrap = QWidget(self)
     form = QFormLayout()
     hint_lookup = Settings()
     for k in settings.allKeys():
         if k.startswith("_"):
             continue  # Ignore hidden keys
         v = settings.value(k)  # Read value
         label = k.capitalize().replace('_', ' ')
         abs_key = settings.group() + '/' + k
         self._initial_values[abs_key] = v  # Store initial value
         hints = hint_lookup.get_enum_hint(abs_key)  # Check for enum hints
         # Create a fitting editor widget based on value type:
         if hints is not None:
             w = QComboBox()
             w.addItems(hints)
             w.setEditable(True)
             w.setEditText(v)
             w.editTextChanged.connect(
                 partial(self._on_setting_changed, abs_key, w))
         elif isinstance(v, str):
             if v.lower() in ('true', 'false'):
                 w = QCheckBox()
                 w.setChecked(v.lower() == 'true')
                 w.toggled.connect(
                     partial(self._on_setting_changed, abs_key, w))
             else:
                 w = QLineEdit(v)
                 w.textChanged.connect(
                     partial(self._on_setting_changed, abs_key, w))
         elif isinstance(v, int):
             w = QSpinBox()
             w.setRange(np.iinfo(np.int32).min, np.iinfo(np.int32).max)
             w.setValue(v)
             w.valueChanged.connect(
                 partial(self._on_setting_changed, abs_key, w))
         elif isinstance(v, float):
             w = QDoubleSpinBox()
             w.setRange(np.finfo(np.float32).min, np.finfo(np.float32).max)
             w.setValue(v)
             w.valueChanged.connect(
                 partial(self._on_setting_changed, abs_key, w))
         else:
             w = QLineEdit(str(v))
             w.textChanged.connect(
                 partial(self._on_setting_changed, abs_key, w))
         self._lut[abs_key] = w
         form.addRow(label, w)
     wrap.setLayout(form)
     return wrap
Beispiel #5
0
 def _create_settings_widgets(self, settings):
     """
     Create a widget for a settings instance, containing label/editor pairs
     for each setting in the current level of the passed QSettings instance.
     The key of the setting is used as the label, but it's capitalized and
     underscores are replaced by spaces.
     """
     wrap = QWidget(self)
     form = QFormLayout()
     hint_lookup = Settings()
     for k in settings.allKeys():
         if k.startswith("_"):
             continue                                # Ignore hidden keys
         v = settings.value(k)                       # Read value
         label = k.capitalize().replace('_', ' ')
         abs_key = settings.group() + '/' + k
         self._initial_values[abs_key] = v           # Store initial value
         hints = hint_lookup.get_enum_hint(abs_key)  # Check for enum hints
         # Create a fitting editor widget based on value type:
         if hints is not None:
             w = QComboBox()
             w.addItems(hints)
             w.setEditable(True)
             w.setEditText(v)
             w.editTextChanged.connect(partial(self._on_setting_changed,
                                               abs_key, w))
         elif isinstance(v, str):
             if v.lower() in ('true', 'false'):
                 w = QCheckBox()
                 w.setChecked(v.lower() == 'true')
                 w.toggled.connect(partial(self._on_setting_changed,
                                           abs_key, w))
             else:
                 w = QLineEdit(v)
                 w.textChanged.connect(partial(self._on_setting_changed,
                                               abs_key, w))
         elif isinstance(v, int):
             w = QSpinBox()
             w.setRange(np.iinfo(np.int32).min, np.iinfo(np.int32).max)
             w.setValue(v)
             w.valueChanged.connect(partial(self._on_setting_changed,
                                            abs_key, w))
         elif isinstance(v, float):
             w = QDoubleSpinBox()
             w.setRange(np.finfo(np.float32).min, np.finfo(np.float32).max)
             w.setValue(v)
             w.valueChanged.connect(partial(self._on_setting_changed,
                                            abs_key, w))
         else:
             w = QLineEdit(str(v))
             w.textChanged.connect(partial(self._on_setting_changed,
                                           abs_key, w))
         self._lut[abs_key] = w
         form.addRow(label, w)
     wrap.setLayout(form)
     return wrap
Beispiel #6
0
    def __init__(self, main_window):
        """
        Initializates the manager, and performs discovery of plugins
        """
        self.plugins = AttributeDict()
        self.ui = main_window
        self._enabled = {}
        self.settings = Settings(self.ui, group="General")
        self.settings.set_default("extra_plugin_directories", "")
        self.enabled_store = Settings(self.ui, group="PluginManager/enabled")

        self.discover()
Beispiel #7
0
    def _on_reset(self):
        """
        Callback for reset button. Prompts user for confirmation, then proceeds
        to reset settings to default values if confirmed, before updating
        controls and applying any changes (emits change signal if any changes).
        """
        mb = QMessageBox(
            QMessageBox.Warning, tr("Reset all settings"),
            tr("This will reset all settings to their default " +
               "values. Are you sure you want to continue?"),
            QMessageBox.Yes | QMessageBox.No)
        mb.setDefaultButton(QMessageBox.No)
        dr = mb.exec_()
        if dr == QMessageBox.Yes:
            # This clears all settings, and recreates only those values
            # initialized with set_default this session.
            Settings.restore_from_defaults()

            # Now we update controls:
            s = QSettings(self.ui)
            keys = list(
                self._initial_values.keys())  # Use copy, as we may modify
            for k in keys:
                # Check if setting is still present
                if s.contains(k):
                    # Present, update to new value (triggers _on_change)
                    v = s.value(k)
                    w = self._lut[k]
                    if isinstance(w, QLineEdit):
                        w.setText(v)
                    elif isinstance(w, QCheckBox):
                        w.setChecked(v.lower() == "true")
                    elif isinstance(w, (QSpinBox, QDoubleSpinBox)):
                        w.setValue(v)
                else:
                    # Setting was removed, remove editor
                    w = self._lut[k]
                    layout = w.parent().layout()
                    label = layout.labelForField(w)
                    layout.removeWidget(w)
                    w.close()
                    if label is not None:
                        layout.removeWidget(label)
                        label.close()
                    del self._lut[k]
                    del self._initial_values[k]
                    self._changes[k] = None
                    # Check whether all editors for tab was removed
                    if layout.count() == 0:
                        wrap = w.parent()
                        self.tabs.removeTab(self.tabs.indexOf(wrap))
            # Finally apply changes (update _initial_values, and emit signal)
            self.apply_changes()
Beispiel #8
0
    def _on_reset(self):
        """
        Callback for reset button. Prompts user for confirmation, then proceeds
        to reset settings to default values if confirmed, before updating
        controls and applying any changes (emits change signal if any changes).
        """
        mb = QMessageBox(QMessageBox.Warning,
                         tr("Reset all settings"),
                         tr("This will reset all settings to their default " +
                            "values. Are you sure you want to continue?"),
                         QMessageBox.Yes | QMessageBox.No)
        mb.setDefaultButton(QMessageBox.No)
        dr = mb.exec_()
        if dr == QMessageBox.Yes:
            # This clears all settings, and recreates only those values
            # initialized with set_default this session.
            Settings.restore_from_defaults()

            # Now we update controls:
            s = QSettings(self.ui)
            keys = list(self._initial_values.keys())  # Use copy, as we may modify
            for k in keys:
                # Check if setting is still present
                if s.contains(k):
                    # Present, update to new value (triggers _on_change)
                    v = s.value(k)
                    w = self._lut[k]
                    if isinstance(w, QLineEdit):
                        w.setText(v)
                    elif isinstance(w, QCheckBox):
                        w.setChecked(v.lower() == "true")
                    elif isinstance(w, (QSpinBox, QDoubleSpinBox)):
                        w.setValue(v)
                else:
                    # Setting was removed, remove editor
                    w = self._lut[k]
                    layout = w.parent().layout()
                    label = layout.labelForField(w)
                    layout.removeWidget(w)
                    w.close()
                    if label is not None:
                        layout.removeWidget(label)
                        label.close()
                    del self._lut[k]
                    del self._initial_values[k]
                    self._changes[k] = None
                    # Check whether all editors for tab was removed
                    if layout.count() == 0:
                        wrap = w.parent()
                        self.tabs.removeTab(self.tabs.indexOf(wrap))
            # Finally apply changes (update _initial_values, and emit signal)
            self.apply_changes()
Beispiel #9
0
    def __init__(self, main_window):
        """
        Initializates the manager, and performs discovery of plugins
        """
        self.plugins = AttributeDict()
        self.ui = main_window
        self._enabled = {}
        self.settings = Settings(self.ui, group="General")
        self.settings.set_default("extra_plugin_directories", "")
        self.enabled_store = Settings(self.ui, group="PluginManager/enabled")

        self.discover()
Beispiel #10
0
def main():
    from python_qt_binding import QtGui, QtCore, QT_BINDING
    import hyperspyui.info
    from hyperspyui.singleapplication import get_app
    from hyperspyui.settings import Settings
    from hyperspyui.util import dummy_context_manager

    # Need to set early to make QSettings accessible
    QtCore.QCoreApplication.setApplicationName("HyperSpyUI")
    QtCore.QCoreApplication.setOrganizationName("Hyperspy")
    QtCore.QCoreApplication.setApplicationVersion(hyperspyui.info.version)

    # First, clear all default settings!
    # TODO: This will cause a concurrency issue with multiple launch
    Settings.clear_defaults()
    # Setup default for single/multi-instance
    settings = Settings(group="General")
    settings.set_default('allow_multiple_instances', False)
    if settings['allow_multiple_instances', bool]:
        # Using multiple instances, get a new application
        app = QtGui.QApplication(sys.argv)
    else:
        # Make sure we only have a single instance
        app = get_app('hyperspyui')

    log_file = _get_logfile()
    if log_file:
        sys.stdout = sys.stderr = log_file
    else:
        log_file = dummy_context_manager()

    with log_file:
        # Create and display the splash screen
        splash_pix = QtGui.QPixmap(
            os.path.dirname(__file__) + './images/splash.png')
        splash = QtGui.QSplashScreen(splash_pix,
                                     QtCore.Qt.WindowStaysOnTopHint)
        splash.setMask(splash_pix.mask())
        splash.show()
        app.processEvents()

        # Need to have import here, as it can take some time, so should happen
        # after displaying splash
        from hyperspyui.mainwindow import MainWindow

        form = MainWindow()
        if not settings['allow_multiple_instances', bool]:
            if QT_BINDING == 'pyqt':
                app.messageAvailable.connect(form.handleSecondInstance)
            elif QT_BINDING == 'pyside':
                app.messageReceived.connect(form.handleSecondInstance)
        form.showMaximized()

        splash.finish(form)
        form.load_complete.emit()
        # Ensure logging is OK
        import hyperspy.api as hs
        hs.set_log_level(LOGLEVEL)

        app.exec_()
Beispiel #11
0
def main():
    from qtpy.QtCore import QCoreApplication
    from qtpy.QtWidgets import QApplication
    from qtpy import API

    import hyperspyui.info
    from hyperspyui.settings import Settings

    # Need to set early to make QSettings accessible
    QCoreApplication.setApplicationName("HyperSpyUI")
    QCoreApplication.setOrganizationName("Hyperspy")
    QCoreApplication.setApplicationVersion(hyperspyui.info.version)

    # First, clear all default settings!
    # TODO: This will cause a concurrency issue with multiple launch
    Settings.clear_defaults()
    # Setup default for single/multi-instance
    settings = Settings(group="General")
    settings.set_default('allow_multiple_instances', False)
    if settings['allow_multiple_instances', bool]:
        # Using multiple instances, get a new application
        app = QApplication(sys.argv)
    else:
        # Make sure we only have a single instance
        from hyperspyui.singleapplication import get_app
        app = get_app('hyperspyui')

    splash = get_splash()

    log_file = _get_logfile()
    if log_file:
        sys.stdout = sys.stderr = log_file
    else:

        @contextmanager
        def dummy_context_manager(*args, **kwargs):
            yield

        log_file = dummy_context_manager()

    with log_file:
        # Need to have import here, since QApplication needs to be called first
        from hyperspyui.mainwindow import MainWindow

        form = MainWindow(splash=splash)
        if not settings['allow_multiple_instances', bool]:
            if "pyqt" in API:
                app.messageAvailable.connect(form.handleSecondInstance)
            elif API == 'pyside':
                app.messageReceived.connect(form.handleSecondInstance)
        form.showMaximized()

        form.splash.hide()
        form.load_complete.emit()
        # Ensure logging is OK
        import hyperspy.api as hs
        hs.set_log_level(LOGLEVEL)

        app.exec_()
Beispiel #12
0
    def __init__(self, main_window):
        super(Plugin, self).__init__()
        self.ui = main_window
        if self.name is None:
            set_group = 'plugins/' + str.lower(self.__class__.__name__)
            set_group = set_group.replace('.', '')
        else:
            set_group = 'plugins/' + self.name.replace('/', '-').replace(
                '\\', '-')
        self.settings = Settings(self.ui, group=set_group)

        self.actions = {}
        self.menu_actions = {}
        self.toolbar_actions = {}
        self.tools = []
        self.widgets = set()
        self.dialogs = []
Beispiel #13
0
class MainWindowBase(QtWidgets.QMainWindow):

    """
    Base layer in application stack. Should handle the connection to our custom
    matplotlib backend, and manage the Figures. As such, it does not know
    anything about hyperspy, and can readily be reused for other projects.
    Should also set up basic UI with utilities, and relevant functions for
    inhereting classes to override.
    """

    def __init__(self, parent=None):
        super(MainWindowBase, self).__init__(parent)

        # Do the import here to update the splash

        import hyperspyui.mdi_mpl_backend
        from hyperspyui.settings import Settings

        # Setup settings:
        self.settings = Settings(self, 'General')
        # Default setting values:
        self.settings.set_default('toolbar_button_size', 24)
        self.settings.set_default('default_widget_floating', False)
        self.settings.set_default('working_directory', "")
        self.settings.set_default('low_process_priority', False)
        # Override any possible invalid stored values, which could prevent load
        if 'toolbar_button_size' not in self.settings or \
                not isinstance(self.settings['toolbar_button_size'], int):
            self.settings['toolbar_button_size'] = 24
        if self.low_process_priority:
            lowpriority()

        # State varaibles
        self.should_capture_traits = None
        self.active_tool = None

        # Collections
        self.widgets = []   # Widgets in widget bar
        self.figures = []   # Matplotlib figures
        self.editors = []   # EditorWidgets
        self.traits_dialogs = []
        self.actions = {}
        self._action_selection_cbs = {}
        self.toolbars = {}
        self.menus = {}
        self.tools = []
        self.plugin_manager = None

        # MPL backend bindings
        hyperspyui.mdi_mpl_backend.connect_on_new_figure(self.on_new_figure)
        hyperspyui.mdi_mpl_backend.connect_on_destroy(self.on_destroy_figure)

        # Create UI
        self.windowmenu = None
        self.create_ui()

        # Connect figure management functions
        self.main_frame.subWindowActivated.connect(self.on_subwin_activated)

        # Save standard layout/state
        self.settings.set_default('_geometry', self.saveGeometry())
        self.settings.set_default('_windowState', self.saveState())

        # Restore layout/state if saved
        geometry = self.settings['_geometry']
        state = self.settings['_windowState']
        if geometry:
            self.restoreGeometry(geometry)
        if state:
            self.restoreState(state)

    @property
    def toolbar_button_size(self):
        return self.settings['toolbar_button_size', int]

    @toolbar_button_size.setter
    def toolbar_button_size(self, value):
        self.settings['toolbar_button_size'] = value
        self.setIconSize(
            QtCore.QSize(self.toolbar_button_size, self.toolbar_button_size))

    @property
    def cur_dir(self):
        return self.settings['working_directory'] or ''

    @cur_dir.setter
    def cur_dir(self, value):
        self.settings['working_directory'] = value

    @property
    def low_process_priority(self):
        return self.settings['low_process_priority', bool]

    @low_process_priority.setter
    def low_process_priority(self, value):
        self.settings['low_process_priority'] = value
        if value:
            lowpriority()
        else:
            normalpriority()

    @property
    def plugins(self):
        return self.plugin_manager.plugins

    def handleSecondInstance(self, argv):
        # overload if needed
        self.setWindowState(self.windowState() & ~Qt.WindowMinimized |
                            Qt.WindowActive)
        self.activateWindow()

    def closeEvent(self, event):
        self.settings['_geometry'] = self.saveGeometry()
        self.settings['_windowState'] = self.saveState()
        return super(MainWindowBase, self).closeEvent(event)

    def reset_geometry(self):
        self.settings.restore_key_default('_geometry')
        self.settings.restore_key_default('_windowState')
        geometry = self.settings['_geometry']
        state = self.settings['_windowState']
        if geometry:
            self.restoreGeometry(geometry)
        if state:
            self.restoreState(state)
        self.setWindowState(Qt.WindowMaximized)

    def create_ui(self):
        self.setIconSize(
            QtCore.QSize(self.toolbar_button_size, self.toolbar_button_size))
        self.main_frame = QtWidgets.QMdiArea()

        self.setCorner(Qt.TopRightCorner, Qt.RightDockWidgetArea)
        self.setCorner(Qt.TopLeftCorner, Qt.LeftDockWidgetArea)

        self.set_splash("Initializing plugins")
        self.init_plugins()

        self.set_splash("Creating default actions")
        self.create_default_actions()   # Goes before menu/toolbar/widgetbar

        # Needs to go before menu, so console can be in menu
        self.set_splash("Creating console")
        self.create_console()
        # This needs to happen before the widgetbar and toolbar
        self.set_splash("Creating menus")
        self.create_menu()
        self.set_splash("Creating toolbars")
        self.create_toolbars()
        self.set_splash("Creating widgets")
        self.create_widgetbar()

        self.setCentralWidget(self.main_frame)

    def init_plugins(self):
        from .pluginmanager import PluginManager
        self.plugin_manager = PluginManager(self)
        # Disable Version selector plugin until it is fixed
        self.plugin_manager.enabled_store['Version selector'] = False
        self.plugin_manager.init_plugins()

    def create_default_actions(self):
        """
        Create default actions that can be used for e.g. toolbars and menus,
        or triggered manually.
        """
        self.set_splash("Creating plugin actions")
        self.plugin_manager.create_actions()

        self.selectable_tools = QtWidgets.QActionGroup(self)
        self.selectable_tools.setExclusive(True)

        # Nested docking action
        ac_nested = QtWidgets.QAction(tr("Nested docking"), self)
        ac_nested.setStatusTip(tr("Allow nested widget docking"))
        ac_nested.setCheckable(True)
        ac_nested.setChecked(self.isDockNestingEnabled())
        ac_nested.triggered[bool].connect(self.setDockNestingEnabled)
        self.actions['nested_docking'] = ac_nested

        # Tile windows action
        ac_tile = QtWidgets.QAction(tr("Tile"), self)
        ac_tile.setStatusTip(tr("Arranges all figures in a tile pattern"))
        ac_tile.triggered.connect(self.main_frame.tileSubWindows)
        self.actions['tile_windows'] = ac_tile

        # Cascade windows action
        ac_cascade = QtWidgets.QAction(tr("Cascade"), self)
        ac_cascade.setStatusTip(
            tr("Arranges all figures in a cascade pattern"))
        ac_cascade.triggered.connect(self.main_frame.cascadeSubWindows)
        self.actions['cascade_windows'] = ac_cascade

        # Close all figures action
        ac_close_figs = QtWidgets.QAction(tr("Close all"), self)
        ac_close_figs.setStatusTip(tr("Closes all matplotlib figures"))
        ac_close_figs.triggered.connect(lambda: matplotlib.pyplot.close("all"))
        self.actions['close_all_windows'] = ac_close_figs

        # Reset geometry action
        ac_reset_layout = QtWidgets.QAction(tr("Reset layout"), self)
        ac_reset_layout.setStatusTip(tr("Resets layout of toolbars and "
                                        "widgets"))
        ac_reset_layout.triggered.connect(self.reset_geometry)
        self.actions['reset_layout'] = ac_reset_layout

    def create_menu(self):
        mb = self.menuBar()
        # Window menu is filled in add_widget and add_figure
        self.windowmenu = mb.addMenu(tr("&Windows"))
        self.windowmenu.addAction(self._console_dock.toggleViewAction())
        self.windowmenu.addAction(self.actions['nested_docking'])
        # Figure windows go below this separator. Other windows can be added
        # above it with insertAction(self.windowmenu_sep, QAction)
        self.windowmenu_sep = self.windowmenu.addSeparator()
        self.windowmenu.addAction(self.actions['tile_windows'])
        self.windowmenu.addAction(self.actions['cascade_windows'])
        self.windowmenu.addSeparator()
        self.windowmenu.addAction(self.actions['close_all_windows'])
        self.windowmenu_actions_sep = self.windowmenu.addSeparator()

        self.plugin_manager.create_menu()

    def create_tools(self):
        """Override to create tools on UI construction.
        """
        self.plugin_manager.create_tools()

    def create_toolbars(self):
        """
        Override to create toolbars and toolbar buttons on UI construction.
        It is called after create_default_action(), so add_toolbar_button()
        can be used to add previously defined acctions.
        """
        self.create_tools()
        self.plugin_manager.create_toolbars()

    def create_widgetbar(self):
        """
        The widget bar itself is created and managed implicitly by Qt. Override
        this function to add widgets on UI construction.
        """

        self.plugin_manager.create_widgets()

    def edit_settings(self):
        """
        Shows a dialog for editing the application and plugins settings.
        """
        from hyperspyui.widgets.settingsdialog import SettingsDialog
        d = SettingsDialog(self, self)
        d.settings_changed.connect(self.on_settings_changed)
        d.exec_()

    def on_settings_changed(self):
        """
        Callback for SettingsDialog, or anything else that updates settings
        and need to apply the change.
        """
        # Due to the way the property is defined, this updates the UI:
        self.toolbar_button_size = self.toolbar_button_size
        self.low_process_priority = self.low_process_priority

    def select_tool(self, tool):
        if self.active_tool is not None:
            try:
                self.active_tool.disconnect_windows(self.figures)
            except Exception as e:
                warnings.warn("Exception disabling tool %s: %s" % (
                    self.active_tool.get_name(), e))
        self.active_tool = tool
        tool.connect_windows(self.figures)

    # --------- Figure management ---------

    # --------- MPL Events ---------

    def on_new_figure(self, figure, userdata=None):
        """
        Callback for MPL backend.
        """
        self.main_frame.addSubWindow(figure)
        self.figures.append(figure)
        self.windowmenu.addAction(figure.activateAction())
        for tool in self.tools:
            if tool.single_action() is not None:
                tool.connect_windows(figure)
        if self.active_tool is not None:
            self.active_tool.connect_windows(figure)

    def on_destroy_figure(self, figure, userdata=None):
        """
        Callback for MPL backend.
        """
        if figure in self.figures:
            self.figures.remove(figure)
        self.windowmenu.removeAction(figure.activateAction())
        for tool in self.tools:
            if tool.single_action() is not None:
                tool.disconnect_windows(figure)
        if self.active_tool is not None:
            self.active_tool.disconnect_windows(figure)
        self.main_frame.removeSubWindow(figure)

    # --------- End MPL Events ---------

    def on_subwin_activated(self, mdi_figure):
        if mdi_figure and API == 'pyside':
            mdi_figure.activateAction().setChecked(True)
        self.check_action_selections(mdi_figure)

    def check_action_selections(self, mdi_figure=None):
        if mdi_figure is None:
            mdi_figure = self.main_frame.activeSubWindow()
        for key, cb in self._action_selection_cbs.items():
            cb(mdi_figure, self.actions[key])

    # --------- End figure management ---------


    # --------- Console functions ---------

    def _get_console_exec(self):
        return ""

    def _get_console_exports(self):
        return {'ui': self}

    def _get_console_config(self):
        return None

    def on_console_executing(self, source):
        """
        Override when inherited to perform actions before exectuing 'source'.
        """
        pass

    def on_console_executed(self, response):
        """
        Override when inherited to perform actions after executing, given the
        'response' returned.
        """
        pass

    def create_console(self):
        # We could inherit QAction, and have it reroute when it triggers,
        # and then drop route when it finishes, however this will not catch
        # interactive dialogs and such.
        c = self._get_console_config()
        self.settings.set_default('console_completion_type', 'droplist')
        valid_completions = ConsoleWidget.gui_completion.values
        self.settings.set_enum_hint('console_completion_type',
                                    valid_completions)
        gui_completion = self.settings['console_completion_type']
        if gui_completion not in valid_completions:
            gui_completion = 'droplist'
        control = ConsoleWidget(config=c, gui_completion=gui_completion)
        control.executing.connect(self.on_console_executing)
        control.executed.connect(self.on_console_executed)

        # This is where we push variables to the console
        ex = self._get_console_exec()
        push = self._get_console_exports()
        control.ex(ex)
        control.push(push)

        self.console = control

        self._console_dock = QtWidgets.QDockWidget("Console")
        self._console_dock.setObjectName('console_widget')
        self._console_dock.setWidget(control)
        self.addDockWidget(Qt.BottomDockWidgetArea, self._console_dock)
Beispiel #14
0
class PluginManager(object):
    def __init__(self, main_window):
        """
        Initializates the manager, and performs discovery of plugins
        """
        self.plugins = AttributeDict()
        self.ui = main_window
        self._enabled = {}
        self.settings = Settings(self.ui, group="General")
        self.settings.set_default("extra_plugin_directories", "")
        self.enabled_store = Settings(self.ui, group="PluginManager/enabled")

        self.discover()

    @property
    def enabled(self):
        """Returns a read-only dictionary showing the enabled/disabled state
        of all plugins.
        """
        d = ReadOnlyDict()
        for name, (enabled, _) in d.items():
            d[name] = enabled
        d._readonly = True
        return d

    def enable_plugin(self, name, value=True):
        """Enable/disable plugin functionality. Also loads/unloads plugin. If
        enabling and the plugin is already loaded, this will reload the plugin.
        """
        self.enabled_store[name] = value
        ptype = self._enabled[name][1]
        self._enabled[name] = (value, ptype)
        if name in self.plugins:
            self.unload(self.plugins[name])
        if value:
            self.load(ptype)

    def disable_plugin(self, name):
        """Disable plugin functionality. Also unloads plugin.
        """
        self.enable_plugin(name, False)

    def _import_plugin_from_path(self, name, path):
        try:
            mname = "hyperspyui.plugins." + name
            if sys.version_info >= (3, 5):
                import importlib.util
                spec = importlib.util.spec_from_file_location(mname, path)
                module = importlib.util.module_from_spec(spec)
                spec.loader.exec_module(module)
            else:
                from importlib.machinery import SourceFileLoader
                loader = SourceFileLoader(mname, path)
                loader.load_module()
        except Exception:
            self.warn("import", path)

    def discover(self):
        """Auto-discover all plugins defined in plugin directory.
        """
        import hyperspyui.plugins as plugins
        for plug in plugins.__all__:
            try:
                __import__('hyperspyui.plugins.' + plug, globals())

            except Exception:
                self.warn("import", plug)

        # Import any plugins in extra dirs.
        extra_paths = self.settings['extra_plugin_directories']
        if extra_paths:
            extra_paths = extra_paths.split(os.path.pathsep)
            for path in extra_paths:
                if not os.path.isdir(path):
                    path = os.path.dirname(path)
                modules = glob.glob(path + "/*.py")
                # TODO: In release form, we should consider supporting
                # compiled plugins in pyc/pyo format
                # modules.extend(glob.glob(os.path.dirname(__file__)+"/*.py?"))
                # If so, ensure that duplicates are removed (picks py over pyc)
                modules = [
                    m for m in modules
                    if not os.path.basename(m).startswith('_')
                ]

                for m in modules:
                    name = os.path.splitext(os.path.basename(m))[0]
                    self._import_plugin_from_path(name, m)

        master = Plugin
        self.implementors = sorted(self._inheritors(master),
                                   key=lambda x: x.name)
        logger.debug("Found plugins: %s", self.implementors)

    @staticmethod
    def _inheritors(klass):
        """Return all defined classes that inherit from 'klass'.
        """
        subclasses = set()
        work = [klass]
        while work:
            parent = work.pop()
            for child in parent.__subclasses__():
                if child not in subclasses:
                    subclasses.add(child)
                    work.append(child)
        return subclasses

    def warn(self, f_name, p_name, category=RuntimeWarning):
        tbf = ''.join(traceback.format_exception(*sys.exc_info())[2:])
        warnings.warn(("Exception in {0} of hyperspyui plugin " +
                       "\"{1}\" error:\n{2}").format(f_name, p_name, tbf),
                      RuntimeWarning, 2)

    def init_plugins(self):
        self.plugins.clear()
        for plug_type in self.implementors:
            try:
                self._load_if_enabled(plug_type)
            except Exception:
                self.warn("initialization", plug_type.name)

    def create_actions(self):
        for p in self.plugins.values():
            try:
                p.create_actions()
            except Exception:
                self.warn(sys._getframe().f_code.co_name, p.name)

    def create_menu(self):
        for p in self.plugins.values():
            try:
                p.create_menu()
            except Exception:
                self.warn(sys._getframe().f_code.co_name, p.name)

    def create_tools(self):
        for p in self.plugins.values():
            try:
                p.create_tools()
            except Exception:
                self.warn(sys._getframe().f_code.co_name, p.name)

    def create_toolbars(self):
        for p in self.plugins.values():
            try:
                p.create_toolbars()
            except Exception:
                self.warn(sys._getframe().f_code.co_name, p.name)

    def create_widgets(self):
        for p in self.plugins.values():
            try:
                p.create_widgets()
            except Exception:
                self.warn(sys._getframe().f_code.co_name, p.name)

    def _load_if_enabled(self, p_type):
        if p_type is None or p_type.name is None:
            return
        if self.enabled_store[p_type.name] is None:
            # Init setting to True on first encounter
            self.enabled_store[p_type.name] = True
        enabled = self.enabled_store[p_type.name, bool]
        self._enabled[p_type.name] = (enabled, p_type)
        if enabled:
            # Init
            logger.debug("Initializing plugin: %s", p_type)
            p = p_type(self.ui)
            self.plugins[p.name] = p
            logger.debug("Plugin loaded: %s", p.name)
            return p
        return None

    def load(self, plugin_type):
        try:
            p = self._load_if_enabled(plugin_type)

            if p is not None:
                # Order of execution is significant!
                p.create_actions()
                p.create_menu()
                p.create_tools()
                p.create_toolbars()
                p.create_widgets()
        except Exception:
            self.warn(sys._getframe().f_code.co_name, plugin_type)

    def load_from_file(self, path):
        master = Plugin
        prev = self._inheritors(master)
        name = os.path.splitext(os.path.basename(path))[0]
        mod_name = 'hyperspyui.plugins.' + name
        reload_plugins = mod_name in sys.modules
        try:
            if sys.version_info >= (3, 5):
                spec = importlib.util.spec_from_file_location(mod_name, path)
                mod = importlib.util.module_from_spec(spec)
                spec.loader.exec_module(mod)
            else:
                importlib.machinery.SourceFileLoader(mod_name,
                                                     path).load_module()
        except Exception:
            self.warn("import", name)
        loaded = self._inheritors(master).difference(prev)

        new_ps = []
        for plug_type in loaded:
            if reload_plugins and plug_type.name in self.plugins:
                # Unload any plugins with same name
                self.unload(self.plugins[plug_type.name])
            try:
                p = self._load_if_enabled(plug_type)
            except Exception:
                self.warn('load', plug_type.name)
            if p is not None:
                new_ps.append(p)
        for p in new_ps:
            try:
                p.create_actions()
            except Exception:
                self.warn('create_actions', p.name)
        for p in new_ps:
            try:
                p.create_menu()
            except Exception:
                self.warn('create_menu', p.name)
        for p in new_ps:
            try:
                p.create_tools()
            except Exception:
                self.warn('create_tools', p.name)
        for p in new_ps:
            try:
                p.create_toolbars()
            except Exception:
                self.warn('create_toolbars', p.name)
        for p in new_ps:
            try:
                p.create_widgets()
            except Exception:
                self.warn('create_widgets', p.name)
        return new_ps

    def unload(self, plugin):
        plugin.unload()
        self.plugins.pop(plugin.name)

    def reload(self, plugin):
        new_module = importlib.reload(sys.modules[plugin.__module__])
        new_ptype = new_module[plugin.__class__.__name__]
        if new_ptype is not None:
            self.unload(plugin)
            self.load(new_ptype)
Beispiel #15
0
class PluginManager(object):

    def __init__(self, main_window):
        """
        Initializates the manager, and performs discovery of plugins
        """
        self.plugins = AttributeDict()
        self.ui = main_window
        self._enabled = {}
        self.settings = Settings(self.ui, group="General")
        self.settings.set_default("extra_plugin_directories", "")
        self.enabled_store = Settings(self.ui, group="PluginManager/enabled")

        self.discover()

    @property
    def enabled(self):
        """Returns a read-only dictionary showing the enabled/disabled state
        of all plugins.
        """
        d = ReadOnlyDict()
        for name, (enabled, _) in d.items():
            d[name] = enabled
        d._readonly = True
        return d

    def enable_plugin(self, name, value=True):
        """Enable/disable plugin functionality. Also loads/unloads plugin. If
        enabling and the plugin is already loaded, this will reload the plugin.
        """
        self.enabled_store[name] = value
        ptype = self._enabled[name][1]
        self._enabled[name] = (value, ptype)
        if name in self.plugins:
            self.unload(self.plugins[name])
        if value:
            self.load(ptype)

    def disable_plugin(self, name):
        """Disable plugin functionality. Also unloads plugin.
        """
        self.enable_plugin(name, False)

    def _import_plugin_from_path(self, name, path):
        try:
            mname = "hyperspyui.plugins." + name
            if sys.version_info >= (3, 5):
                import importlib.util
                spec = importlib.util.spec_from_file_location(mname, path)
                module = importlib.util.module_from_spec(spec)
                spec.loader.exec_module(module)
            else:
                from importlib.machinery import SourceFileLoader
                loader = SourceFileLoader(mname, path)
                loader.load_module()
        except Exception:
            self.warn("import", path)

    def discover(self):
        """Auto-discover all plugins defined in plugin directory.
        """
        import hyperspyui.plugins as plugins
        for plug in plugins.__all__:
            try:
                __import__('hyperspyui.plugins.' + plug, globals())

            except Exception:
                self.warn("import", plug)

        # Import any plugins in extra dirs.
        extra_paths = self.settings['extra_plugin_directories']
        if extra_paths:
            extra_paths = extra_paths.split(os.path.pathsep)
            for path in extra_paths:
                if not os.path.isdir(path):
                    path = os.path.dirname(path)
                modules = glob.glob(path + "/*.py")
                # TODO: In release form, we should consider supporting
                # compiled plugins in pyc/pyo format
                # modules.extend(glob.glob(os.path.dirname(__file__)+"/*.py?"))
                # If so, ensure that duplicates are removed (picks py over pyc)
                modules = [m for m in modules
                           if not os.path.basename(m).startswith('_')]

                for m in modules:
                    name = os.path.splitext(os.path.basename(m))[0]
                    self._import_plugin_from_path(name, m)

        master = Plugin
        self.implementors = sorted(self._inheritors(master), key=lambda x: x.name)
        logger.debug("Found plugins: %s", self.implementors)

    @staticmethod
    def _inheritors(klass):
        """Return all defined classes that inherit from 'klass'.
        """
        subclasses = set()
        work = [klass]
        while work:
            parent = work.pop()
            for child in parent.__subclasses__():
                if child not in subclasses:
                    subclasses.add(child)
                    work.append(child)
        return subclasses

    def warn(self, f_name, p_name, category=RuntimeWarning):
        tbf = ''.join(traceback.format_exception(*sys.exc_info())[2:])
        warnings.warn(("Exception in {0} of hyperspyui plugin " +
                       "\"{1}\" error:\n{2}").format(f_name, p_name, tbf),
                      RuntimeWarning, 2)

    def init_plugins(self):
        self.plugins.clear()
        for plug_type in self.implementors:
            try:
                self._load_if_enabled(plug_type)
            except Exception:
                self.warn("initialization", plug_type.name)

    def create_actions(self):
        for p in self.plugins.values():
            try:
                p.create_actions()
            except Exception:
                self.warn(sys._getframe().f_code.co_name, p.name)

    def create_menu(self):
        for p in self.plugins.values():
            try:
                p.create_menu()
            except Exception:
                self.warn(sys._getframe().f_code.co_name, p.name)

    def create_tools(self):
        for p in self.plugins.values():
            try:
                p.create_tools()
            except Exception:
                self.warn(sys._getframe().f_code.co_name, p.name)

    def create_toolbars(self):
        for p in self.plugins.values():
            try:
                p.create_toolbars()
            except Exception:
                self.warn(sys._getframe().f_code.co_name, p.name)

    def create_widgets(self):
        for p in self.plugins.values():
            try:
                p.create_widgets()
            except Exception:
                self.warn(sys._getframe().f_code.co_name, p.name)

    def _load_if_enabled(self, p_type):
        if p_type is None or p_type.name is None:
            return
        if self.enabled_store[p_type.name] is None:
            # Init setting to True on first encounter
            self.enabled_store[p_type.name] = True
        enabled = self.enabled_store[p_type.name, bool]
        self._enabled[p_type.name] = (enabled, p_type)
        if enabled:
            # Init
            logger.debug("Initializing plugin: %s", p_type)
            p = p_type(self.ui)
            self.plugins[p.name] = p
            logger.debug("Plugin loaded: %s", p.name)
            return p
        return None

    def load(self, plugin_type):
        try:
            p = self._load_if_enabled(plugin_type)

            if p is not None:
                # Order of execution is significant!
                p.create_actions()
                p.create_menu()
                p.create_tools()
                p.create_toolbars()
                p.create_widgets()
        except Exception:
            self.warn(sys._getframe().f_code.co_name, p.name)

    def load_from_file(self, path):
        master = Plugin
        prev = self._inheritors(master)
        name = os.path.splitext(os.path.basename(path))[0]
        mod_name = 'hyperspyui.plugins.' + name
        reload_plugins = mod_name in sys.modules
        try:
            if sys.version_info >= (3, 5):
                spec = importlib.util.spec_from_file_location(
                    mod_name, path)
                mod = importlib.util.module_from_spec(spec)
                spec.loader.exec_module(mod)
            else:
                importlib.machinery.SourceFileLoader(
                    mod_name, path).load_module()
        except Exception:
            self.warn("import", name)
        loaded = self._inheritors(master).difference(prev)

        new_ps = []
        for plug_type in loaded:
            if reload_plugins and plug_type.name in self.plugins:
                # Unload any plugins with same name
                self.unload(self.plugins[plug_type.name])
            try:
                p = self._load_if_enabled(plug_type)
            except Exception:
                self.warn('load', plug_type.name)
            if p is not None:
                new_ps.append(p)
        for p in new_ps:
            try:
                p.create_actions()
            except Exception:
                self.warn('create_actions', p.name)
        for p in new_ps:
            try:
                p.create_menu()
            except Exception:
                self.warn('create_menu', p.name)
        for p in new_ps:
            try:
                p.create_tools()
            except Exception:
                self.warn('create_tools', p.name)
        for p in new_ps:
            try:
                p.create_toolbars()
            except Exception:
                self.warn('create_toolbars', p.name)
        for p in new_ps:
            try:
                p.create_widgets()
            except Exception:
                self.warn('create_widgets', p.name)
        return new_ps

    def unload(self, plugin):
        plugin.unload()
        self.plugins.pop(plugin.name)

    def reload(self, plugin):
        new_module = importlib.reload(sys.modules[plugin.__module__])
        new_ptype = new_module[plugin.__class__.__name__]
        if new_ptype is not None:
            self.unload(plugin)
            self.load(new_ptype)
Beispiel #16
0
def main():
    from qtpy.QtCore import Qt, QCoreApplication
    from qtpy.QtWidgets import QApplication
    from qtpy import API

    import hyperspyui.info
    from hyperspyui.settings import Settings

    # QtWebEngineWidgets must be imported before a QCoreApplication instance
    # is created (used in eelsdb plugin)
    # Avoid a bug in Qt: https://bugreports.qt.io/browse/QTBUG-46720
    from qtpy import QtWebEngineWidgets

    # Need to set early to make QSettings accessible
    QCoreApplication.setApplicationName("HyperSpyUI")
    QCoreApplication.setOrganizationName("Hyperspy")
    QCoreApplication.setApplicationVersion(hyperspyui.info.__version__)
    # To avoid the warning:
    # Qt WebEngine seems to be initialized from a plugin. Please set
    # Qt::AA_ShareOpenGLContexts using QCoreApplication::setAttribute before
    # constructing QGuiApplication.
    # Only available for pyqt>=5.4
    if hasattr(Qt, "AA_ShareOpenGLContexts"):
        QCoreApplication.setAttribute(Qt.AA_ShareOpenGLContexts)

    # First, clear all default settings!
    # TODO: This will cause a concurrency issue with multiple launch
    Settings.clear_defaults()
    # Setup default for single/multi-instance
    settings = Settings(group="General")
    settings.set_default('allow_multiple_instances', False)
    if settings['allow_multiple_instances', bool]:
        # Using multiple instances, get a new application
        app = QApplication(sys.argv)
    else:
        # Make sure we only have a single instance
        from hyperspyui.singleapplication import get_app
        app = get_app('hyperspyui')

    splash = get_splash()

    log_file = _get_logfile()
    if log_file:
        sys.stdout = sys.stderr = log_file
    else:

        @contextmanager
        def dummy_context_manager(*args, **kwargs):
            yield

        log_file = dummy_context_manager()

    with log_file:
        # Need to have import here, since QApplication needs to be called first
        from hyperspyui.mainwindow import MainWindow

        form = MainWindow(splash=splash)
        if not settings['allow_multiple_instances', bool]:
            if "pyqt" in API:
                app.messageAvailable.connect(form.handleSecondInstance)
            elif API == 'pyside':
                app.messageReceived.connect(form.handleSecondInstance)
        form.showMaximized()

        form.splash.hide()
        form.load_complete.emit()
        # Ensure logging is OK
        import hyperspy.api as hs
        hs.set_log_level(LOGLEVEL)

        app.exec_()