示例#1
0
class PreferencesDialog(QDialog):
    def __init__(self, parent=None):
        super().__init__(parent)

        self.contentsWidget = QListWidget()
        self.pagesWidget = QStackedWidget()

        self.plotPref = PlotPreferences(parent)
        self.dataPref = DataPreferences(parent)
        self.pagesWidget.addWidget(self.plotPref)
        self.pagesWidget.addWidget(self.dataPref)

        self.contentsWidget.addItem("Plot")
        self.contentsWidget.addItem("Data")
        self.contentsWidget.currentItemChanged.connect(self.changePage)
        self.contentsWidget.setCurrentRow(0)

        self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok
                                          | QDialogButtonBox.Apply
                                          | QDialogButtonBox.Close)

        okButton = self.buttonBox.button(QDialogButtonBox.Ok)
        okButton.clicked.connect(self.ok)
        applyButton = self.buttonBox.button(QDialogButtonBox.Apply)
        applyButton.clicked.connect(self.apply)
        closeButton = self.buttonBox.button(QDialogButtonBox.Close)
        closeButton.clicked.connect(self.close)

        horizontalLayout = QHBoxLayout()
        horizontalLayout.addWidget(self.contentsWidget)
        horizontalLayout.addWidget(self.pagesWidget)

        self.contentsWidget.setSizeAdjustPolicy(
            QAbstractScrollArea.AdjustToContents)

        mainLayout = QVBoxLayout()
        mainLayout.addLayout(horizontalLayout)
        mainLayout.addWidget(self.buttonBox)

        self.setLayout(mainLayout)

        self.setWindowTitle("Preferences")

    def changePage(self, current, previous):
        if not current:
            current = previous
        self.pagesWidget.setCurrentIndex(self.contentsWidget.row(current))

    def show(self):
        for n in range(self.pagesWidget.count()):
            self.pagesWidget.widget(n).setCurrentValues()
        super().show()

    def apply(self):
        self.pagesWidget.currentWidget().apply()

    def ok(self):
        for idx in range(self.pagesWidget.count()):
            self.pagesWidget.widget(idx).apply()
        self.accept()
示例#2
0
class RunConfigDialog(BaseRunConfigDialog):
    """Run configuration dialog box: multiple file version"""
    def __init__(self, parent=None):
        BaseRunConfigDialog.__init__(self, parent)
        self.file_to_run = None
        self.combo = None
        self.stack = None

    def run_btn_clicked(self):
        """Run button was just clicked"""
        self.file_to_run = to_text_string(self.combo.currentText())

    def setup(self, fname):
        """Setup Run Configuration dialog with filename *fname*"""
        combo_label = QLabel(_("Select a run configuration:"))
        self.combo = QComboBox()
        self.combo.setMaxVisibleItems(20)
        self.combo.setSizeAdjustPolicy(QComboBox.AdjustToMinimumContentsLength)
        self.combo.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)

        self.stack = QStackedWidget()

        configurations = _get_run_configurations()
        for index, (filename, options) in enumerate(configurations):
            if fname == filename:
                break
        else:
            # There is no run configuration for script *fname*:
            # creating a temporary configuration that will be kept only if
            # dialog changes are accepted by the user
            configurations.insert(0, (fname, RunConfiguration(fname).get()))
            index = 0
        for filename, options in configurations:
            widget = RunConfigOptions(self)
            widget.set(options)
            self.combo.addItem(filename)
            self.stack.addWidget(widget)
        self.combo.currentIndexChanged.connect(self.stack.setCurrentIndex)
        self.combo.setCurrentIndex(index)

        self.add_widgets(combo_label, self.combo, 10, self.stack)
        self.add_button_box(QDialogButtonBox.Ok|QDialogButtonBox.Cancel)

        self.setWindowTitle(_("Run configuration per file"))

    def accept(self):
        """Reimplement Qt method"""
        configurations = []
        for index in range(self.stack.count()):
            filename = to_text_string(self.combo.itemText(index))
            runconfigoptions = self.stack.widget(index)
            if index == self.stack.currentIndex() and\
               not runconfigoptions.is_valid():
                return
            options = runconfigoptions.get()
            configurations.append( (filename, options) )
        _set_run_configurations(configurations)
        QDialog.accept(self)
示例#3
0
class RunConfigDialog(BaseRunConfigDialog):
    """Run configuration dialog box: multiple file version"""
    def __init__(self, parent=None):
        BaseRunConfigDialog.__init__(self, parent)
        self.file_to_run = None
        self.combo = None
        self.stack = None
        
    def run_btn_clicked(self):
        """Run button was just clicked"""
        self.file_to_run = to_text_string(self.combo.currentText())
        
    def setup(self, fname):
        """Setup Run Configuration dialog with filename *fname*"""
        combo_label = QLabel(_("Select a run configuration:"))
        self.combo = QComboBox()
        self.combo.setMaxVisibleItems(20)
        self.combo.setSizeAdjustPolicy(QComboBox.AdjustToMinimumContentsLength)
        self.combo.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
        
        self.stack = QStackedWidget()

        configurations = _get_run_configurations()
        for index, (filename, options) in enumerate(configurations):
            if fname == filename:
                break
        else:
            # There is no run configuration for script *fname*:
            # creating a temporary configuration that will be kept only if
            # dialog changes are accepted by the user
            configurations.insert(0, (fname, RunConfiguration(fname).get()))
            index = 0
        for filename, options in configurations:
            widget = RunConfigOptions(self)
            widget.set(options)
            self.combo.addItem(filename)
            self.stack.addWidget(widget)
        self.combo.currentIndexChanged.connect(self.stack.setCurrentIndex)
        self.combo.setCurrentIndex(index)

        self.add_widgets(combo_label, self.combo, 10, self.stack)
        self.add_button_box(QDialogButtonBox.Ok|QDialogButtonBox.Cancel)

        self.setWindowTitle(_("Run configuration per file"))
        
    def accept(self):
        """Reimplement Qt method"""
        configurations = []
        for index in range(self.stack.count()):
            filename = to_text_string(self.combo.itemText(index))
            runconfigoptions = self.stack.widget(index)
            if index == self.stack.currentIndex() and\
               not runconfigoptions.is_valid():
                return
            options = runconfigoptions.get()
            configurations.append( (filename, options) )
        _set_run_configurations(configurations)
        QDialog.accept(self)
示例#4
0
文件: plugin.py 项目: burrbull/spyder
class VariableExplorer(SpyderPluginWidget):
    """Variable Explorer plugin."""

    CONF_SECTION = 'variable_explorer'
    CONFIGWIDGET_CLASS = VariableExplorerConfigPage
    DISABLE_ACTIONS_WHEN_HIDDEN = False
    INITIAL_FREE_MEMORY_TIME_TRIGGER = 60 * 1000  # ms
    SECONDARY_FREE_MEMORY_TIME_TRIGGER = 180 * 1000  # ms
    sig_option_changed = Signal(str, object)

    def __init__(self, parent):
        SpyderPluginWidget.__init__(self, parent)

        # Widgets
        self.stack = QStackedWidget(self)
        self.shellwidgets = {}

        # Layout
        layout = QVBoxLayout()
        layout.addWidget(self.stack)
        self.setLayout(layout)


        # Initialize plugin
        self.initialize_plugin()

    def get_settings(self):
        """
        Retrieve all Variable Explorer configuration settings.
        
        Specifically, return the settings in CONF_SECTION with keys in 
        REMOTE_SETTINGS, and the setting 'dataframe_format'.
        
        Returns:
            dict: settings
        """
        settings = {}
        for name in REMOTE_SETTINGS:
            settings[name] = self.get_option(name)

        # dataframe_format is stored without percent sign in config
        # to avoid interference with ConfigParser's interpolation
        name = 'dataframe_format'
        settings[name] = '%{0}'.format(self.get_option(name))
        return settings

    @Slot(str, object)
    def change_option(self, option_name, new_value):
        """
        Change a config option.

        This function is called if sig_option_changed is received. If the
        option changed is the dataframe format, then the leading '%' character
        is stripped (because it can't be stored in the user config). Then,
        the signal is emitted again, so that the new value is saved in the
        user config.
        """
        if option_name == 'dataframe_format':
            assert new_value.startswith('%')
            new_value = new_value[1:]
        self.sig_option_changed.emit(option_name, new_value)

    @Slot()
    def free_memory(self):
        """Free memory signal."""
        self.main.free_memory()
        QTimer.singleShot(self.INITIAL_FREE_MEMORY_TIME_TRIGGER,
                          lambda: self.main.free_memory())
        QTimer.singleShot(self.SECONDARY_FREE_MEMORY_TIME_TRIGGER,
                          lambda: self.main.free_memory())

    # ----- Stack accesors ----------------------------------------------------
    def set_current_widget(self, nsb):
        self.stack.setCurrentWidget(nsb)
        # We update the actions of the options button (cog menu) and we move
        # it to the layout of the current widget.
        self.refresh_actions()
        nsb.setup_options_button()

    def current_widget(self):
        return self.stack.currentWidget()

    def count(self):
        return self.stack.count()

    def remove_widget(self, nsb):
        self.stack.removeWidget(nsb)

    def add_widget(self, nsb):
        self.stack.addWidget(nsb)

    # ----- Public API --------------------------------------------------------
    def add_shellwidget(self, shellwidget):
        """
        Register shell with variable explorer.

        This function opens a new NamespaceBrowser for browsing the variables
        in the shell.
        """
        shellwidget_id = id(shellwidget)
        if shellwidget_id not in self.shellwidgets:
            self.options_button.setVisible(True)
            nsb = NamespaceBrowser(self, options_button=self.options_button)
            nsb.set_shellwidget(shellwidget)
            nsb.setup(**self.get_settings())
            nsb.sig_option_changed.connect(self.change_option)
            nsb.sig_free_memory.connect(self.free_memory)
            self.add_widget(nsb)
            self.shellwidgets[shellwidget_id] = nsb
            self.set_shellwidget_from_id(shellwidget_id)
            return nsb
        
    def remove_shellwidget(self, shellwidget_id):
        # If shellwidget_id is not in self.shellwidgets, it simply means
        # that shell was not a Python-based console (it was a terminal)
        if shellwidget_id in self.shellwidgets:
            nsb = self.shellwidgets.pop(shellwidget_id)
            self.remove_widget(nsb)
            nsb.close()
    
    def set_shellwidget_from_id(self, shellwidget_id):
        if shellwidget_id in self.shellwidgets:
            nsb = self.shellwidgets[shellwidget_id]
            self.set_current_widget(nsb)

    def import_data(self, fname):
        """Import data in current namespace"""
        if self.count():
            nsb = self.current_widget()
            nsb.refresh_table()
            nsb.import_data(filenames=fname)
            if self.dockwidget and not self.ismaximized:
                self.dockwidget.setVisible(True)
                self.dockwidget.raise_()

    #------ SpyderPluginWidget API ---------------------------------------------
    def get_plugin_title(self):
        """Return widget title"""
        return _('Variable explorer')

    def get_plugin_icon(self):
        """Return plugin icon"""
        return ima.icon('dictedit')
    
    def get_focus_widget(self):
        """
        Return the widget to give focus to when
        this plugin's dockwidget is raised on top-level
        """
        return self.current_widget()
        
    def closing_plugin(self, cancelable=False):
        """Perform actions before parent main window is closed"""
        return True
        
    def refresh_plugin(self):
        """Refresh widget"""
        pass

    def get_plugin_actions(self):
        """Return a list of actions related to plugin"""
        return self.current_widget().actions if self.current_widget() else []

    def register_plugin(self):
        """Register plugin in Spyder's main window"""
        self.main.add_dockwidget(self)

    def apply_plugin_settings(self, options):
        """Apply configuration file's plugin settings"""
        for nsb in list(self.shellwidgets.values()):
            nsb.setup(**self.get_settings())
示例#5
0
class ConfigDialog(QDialog):
    def __init__(self):
        super(ConfigDialog, self).__init__()

        # Set size and position
        self.setGeometry(0, 0, 900, 550)
        frameGm = self.frameGeometry()
        screen = QApplication.desktop().screenNumber(QApplication.desktop().cursor().pos())
        centerPoint = QApplication.desktop().screenGeometry(screen).center()
        frameGm.moveCenter(centerPoint)
        self.move(frameGm.topLeft())

        self.contentsWidget = QListView()
        self.contentsWidget.setViewMode(QListView.IconMode)
        # self.contentsWidget.setIconSize(QSize(96, 84))
        self.contentsWidget.setMovement(QListView.Static)
        self.contentsWidget.setMaximumWidth(174)
        self.contentsWidget.setSpacing(12)
        self.contentsWidget.setSelectionMode(QAbstractItemView.SingleSelection)

        self.contentsModel = QStandardItemModel()
        self.contentsWidget.setModel(self.contentsModel)
        self.contentsWidget.selectionModel().currentChanged.connect(self.changePage)

        self.buttonboxWidget = QDialogButtonBox(
            QDialogButtonBox.Ok | QDialogButtonBox.Cancel | QDialogButtonBox.Apply# | QDialogButtonBox.Help
        )
        self.buttonboxWidget.button(QDialogButtonBox.Ok).clicked.connect(self.ok)
        self.buttonboxWidget.button(QDialogButtonBox.Apply).clicked.connect(self.apply)
        self.buttonboxWidget.button(QDialogButtonBox.Cancel).clicked.connect(self.close)

        self.pagesWidget = QStackedWidget()

        horizontalLayout = QHBoxLayout()
        horizontalLayout.addWidget(self.contentsWidget)
        horizontalLayout.addWidget(self.pagesWidget, 1)

        mainLayout = QVBoxLayout()
        mainLayout.addLayout(horizontalLayout)
        # mainLayout.addStretch(1)
        mainLayout.addSpacing(12)
        mainLayout.addWidget(self.buttonboxWidget)

        self.setLayout(mainLayout)
        self.setWindowTitle("Config Dialog")

        # Set modality
        self.setModal(True)

        self.lastwidget = None

        self.createIcons()
        self.restore()

        pluginmanager.attach(self.pluginsChanged)

    def createIcons(self):
        self.contentsModel.clear()
        for pluginInfo in pluginmanager.getPluginsOfCategory("SettingsPlugin"):
            item = QStandardItem(pluginInfo.plugin_object.icon, pluginInfo.plugin_object.name())
            item.widget = pluginInfo.plugin_object.widget
            item.setTextAlignment(Qt.AlignHCenter)
            item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
            item.setSizeHint(QSize(136, 80))
            self.contentsModel.appendRow(item)

    def show(self):
        if self.lastwidget:
            self.pagesWidget.addWidget(self.lastwidget)
            self.pagesWidget.setCurrentWidget(self.lastwidget)
        self.restore()
        super(ConfigDialog, self).show()

    def changePage(self, current, previous):
        if not current:
            current = previous
        current = self.contentsModel.itemFromIndex(current)
        self.pagesWidget.addWidget(current.widget)
        self.pagesWidget.setCurrentWidget(current.widget)
        self.lastwidget = current.widget

    def pluginsChanged(self):
        self.createIcons()

    def restore(self):
        for pluginInfo in pluginmanager.getPluginsOfCategory("SettingsPlugin"):
            pluginInfo.plugin_object.restore()

        self.apply()

    def ok(self):
        self._empty()
        self.apply()
        self.accept()

    def apply(self):
        for pluginInfo in pluginmanager.getPluginsOfCategory("SettingsPlugin"):
            pluginInfo.plugin_object.save()

    def close(self):
        self._empty()
        self.restore()
        self.reject()

    def _empty(self):
        """
        Disown all widget children (otherwise their c++ objects are force deleted when the dialog closes).
        Must be run in reverse to avoid index update errors
        """
        for i in reversed(range(self.pagesWidget.count())):
            self.pagesWidget.widget(i).setParent(None)

    def closeEvent(self, event):
        self.close()
        event.accept()

    def keyPressEvent(self, e: QKeyEvent):
        if e.key() != Qt.Key_Escape:
            super(ConfigDialog, self).keyPressEvent(e)
        else:
            self.close()
class ExtendedTabWidget(QFrame):
    '''
    Like QTabWidget, except the tab bar is located elsewhere. Intended for use with ExtendedTabBar.
    '''
    def __init__(self):
        super(ExtendedTabWidget, self).__init__()

        self._tab_bar = None
        self._stack = QStackedWidget()

        self._main_layout = QBoxLayout(QBoxLayout.LeftToRight)
        self._main_layout.setContentsMargins(0, 0, 0, 0)
        self._main_layout.addWidget(self._stack)
        self.setLayout(self._main_layout)

    def _move_tab(self, from_, to):
        '''
        Handles tab moves so that the tab bar indices stay aligned with the widget stack indices.
        '''
        displaced_widget = self._stack.widget(from_)
        moved_widget = self._stack.widget(to)

        self._stack.removeWidget(moved_widget)
        self._stack.removeWidget(displaced_widget)

        self._stack.insertWidget(to, displaced_widget)
        self._stack.insertWidget(from_, moved_widget)

        self._stack.setCurrentIndex(self._tab_bar.currentIndex())

    def setTabBar(self, tab_bar):
        '''
        Sets the tab bar that will be used to switch between tabs.

        tab_bar
            The tab bar to set as the controller of this widget.
        '''
        if self._tab_bar is not None:
            raise Exception('Tab bar already set.')

        self._tab_bar = tab_bar
        tab_bar.currentChanged.connect(self._stack.setCurrentIndex)
        tab_bar.tabCloseRequested.connect(self.closeTab)
        tab_bar.tabMoved.connect(self._move_tab)

    def closeTab(self, index):
        '''
        Closes a tab, removing from this widget, the tab bar, and deleting its widget.

        index
            Index of the tab to be closed.
        '''
        self._tab_bar.removeTab(index)

        widget = self._stack.widget(index)
        self._stack.removeWidget(widget)

        widget.deleteLater()

        self._stack.setCurrentIndex(self._tab_bar.currentIndex())

    def addTab(self, widget, label):
        '''
        Adds a tab.

        widget
            The widget for the tab contents.

        label
            The name of the tab to show in the tab bar.

        Returns the index of the added tab.
        '''
        index = self._tab_bar.addTab(label)
        self._stack.insertWidget(index, widget)

        return index

    def count(self):
        '''
        Returns the number of widgets.
        '''
        return self._stack.count()

    def widget(self, index):
        '''
        Returns the widget at the given index.
        '''
        return self._stack.widget(index)

    def indexOf(self, widget):
        '''
        Returns the index of the given widget.
        '''
        return self._stack.indexOf(widget)
示例#7
0
class Plots(SpyderPluginWidget):
    """Plots plugin."""

    CONF_SECTION = 'plots'
    CONFIGWIDGET_CLASS = PlotsConfigPage
    DISABLE_ACTIONS_WHEN_HIDDEN = False
    sig_option_changed = Signal(str, object)

    def __init__(self, parent):
        SpyderPluginWidget.__init__(self, parent)

        # Widgets
        self.stack = QStackedWidget(self)
        self.shellwidgets = {}

        # Layout
        layout = QGridLayout(self)
        layout.addWidget(self.stack)

        # Initialize plugin
        self.initialize_plugin()

    def get_settings(self):
        """Retrieve all Plots configuration settings."""
        return {
            name: self.get_option(name)
            for name in ['mute_inline_plotting', 'show_plot_outline']
        }

    # ---- Stack accesors
    def set_current_widget(self, fig_browser):
        """
        Set the currently visible fig_browser in the stack widget, refresh the
        actions of the cog menu button and move it to the layout of the new
        fig_browser.
        """
        self.stack.setCurrentWidget(fig_browser)
        # We update the actions of the options button (cog menu) and
        # we move it to the layout of the current widget.
        self.refresh_actions()
        fig_browser.setup_options_button()

    def current_widget(self):
        return self.stack.currentWidget()

    def count(self):
        return self.stack.count()

    def remove_widget(self, fig_browser):
        self.stack.removeWidget(fig_browser)

    def add_widget(self, fig_browser):
        self.stack.addWidget(fig_browser)

    # ---- Public API
    def add_shellwidget(self, shellwidget):
        """
        Register shell with figure explorer.

        This function opens a new FigureBrowser for browsing the figures
        in the shell.
        """
        shellwidget_id = id(shellwidget)
        if shellwidget_id not in self.shellwidgets:
            self.options_button.setVisible(True)
            fig_browser = FigureBrowser(self,
                                        options_button=self.options_button)
            fig_browser.set_shellwidget(shellwidget)
            fig_browser.setup(**self.get_settings())
            fig_browser.sig_option_changed.connect(
                self.sig_option_changed.emit)
            fig_browser.thumbnails_sb.redirect_stdio.connect(
                self.main.redirect_internalshell_stdio)
            self.add_widget(fig_browser)
            self.shellwidgets[shellwidget_id] = fig_browser
            self.set_shellwidget_from_id(shellwidget_id)
            return fig_browser

    def remove_shellwidget(self, shellwidget_id):
        # If shellwidget_id is not in self.shellwidgets, it simply means
        # that shell was not a Python-based console (it was a terminal)
        if shellwidget_id in self.shellwidgets:
            fig_browser = self.shellwidgets.pop(shellwidget_id)
            self.remove_widget(fig_browser)
            fig_browser.close()

    def set_shellwidget_from_id(self, shellwidget_id):
        if shellwidget_id in self.shellwidgets:
            fig_browser = self.shellwidgets[shellwidget_id]
            self.set_current_widget(fig_browser)

    # ---- SpyderPluginWidget API
    def get_plugin_title(self):
        """Return widget title"""
        return _('Plots')

    def get_plugin_icon(self):
        """Return plugin icon"""
        return ima.icon('hist')

    def get_focus_widget(self):
        """
        Return the widget to give focus to when
        this plugin's dockwidget is raised on top-level
        """
        return self.current_widget()

    def closing_plugin(self, cancelable=False):
        """Perform actions before parent main window is closed"""
        return True

    def refresh_plugin(self):
        """Refresh widget"""
        pass

    def get_plugin_actions(self):
        """Return a list of actions related to plugin"""
        return self.current_widget().actions if self.current_widget() else []

    def register_plugin(self):
        """Register plugin in Spyder's main window"""
        self.main.add_dockwidget(self)

    def apply_plugin_settings(self, options):
        """Apply configuration file's plugin settings"""
        for fig_browser in list(self.shellwidgets.values()):
            fig_browser.setup(**self.get_settings())
示例#8
0
文件: plugin.py 项目: burrbull/spyder
class Plots(SpyderPluginWidget):
    """Plots plugin."""

    CONF_SECTION = 'plots'
    CONFIGWIDGET_CLASS = PlotsConfigPage
    DISABLE_ACTIONS_WHEN_HIDDEN = False
    sig_option_changed = Signal(str, object)

    def __init__(self, parent):
        SpyderPluginWidget.__init__(self, parent)

        # Widgets
        self.stack = QStackedWidget(self)
        self.shellwidgets = {}

        # Layout
        layout = QGridLayout(self)
        layout.addWidget(self.stack)

        # Initialize plugin
        self.initialize_plugin()

    def get_settings(self):
        """Retrieve all Plots configuration settings."""
        return {name: self.get_option(name) for name in
                ['mute_inline_plotting',  'show_plot_outline']}

    # ---- Stack accesors
    def set_current_widget(self, fig_browser):
        """
        Set the currently visible fig_browser in the stack widget, refresh the
        actions of the cog menu button and move it to the layout of the new
        fig_browser.
        """
        self.stack.setCurrentWidget(fig_browser)
        # We update the actions of the options button (cog menu) and
        # we move it to the layout of the current widget.
        self.refresh_actions()
        fig_browser.setup_options_button()

    def current_widget(self):
        return self.stack.currentWidget()

    def count(self):
        return self.stack.count()

    def remove_widget(self, fig_browser):
        self.stack.removeWidget(fig_browser)

    def add_widget(self, fig_browser):
        self.stack.addWidget(fig_browser)

    # ---- Public API
    def add_shellwidget(self, shellwidget):
        """
        Register shell with figure explorer.

        This function opens a new FigureBrowser for browsing the figures
        in the shell.
        """
        shellwidget_id = id(shellwidget)
        if shellwidget_id not in self.shellwidgets:
            self.options_button.setVisible(True)
            fig_browser = FigureBrowser(
                self, options_button=self.options_button,
                background_color=MAIN_BG_COLOR)
            fig_browser.set_shellwidget(shellwidget)
            fig_browser.setup(**self.get_settings())
            fig_browser.sig_option_changed.connect(
                self.sig_option_changed.emit)
            fig_browser.thumbnails_sb.redirect_stdio.connect(
                self.main.redirect_internalshell_stdio)
            self.add_widget(fig_browser)
            self.shellwidgets[shellwidget_id] = fig_browser
            self.set_shellwidget_from_id(shellwidget_id)
            return fig_browser

    def remove_shellwidget(self, shellwidget_id):
        # If shellwidget_id is not in self.shellwidgets, it simply means
        # that shell was not a Python-based console (it was a terminal)
        if shellwidget_id in self.shellwidgets:
            fig_browser = self.shellwidgets.pop(shellwidget_id)
            self.remove_widget(fig_browser)
            fig_browser.close()

    def set_shellwidget_from_id(self, shellwidget_id):
        if shellwidget_id in self.shellwidgets:
            fig_browser = self.shellwidgets[shellwidget_id]
            self.set_current_widget(fig_browser)

    # ---- SpyderPluginWidget API
    def get_plugin_title(self):
        """Return widget title"""
        return _('Plots')

    def get_plugin_icon(self):
        """Return plugin icon"""
        return ima.icon('hist')

    def get_focus_widget(self):
        """
        Return the widget to give focus to when
        this plugin's dockwidget is raised on top-level
        """
        return self.current_widget()

    def closing_plugin(self, cancelable=False):
        """Perform actions before parent main window is closed"""
        return True

    def refresh_plugin(self):
        """Refresh widget"""
        pass

    def get_plugin_actions(self):
        """Return a list of actions related to plugin"""
        return self.current_widget().actions if self.current_widget() else []

    def register_plugin(self):
        """Register plugin in Spyder's main window"""
        self.main.add_dockwidget(self)

    def apply_plugin_settings(self, options):
        """Apply configuration file's plugin settings"""
        for fig_browser in list(self.shellwidgets.values()):
            fig_browser.setup(**self.get_settings())
示例#9
0
class ConfigDialog(QDialog):
    """Configuration or preferences dialog box"""

    # Signals
    check_settings = Signal()
    size_change = Signal(QSize)

    def __init__(self, parent=None, objname=None):
        QDialog.__init__(self, parent)

        # If used for data object in tree, the main is the tree widget.
        self.parent = parent
        self.objname = objname

        # Widgets
        self.pages_widget = QStackedWidget()
        self.contents_widget = QListWidget()
        self.button_reset = QPushButton(_('Reset to defaults'))

        bbox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Apply |
                                QDialogButtonBox.Cancel)
        self.apply_btn = bbox.button(QDialogButtonBox.Apply)

        # Widgets setup
        # Destroying the C++ object right after closing the dialog box,
        # otherwise it may be garbage-collected in another QThread
        # (e.g. the editor's analysis thread in Ezcad), thus leading to
        # a segmentation fault on UNIX or an application crash on Windows
        self.setAttribute(Qt.WA_DeleteOnClose)
        if self.objname is None:
            self.setWindowTitle(_('Preferences'))
        else:
            self.setWindowTitle(_('Preferences of ') + self.objname)
        self.setWindowIcon(ima.icon('configure'))
        self.contents_widget.setMovement(QListView.Static)
        self.contents_widget.setSpacing(1)
        self.contents_widget.setCurrentRow(0)

        # Layout
        hsplitter = QSplitter()
        hsplitter.addWidget(self.contents_widget)
        hsplitter.addWidget(self.pages_widget)
        hsplitter.setSizes([150,500])

        btnlayout = QHBoxLayout()
        btnlayout.addWidget(self.button_reset)
        btnlayout.addStretch(1)
        btnlayout.addWidget(bbox)

        vlayout = QVBoxLayout()
        vlayout.addWidget(hsplitter)
        vlayout.addLayout(btnlayout)

        self.setLayout(vlayout)

        # Signals and slots
        self.pages_widget.currentChanged.connect(self.current_page_changed)
        self.contents_widget.currentRowChanged.connect(
            self.pages_widget.setCurrentIndex)
        bbox.accepted.connect(self.accept)
        bbox.rejected.connect(self.reject)
        bbox.clicked.connect(self.button_clicked)

        # Ensures that the config is present on ezcad first run
        CONF.set('main', 'interface_language', load_lang_conf())

    def get_current_index(self):
        """Return current page index"""
        return self.contents_widget.currentRow()

    def set_current_index(self, index):
        """Set current page index"""
        self.contents_widget.setCurrentRow(index)

    def get_page(self, index=None):
        """Return page widget"""
        if index is None:
            widget = self.pages_widget.currentWidget()
        else:
            widget = self.pages_widget.widget(index)
        return widget.widget()

    @Slot()
    def accept(self):
        """Reimplement Qt method"""
        for index in range(self.pages_widget.count()):
            configpage = self.get_page(index)
            configpage.apply_changes()
        QDialog.accept(self)

    def button_clicked(self, button):
        if button is self.apply_btn:
            # Apply button was clicked
            configpage = self.get_page()
            configpage.apply_changes()

    def current_page_changed(self, index):
        # widget = self.get_page(index)
        self.apply_btn.setVisible(True)
        self.apply_btn.setEnabled(True)

    def add_page(self, widget):
        scrollarea = QScrollArea(self)
        scrollarea.setWidgetResizable(True)
        scrollarea.setWidget(widget)
        self.pages_widget.addWidget(scrollarea)
        item = QListWidgetItem(self.contents_widget)
        try:
            item.setIcon(widget.get_icon())
        except TypeError:
            pass
        item.setText(widget.get_name())
        item.setFlags(Qt.ItemIsSelectable|Qt.ItemIsEnabled)
        item.setSizeHint(QSize(0, 25))

    def check_all_settings(self):
        """This method is called to check all configuration page settings
        after configuration dialog has been shown"""
        self.check_settings.emit()

    def resizeEvent(self, event):
        """
        Reimplement Qt method to be able to save the widget's size from the
        main application
        """
        QDialog.resizeEvent(self, event)
        self.size_change.emit(self.size())
示例#10
0
class MxStackedMixin:
    """Mixin to Plugin classes to stacked child widgets.

    The stacked child widgets are of MX_WIDGET_CLASS.
    Each of the stacked child widgets is connected to each MxShellWidget.

    """
    MX_WIDGET_CLASS = None  # To be defined in sub class

    def __init__(self, parent):

        self.main = parent  # Spyder3

        # Widgets
        self.stack = QStackedWidget(self)
        self.shellwidgets = {}

        # Fallback widget when no MxConsole available.
        # Spyder 4 output internal error message without this.
        self.blankwidget = QLabel(text="No MxConsole Available")
        self.blankwidget.setAlignment(Qt.AlignCenter)
        self.stack.addWidget(self.blankwidget)

        # On active tab in IPython console change
        if spyder.version_info > (5, 2):
            self.main.ipyconsole.get_widget().tabwidget.currentChanged.connect(
                self.on_ipyconsole_current_changed)
        else:
            self.main.ipyconsole.tabwidget.currentChanged.connect(
                self.on_ipyconsole_current_changed)

    # ----- Stack accesors ----------------------------------------------------
    # Modified from https://github.com/spyder-ide/spyder/blob/v3.3.2/spyder/plugins/variableexplorer.py#L140

    def set_current_widget(self, mxwidget):
        self.stack.setCurrentWidget(mxwidget)
        # self.refresh_actions()
        if spyder.version_info < (5,):
            mxwidget.setup_options_button()

    def current_widget(self):
        return self.stack.currentWidget()

    def count(self):
        return self.stack.count()

    def remove_widget(self, mxwidget):
        self.stack.removeWidget(mxwidget)

    def add_widget(self, mxwidget):
        self.stack.addWidget(mxwidget)

    # ----- Public API --------------------------------------------------------
    # Modified from https://github.com/spyder-ide/spyder/blob/v3.3.2/spyder/plugins/variableexplorer.py#L156

    def add_shellwidget(self, shellwidget):
        """
        Register shell with variable explorer.

        This function opens a new NamespaceBrowser for browsing the variables
        in the shell.
        """
        shellwidget_id = id(shellwidget)
        if shellwidget_id not in self.shellwidgets:
            if spyder.version_info < (4,) or spyder.version_info > (5,):
                mxwidget = self.MX_WIDGET_CLASS(self, options_button=None)
            else:
                self.options_button.setVisible(True)
                mxwidget = self.MX_WIDGET_CLASS(
                        self, options_button=self.options_button)
            mxwidget.set_shellwidget(shellwidget)
            # analyzer.sig_option_changed.connect(self.change_option)
            # analyzer.sig_free_memory.connect(self.free_memory)
            self.add_widget(mxwidget)
            self.shellwidgets[shellwidget_id] = mxwidget
            self.set_shellwidget_from_id(shellwidget_id)
            return mxwidget

    def remove_shellwidget(self, shellwidget_id):
        # If shellwidget_id is not in self.shellwidgets, it simply means
        # that shell was not a Python-based console (it was a terminal)
        if shellwidget_id in self.shellwidgets:
            mxwidget = self.shellwidgets.pop(shellwidget_id)
            self.remove_widget(mxwidget)
            mxwidget.close()

    def set_shellwidget_from_id(self, shellwidget_id):
        if shellwidget_id in self.shellwidgets:
            mxwidget = self.shellwidgets[shellwidget_id]
            self.set_current_widget(mxwidget)

    def on_ipyconsole_current_changed(self):
        # Slot like IPythonConsole.reflesh_plugin
        if spyder.version_info > (5, 2):
            client = self.main.ipyconsole.get_widget().tabwidget.currentWidget()
        else:
            client = self.main.ipyconsole.tabwidget.currentWidget()
        if client:
            sw = client.shellwidget
            self.set_shellwidget_from_id(id(sw))
示例#11
0
class ArrayEditor(QDialog):
    """Array Editor Dialog"""
    def __init__(self, parent=None):
        QDialog.__init__(self, parent)

        # Destroying the C++ object right after closing the dialog box,
        # otherwise it may be garbage-collected in another QThread
        # (e.g. the editor's analysis thread in Spyder), thus leading to
        # a segmentation fault on UNIX or an application crash on Windows
        self.setAttribute(Qt.WA_DeleteOnClose)

        self.data = None
        self.arraywidget = None
        self.stack = None
        self.layout = None
        self.btn_save_and_close = None
        self.btn_close = None
        # Values for 3d array editor
        self.dim_indexes = [{}, {}, {}]
        self.last_dim = 0  # Adjust this for changing the startup dimension

    def setup_and_check(self,
                        data,
                        title='',
                        readonly=False,
                        xlabels=None,
                        ylabels=None):
        """
        Setup ArrayEditor:
        return False if data is not supported, True otherwise
        """
        self.data = data
        self.data.flags.writeable = True
        is_record_array = data.dtype.names is not None
        is_masked_array = isinstance(data, np.ma.MaskedArray)

        if data.ndim > 3:
            self.error(
                _("Arrays with more than 3 dimensions are not "
                  "supported"))
            return False
        if xlabels is not None and len(xlabels) != self.data.shape[1]:
            self.error(
                _("The 'xlabels' argument length do no match array "
                  "column number"))
            return False
        if ylabels is not None and len(ylabels) != self.data.shape[0]:
            self.error(
                _("The 'ylabels' argument length do no match array row "
                  "number"))
            return False
        if not is_record_array:
            dtn = data.dtype.name
            if dtn not in SUPPORTED_FORMATS and not dtn.startswith('str') \
               and not dtn.startswith('unicode'):
                arr = _("%s arrays") % data.dtype.name
                self.error(_("%s are currently not supported") % arr)
                return False

        self.layout = QGridLayout()
        self.setLayout(self.layout)
        self.setWindowIcon(ima.icon('arredit'))
        if title:
            title = to_text_string(title) + " - " + _("NumPy array")
        else:
            title = _("Array editor")
        if readonly:
            title += ' (' + _('read only') + ')'
        self.setWindowTitle(title)
        self.resize(600, 500)

        # Stack widget
        self.stack = QStackedWidget(self)
        if is_record_array:
            for name in data.dtype.names:
                self.stack.addWidget(
                    ArrayEditorWidget(self, data[name], readonly, xlabels,
                                      ylabels))
        elif is_masked_array:
            self.stack.addWidget(
                ArrayEditorWidget(self, data, readonly, xlabels, ylabels))
            self.stack.addWidget(
                ArrayEditorWidget(self, data.data, readonly, xlabels, ylabels))
            self.stack.addWidget(
                ArrayEditorWidget(self, data.mask, readonly, xlabels, ylabels))
        elif data.ndim == 3:
            pass
        else:
            self.stack.addWidget(
                ArrayEditorWidget(self, data, readonly, xlabels, ylabels))
        self.arraywidget = self.stack.currentWidget()
        if self.arraywidget:
            self.arraywidget.model.dataChanged.connect(
                self.save_and_close_enable)
        self.stack.currentChanged.connect(self.current_widget_changed)
        self.layout.addWidget(self.stack, 1, 0)

        # Buttons configuration
        btn_layout = QHBoxLayout()
        if is_record_array or is_masked_array or data.ndim == 3:
            if is_record_array:
                btn_layout.addWidget(QLabel(_("Record array fields:")))
                names = []
                for name in data.dtype.names:
                    field = data.dtype.fields[name]
                    text = name
                    if len(field) >= 3:
                        title = field[2]
                        if not is_text_string(title):
                            title = repr(title)
                        text += ' - ' + title
                    names.append(text)
            else:
                names = [_('Masked data'), _('Data'), _('Mask')]
            if data.ndim == 3:
                # QSpinBox
                self.index_spin = QSpinBox(self, keyboardTracking=False)
                self.index_spin.valueChanged.connect(self.change_active_widget)
                # QComboBox
                names = [str(i) for i in range(3)]
                ra_combo = QComboBox(self)
                ra_combo.addItems(names)
                ra_combo.currentIndexChanged.connect(self.current_dim_changed)
                # Adding the widgets to layout
                label = QLabel(_("Axis:"))
                btn_layout.addWidget(label)
                btn_layout.addWidget(ra_combo)
                self.shape_label = QLabel()
                btn_layout.addWidget(self.shape_label)
                label = QLabel(_("Index:"))
                btn_layout.addWidget(label)
                btn_layout.addWidget(self.index_spin)
                self.slicing_label = QLabel()
                btn_layout.addWidget(self.slicing_label)
                # set the widget to display when launched
                self.current_dim_changed(self.last_dim)
            else:
                ra_combo = QComboBox(self)
                ra_combo.currentIndexChanged.connect(
                    self.stack.setCurrentIndex)
                ra_combo.addItems(names)
                btn_layout.addWidget(ra_combo)
            if is_masked_array:
                label = QLabel(
                    _("<u>Warning</u>: changes are applied separately"))
                label.setToolTip(_("For performance reasons, changes applied "\
                                   "to masked array won't be reflected in "\
                                   "array's data (and vice-versa)."))
                btn_layout.addWidget(label)

        btn_layout.addStretch()

        if not readonly:
            self.btn_save_and_close = QPushButton(_('Save and Close'))
            self.btn_save_and_close.setDisabled(True)
            self.btn_save_and_close.clicked.connect(self.accept)
            btn_layout.addWidget(self.btn_save_and_close)

        self.btn_close = QPushButton(_('Close'))
        self.btn_close.setAutoDefault(True)
        self.btn_close.setDefault(True)
        self.btn_close.clicked.connect(self.reject)
        btn_layout.addWidget(self.btn_close)
        self.layout.addLayout(btn_layout, 2, 0)

        self.setMinimumSize(400, 300)

        # Make the dialog act as a window
        self.setWindowFlags(Qt.Window)

        return True

    @Slot(QModelIndex, QModelIndex)
    def save_and_close_enable(self, left_top, bottom_right):
        """Handle the data change event to enable the save and close button."""
        if self.btn_save_and_close:
            self.btn_save_and_close.setEnabled(True)
            self.btn_save_and_close.setAutoDefault(True)
            self.btn_save_and_close.setDefault(True)

    def current_widget_changed(self, index):
        self.arraywidget = self.stack.widget(index)
        self.arraywidget.model.dataChanged.connect(self.save_and_close_enable)

    def change_active_widget(self, index):
        """
        This is implemented for handling negative values in index for
        3d arrays, to give the same behavior as slicing
        """
        string_index = [':'] * 3
        string_index[self.last_dim] = '<font color=red>%i</font>'
        self.slicing_label.setText(
            (r"Slicing: [" + ", ".join(string_index) + "]") % index)
        if index < 0:
            data_index = self.data.shape[self.last_dim] + index
        else:
            data_index = index
        slice_index = [slice(None)] * 3
        slice_index[self.last_dim] = data_index

        stack_index = self.dim_indexes[self.last_dim].get(data_index)
        if stack_index == None:
            stack_index = self.stack.count()
            try:
                self.stack.addWidget(
                    ArrayEditorWidget(self, self.data[slice_index]))
            except IndexError:  # Handle arrays of size 0 in one axis
                self.stack.addWidget(ArrayEditorWidget(self, self.data))
            self.dim_indexes[self.last_dim][data_index] = stack_index
            self.stack.update()
        self.stack.setCurrentIndex(stack_index)

    def current_dim_changed(self, index):
        """
        This change the active axis the array editor is plotting over
        in 3D
        """
        self.last_dim = index
        string_size = ['%i'] * 3
        string_size[index] = '<font color=red>%i</font>'
        self.shape_label.setText(
            ('Shape: (' + ', '.join(string_size) + ')    ') % self.data.shape)
        if self.index_spin.value() != 0:
            self.index_spin.setValue(0)
        else:
            # this is done since if the value is currently 0 it does not emit
            # currentIndexChanged(int)
            self.change_active_widget(0)
        self.index_spin.setRange(-self.data.shape[index],
                                 self.data.shape[index] - 1)

    @Slot()
    def accept(self):
        """Reimplement Qt method"""
        for index in range(self.stack.count()):
            self.stack.widget(index).accept_changes()
        QDialog.accept(self)

    def get_value(self):
        """Return modified array -- this is *not* a copy"""
        # It is import to avoid accessing Qt C++ object as it has probably
        # already been destroyed, due to the Qt.WA_DeleteOnClose attribute
        return self.data

    def error(self, message):
        """An error occured, closing the dialog box"""
        QMessageBox.critical(self, _("Array editor"), message)
        self.setAttribute(Qt.WA_DeleteOnClose)
        self.reject()

    @Slot()
    def reject(self):
        """Reimplement Qt method"""
        if self.arraywidget is not None:
            for index in range(self.stack.count()):
                self.stack.widget(index).reject_changes()
        QDialog.reject(self)
示例#12
0
class PreferencesDialog(QDialog):
    """Preferences Dialog for Napari user settings."""

    ui_schema = {
        "call_order": {
            "ui:widget": "plugins"
        },
        "highlight_thickness": {
            "ui:widget": "highlight"
        },
        "shortcuts": {
            "ui:widget": "shortcuts"
        },
        "extension2reader": {
            "ui:widget": "extension2reader"
        },
    }

    resized = Signal(QSize)

    def __init__(self, parent=None):
        from ...settings import get_settings

        super().__init__(parent)
        self.setWindowTitle(trans._("Preferences"))

        self._settings = get_settings()
        self._stack = QStackedWidget(self)
        self._list = QListWidget(self)
        self._list.setObjectName("Preferences")
        self._list.currentRowChanged.connect(self._stack.setCurrentIndex)

        # Set up buttons
        self._button_cancel = QPushButton(trans._("Cancel"))
        self._button_cancel.clicked.connect(self.reject)
        self._button_ok = QPushButton(trans._("OK"))
        self._button_ok.clicked.connect(self.accept)
        self._button_ok.setDefault(True)
        self._button_restore = QPushButton(trans._("Restore defaults"))
        self._button_restore.clicked.connect(self._restore_default_dialog)

        # Layout
        left_layout = QVBoxLayout()
        left_layout.addWidget(self._list)
        left_layout.addStretch()
        left_layout.addWidget(self._button_restore)
        left_layout.addWidget(self._button_cancel)
        left_layout.addWidget(self._button_ok)

        self.setLayout(QHBoxLayout())
        self.layout().addLayout(left_layout, 1)
        self.layout().addWidget(self._stack, 3)

        # Build dialog from settings
        self._rebuild_dialog()

    def keyPressEvent(self, e: 'QKeyEvent'):
        if e.key() == Qt.Key_Escape:
            # escape key should just close the window
            # which implies "accept"
            e.accept()
            self.accept()
            return
        super().keyPressEvent(e)

    def resizeEvent(self, event):
        """Override to emit signal."""
        self.resized.emit(event.size())
        super().resizeEvent(event)

    def _rebuild_dialog(self):
        """Removes settings not to be exposed to user and creates dialog pages."""
        # FIXME: this dialog should not need to know about the plugin manager
        from ...plugins import plugin_manager

        self._starting_pm_order = plugin_manager.call_order()
        self._starting_values = self._settings.dict(exclude={'schema_version'})

        self._list.clear()
        while self._stack.count():
            self._stack.removeWidget(self._stack.currentWidget())

        for field in self._settings.__fields__.values():
            if isinstance(field.type_, type) and issubclass(
                    field.type_, BaseModel):
                self._add_page(field)

        self._list.setCurrentRow(0)

    def _add_page(self, field: 'ModelField'):
        """Builds the preferences widget using the json schema builder.

        Parameters
        ----------
        field : ModelField
            subfield for which to create a page.
        """
        from ..._vendor.qt_json_builder.qt_jsonschema_form import WidgetBuilder

        schema, values = self._get_page_dict(field)
        name = field.field_info.title or field.name

        form = WidgetBuilder().create_form(schema, self.ui_schema)
        # set state values for widget
        form.widget.state = values
        # make settings follow state of the form widget
        form.widget.on_changed.connect(
            lambda d: getattr(self._settings, name.lower()).update(d))

        # need to disable async if octree is enabled.
        # TODO: this shouldn't live here... if there is a coupling/dependency
        # between these settings, it should be declared in the settings schema
        if (name.lower() == 'experimental'
                and values['octree'] and self._settings.env_settings().get(
                    'experimental', {}).get('async_') not in (None, '0')):
            form_layout = form.widget.layout()
            for i in range(form_layout.count()):
                wdg = form_layout.itemAt(i, form_layout.FieldRole).widget()
                if getattr(wdg, '_name') == 'async_':
                    wdg.opacity.setOpacity(0.3)
                    wdg.setDisabled(True)
                    break

        self._list.addItem(field.field_info.title or field.name)
        self._stack.addWidget(form)

    def _get_page_dict(self, field: 'ModelField') -> Tuple[dict, dict, dict]:
        """Provides the schema, set of values for each setting, and the
        properties for each setting."""
        ftype = cast('BaseModel', field.type_)
        schema = json.loads(ftype.schema_json())

        # find enums:
        for name, subfield in ftype.__fields__.items():
            if isinstance(subfield.type_, EnumMeta):
                enums = [s.value for s in subfield.type_]  # type: ignore
                schema["properties"][name]["enum"] = enums
                schema["properties"][name]["type"] = "string"

        # Need to remove certain properties that will not be displayed on the GUI
        setting = getattr(self._settings, field.name)
        with setting.enums_as_values():
            values = setting.dict()
        napari_config = getattr(setting, "NapariConfig", None)
        if hasattr(napari_config, 'preferences_exclude'):
            for val in napari_config.preferences_exclude:
                schema['properties'].pop(val, None)
                values.pop(val, None)

        return schema, values

    def _restore_default_dialog(self):
        """Launches dialog to confirm restore settings choice."""
        response = QMessageBox.question(
            self,
            trans._("Restore Settings"),
            trans._("Are you sure you want to restore default settings?"),
            QMessageBox.RestoreDefaults | QMessageBox.Cancel,
            QMessageBox.RestoreDefaults,
        )
        if response == QMessageBox.RestoreDefaults:
            self._settings.reset()
            self._rebuild_dialog()  # TODO: do we need this?

    def _restart_required_dialog(self):
        """Displays the dialog informing user a restart is required."""
        QMessageBox.information(
            self,
            trans._("Restart required"),
            trans.
            _("A restart is required for some new settings to have an effect."
              ),
        )

    def closeEvent(self, event: 'QCloseEvent') -> None:
        event.accept()
        self.accept()

    def reject(self):
        """Restores the settings in place when dialog was launched."""
        self._settings.update(self._starting_values)

        # FIXME: this dialog should not need to know about the plugin manager
        if self._starting_pm_order:
            from ...plugins import plugin_manager

            plugin_manager.set_call_order(self._starting_pm_order)
        super().reject()
示例#13
0
class PreferencesDialog(QDialog):
    """Preferences Dialog for Napari user settings."""

    resized = Signal(QSize)

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

        self._list = QListWidget(self)
        self._stack = QStackedWidget(self)

        self._list.setObjectName("Preferences")

        # Set up buttons
        self._button_cancel = QPushButton(trans._("Cancel"))
        self._button_ok = QPushButton(trans._("OK"))
        self._default_restore = QPushButton(trans._("Restore defaults"))

        # Setup
        self.setWindowTitle(trans._("Preferences"))

        # Layout
        left_layout = QVBoxLayout()
        left_layout.addWidget(self._list)
        left_layout.addStretch()
        left_layout.addWidget(self._default_restore)
        left_layout.addWidget(self._button_cancel)
        left_layout.addWidget(self._button_ok)

        main_layout = QHBoxLayout()
        main_layout.addLayout(left_layout, 1)
        main_layout.addWidget(self._stack, 3)

        self.setLayout(main_layout)

        # Signals

        self._list.currentRowChanged.connect(
            lambda index: self._stack.setCurrentIndex(index))
        self._button_cancel.clicked.connect(self.on_click_cancel)
        self._button_ok.clicked.connect(self.on_click_ok)
        self._default_restore.clicked.connect(self.restore_defaults)

        # Make widget

        self.make_dialog()
        self._list.setCurrentRow(0)

    def resizeEvent(self, event):
        """Override to emit signal."""
        self.resized.emit(event.size())
        super().resizeEvent(event)

    def make_dialog(self):
        """Removes settings not to be exposed to user and creates dialog pages."""
        # Because there are multiple pages, need to keep a list of values sets.
        self._values_orig_set_list = []
        self._values_set_list = []
        for _key, setting in SETTINGS.schemas().items():
            schema = json.loads(setting['json_schema'])
            # Need to remove certain properties that will not be displayed on the GUI
            properties = schema.pop('properties')
            model = setting['model']
            values = model.dict()
            napari_config = getattr(model, "NapariConfig", None)
            if napari_config is not None:
                for val in napari_config.preferences_exclude:
                    properties.pop(val)
                    values.pop(val)

            schema['properties'] = properties
            self._values_orig_set_list.append(set(values.items()))
            self._values_set_list.append(set(values.items()))

            # Only add pages if there are any properties to add.
            if properties:
                self.add_page(schema, values)

    def restore_defaults(self):
        """Launches dialog to confirm restore settings choice."""

        widget = ConfirmDialog(
            parent=self,
            text=trans._("Are you sure you want to restore default settings?"),
        )
        widget.valueChanged.connect(self._reset_widgets)
        widget.exec_()

    def _reset_widgets(self):
        """Deletes the widgets and rebuilds with defaults."""
        self.close()
        self._list.clear()

        for n in range(self._stack.count()):
            widget = self._stack.removeWidget(self._stack.currentWidget())
            del widget

        self.make_dialog()
        self._list.setCurrentRow(0)
        self.show()

    def on_click_ok(self):
        """Keeps the selected preferences saved to SETTINGS."""
        self.close()

    def on_click_cancel(self):
        """Restores the settings in place when dialog was launched."""
        # Need to check differences for each page.
        for n in range(self._stack.count()):
            # Must set the current row so that the proper set list is updated
            # in check differences.
            self._list.setCurrentRow(n)
            self.check_differences(
                self._values_orig_set_list[n],
                self._values_set_list[n],
            )
        self._list.setCurrentRow(0)
        self.close()

    def add_page(self, schema, values):
        """Creates a new page for each section in dialog.

        Parameters
        ----------
        schema : dict
            Json schema including all information to build each page in the
            preferences dialog.
        values : dict
            Dictionary of current values set in preferences.
        """
        widget = self.build_page_dialog(schema, values)
        self._list.addItem(schema["title"])
        self._stack.addWidget(widget)

    def build_page_dialog(self, schema, values):
        """Builds the preferences widget using the json schema builder.

        Parameters
        ----------
        schema : dict
            Json schema including all information to build each page in the
            preferences dialog.
        values : dict
            Dictionary of current values set in preferences.
        """

        builder = WidgetBuilder()
        form = builder.create_form(schema, {})
        # set state values for widget
        form.widget.state = values
        form.widget.on_changed.connect(lambda d: self.check_differences(
            set(d.items()),
            self._values_set_list[self._list.currentIndex().row()],
        ))

        return form

    def check_differences(self, new_set, values_set):
        """Changes settings in settings manager with changes from dialog.

        Parameters
        ----------
        new_set : set
            The set of new values, with tuples of key value pairs for each
            setting.
        values_set : set
            The old set of values.
        """

        page = self._list.currentItem().text().split(" ")[0].lower()
        different_values = list(new_set - values_set)

        if len(different_values) > 0:
            # change the values in SETTINGS
            for val in different_values:
                try:
                    setattr(SETTINGS._settings[page], val[0], val[1])
                    self._values_set_list[
                        self._list.currentIndex().row()] = new_set
                except:  # noqa: E722
                    continue
示例#14
0
文件: plugin.py 项目: zerocewl/spyder
class Plots(SpyderPluginWidget):
    """Plots plugin."""

    CONF_SECTION = 'plots'
    CONF_FILE = False
    DISABLE_ACTIONS_WHEN_HIDDEN = False

    def __init__(self, parent):
        SpyderPluginWidget.__init__(self, parent)

        # Widgets
        self.stack = QStackedWidget(self)
        self.stack.setStyleSheet("QStackedWidget{padding: 0px; border: 0px}")
        self.shellwidgets = {}

        # Layout
        layout = QGridLayout(self)
        layout.addWidget(self.stack)

    def get_settings(self):
        """Retrieve all Plots configuration settings."""
        return {
            name: self.get_option(name)
            for name in
            ['mute_inline_plotting', 'show_plot_outline', 'auto_fit_plotting']
        }

    # ---- Stack accesors
    def set_current_widget(self, fig_browser):
        """
        Set the currently visible fig_browser in the stack widget, refresh the
        actions of the cog menu button and move it to the layout of the new
        fig_browser.
        """
        self.stack.setCurrentWidget(fig_browser)
        # We update the actions of the options button (cog menu) and
        # we move it to the layout of the current widget.
        self._refresh_actions()
        fig_browser.setup_options_button()

    def current_widget(self):
        return self.stack.currentWidget()

    def count(self):
        return self.stack.count()

    def remove_widget(self, fig_browser):
        self.stack.removeWidget(fig_browser)

    def add_widget(self, fig_browser):
        self.stack.addWidget(fig_browser)

    # ---- Public API
    def add_shellwidget(self, shellwidget):
        """
        Register shell with figure explorer.

        This function opens a new FigureBrowser for browsing the figures
        in the shell.
        """
        shellwidget_id = id(shellwidget)
        if shellwidget_id not in self.shellwidgets:
            self.options_button.setVisible(True)
            fig_browser = FigureBrowser(self,
                                        options_button=self.options_button,
                                        background_color=MAIN_BG_COLOR)
            fig_browser.set_shellwidget(shellwidget)
            fig_browser.setup(**self.get_settings())
            fig_browser.sig_option_changed.connect(
                self.sig_option_changed.emit)
            fig_browser.thumbnails_sb.redirect_stdio.connect(
                self.main.redirect_internalshell_stdio)
            self.register_widget_shortcuts(fig_browser)
            self.add_widget(fig_browser)
            self.shellwidgets[shellwidget_id] = fig_browser
            self.set_shellwidget_from_id(shellwidget_id)
            return fig_browser

    def remove_shellwidget(self, shellwidget_id):
        # If shellwidget_id is not in self.shellwidgets, it simply means
        # that shell was not a Python-based console (it was a terminal)
        if shellwidget_id in self.shellwidgets:
            fig_browser = self.shellwidgets.pop(shellwidget_id)
            self.remove_widget(fig_browser)
            fig_browser.close()

    def set_shellwidget_from_id(self, shellwidget_id):
        if shellwidget_id in self.shellwidgets:
            fig_browser = self.shellwidgets[shellwidget_id]
            self.set_current_widget(fig_browser)

    # ---- SpyderPluginWidget API
    def get_plugin_title(self):
        """Return widget title"""
        return _('Plots')

    def get_plugin_icon(self):
        """Return plugin icon"""
        return ima.icon('hist')

    def get_focus_widget(self):
        """
        Return the widget to give focus to when
        this plugin's dockwidget is raised on top-level
        """
        return self.current_widget()

    def get_plugin_actions(self):
        """Return a list of actions related to plugin"""
        return self.current_widget().actions if self.current_widget() else []

    def apply_plugin_settings(self, options):
        """Apply configuration file's plugin settings"""
        for fig_browser in list(self.shellwidgets.values()):
            fig_browser.setup(**self.get_settings())

    def on_first_registration(self):
        """Action to be performed on first plugin registration"""
        self.tabify(self.main.variableexplorer)
示例#15
0
class ArrayEditor(QDialog):
    """Array Editor Dialog"""    
    def __init__(self, parent=None):
        QDialog.__init__(self, parent)
        
        # Destroying the C++ object right after closing the dialog box,
        # otherwise it may be garbage-collected in another QThread
        # (e.g. the editor's analysis thread in Spyder), thus leading to
        # a segmentation fault on UNIX or an application crash on Windows
        self.setAttribute(Qt.WA_DeleteOnClose)
        
        self.data = None
        self.arraywidget = None
        self.stack = None
        self.layout = None
        # Values for 3d array editor
        self.dim_indexes = [{}, {}, {}]
        self.last_dim = 0  # Adjust this for changing the startup dimension
        
    def setup_and_check(self, data, title='', readonly=False,
                        xlabels=None, ylabels=None):
        """
        Setup ArrayEditor:
        return False if data is not supported, True otherwise
        """
        self.data = data
        is_record_array = data.dtype.names is not None
        is_masked_array = isinstance(data, np.ma.MaskedArray)
        if data.size == 0:
            self.error(_("Array is empty"))
            return False
        if data.ndim > 3:
            self.error(_("Arrays with more than 3 dimensions are not supported"))
            return False
        if xlabels is not None and len(xlabels) != self.data.shape[1]:
            self.error(_("The 'xlabels' argument length do no match array "
                         "column number"))
            return False
        if ylabels is not None and len(ylabels) != self.data.shape[0]:
            self.error(_("The 'ylabels' argument length do no match array row "
                         "number"))
            return False
        if not is_record_array:
            dtn = data.dtype.name
            if dtn not in SUPPORTED_FORMATS and not dtn.startswith('str') \
               and not dtn.startswith('unicode'):
                arr = _("%s arrays") % data.dtype.name
                self.error(_("%s are currently not supported") % arr)
                return False
        
        self.layout = QGridLayout()
        self.setLayout(self.layout)
        self.setWindowIcon(ima.icon('arredit'))
        if title:
            title = to_text_string(title) + " - " + _("NumPy array")
        else:
            title = _("Array editor")
        if readonly:
            title += ' (' + _('read only') + ')'
        self.setWindowTitle(title)
        self.resize(600, 500)
        
        # Stack widget
        self.stack = QStackedWidget(self)
        if is_record_array:
            for name in data.dtype.names:
                self.stack.addWidget(ArrayEditorWidget(self, data[name],
                                                   readonly, xlabels, ylabels))
        elif is_masked_array:
            self.stack.addWidget(ArrayEditorWidget(self, data, readonly,
                                                   xlabels, ylabels))
            self.stack.addWidget(ArrayEditorWidget(self, data.data, readonly,
                                                   xlabels, ylabels))
            self.stack.addWidget(ArrayEditorWidget(self, data.mask, readonly,
                                                   xlabels, ylabels))
        elif data.ndim == 3:
            pass
        else:
            self.stack.addWidget(ArrayEditorWidget(self, data, readonly,
                                                   xlabels, ylabels))
        self.arraywidget = self.stack.currentWidget()
        self.stack.currentChanged.connect(self.current_widget_changed)
        self.layout.addWidget(self.stack, 1, 0)

        # Buttons configuration
        btn_layout = QHBoxLayout()
        if is_record_array or is_masked_array or data.ndim == 3:
            if is_record_array:
                btn_layout.addWidget(QLabel(_("Record array fields:")))
                names = []
                for name in data.dtype.names:
                    field = data.dtype.fields[name]
                    text = name
                    if len(field) >= 3:
                        title = field[2]
                        if not is_text_string(title):
                            title = repr(title)
                        text += ' - '+title
                    names.append(text)
            else:
                names = [_('Masked data'), _('Data'), _('Mask')]
            if data.ndim == 3:
                # QSpinBox
                self.index_spin = QSpinBox(self, keyboardTracking=False)
                self.index_spin.valueChanged.connect(self.change_active_widget)
                # QComboBox
                names = [str(i) for i in range(3)]
                ra_combo = QComboBox(self)
                ra_combo.addItems(names)
                ra_combo.currentIndexChanged.connect(self.current_dim_changed)    
                # Adding the widgets to layout
                label = QLabel(_("Axis:"))
                btn_layout.addWidget(label)
                btn_layout.addWidget(ra_combo)
                self.shape_label = QLabel()
                btn_layout.addWidget(self.shape_label)
                label = QLabel(_("Index:"))
                btn_layout.addWidget(label)
                btn_layout.addWidget(self.index_spin)
                self.slicing_label = QLabel()
                btn_layout.addWidget(self.slicing_label)
                # set the widget to display when launched
                self.current_dim_changed(self.last_dim)
            else:
                ra_combo = QComboBox(self)
                ra_combo.currentIndexChanged.connect(self.stack.setCurrentIndex)
                ra_combo.addItems(names)
                btn_layout.addWidget(ra_combo)
            if is_masked_array:
                label = QLabel(_("<u>Warning</u>: changes are applied separately"))
                label.setToolTip(_("For performance reasons, changes applied "\
                                   "to masked array won't be reflected in "\
                                   "array's data (and vice-versa)."))
                btn_layout.addWidget(label)
            btn_layout.addStretch()
        bbox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
        bbox.accepted.connect(self.accept)
        bbox.rejected.connect(self.reject)
        btn_layout.addWidget(bbox)
        self.layout.addLayout(btn_layout, 2, 0)

        self.setMinimumSize(400, 300)
        
        # Make the dialog act as a window
        self.setWindowFlags(Qt.Window)
        
        return True
            
    def current_widget_changed(self, index):
        self.arraywidget = self.stack.widget(index)     
            
    def change_active_widget(self, index):
        """
        This is implemented for handling negative values in index for
        3d arrays, to give the same behavior as slicing
        """
        string_index = [':']*3
        string_index[self.last_dim] = '<font color=red>%i</font>'
        self.slicing_label.setText((r"Slicing: [" + ", ".join(string_index) +
                                "]") % index)
        if index < 0:
            data_index = self.data.shape[self.last_dim] + index
        else:
            data_index = index
        slice_index = [slice(None)]*3
        slice_index[self.last_dim] = data_index

        stack_index = self.dim_indexes[self.last_dim].get(data_index)
        if stack_index == None:
            stack_index = self.stack.count()
            self.stack.addWidget(ArrayEditorWidget(self,
                                                   self.data[slice_index]))
            self.dim_indexes[self.last_dim][data_index] = stack_index
            self.stack.update()
        self.stack.setCurrentIndex(stack_index)

    def current_dim_changed(self, index):
        """
        This change the active axis the array editor is plotting over
        in 3D
        """
        self.last_dim = index
        string_size = ['%i']*3
        string_size[index] = '<font color=red>%i</font>'
        self.shape_label.setText(('Shape: (' + ', '.join(string_size) +
                                 ')    ') % self.data.shape)
        if self.index_spin.value() != 0:
            self.index_spin.setValue(0)
        else:
            # this is done since if the value is currently 0 it does not emit
            # currentIndexChanged(int)
            self.change_active_widget(0)
        self.index_spin.setRange(-self.data.shape[index],
                                 self.data.shape[index]-1)

    @Slot()
    def accept(self):
        """Reimplement Qt method"""
        for index in range(self.stack.count()):
            self.stack.widget(index).accept_changes()
        QDialog.accept(self)
        
    def get_value(self):
        """Return modified array -- this is *not* a copy"""
        # It is import to avoid accessing Qt C++ object as it has probably
        # already been destroyed, due to the Qt.WA_DeleteOnClose attribute
        return self.data

    def error(self, message):
        """An error occured, closing the dialog box"""
        QMessageBox.critical(self, _("Array editor"), message)
        self.setAttribute(Qt.WA_DeleteOnClose)
        self.reject()

    @Slot()
    def reject(self):
        """Reimplement Qt method"""
        if self.arraywidget is not None:
            for index in range(self.stack.count()):
                self.stack.widget(index).reject_changes()
        QDialog.reject(self)
示例#16
0
class VariableExplorer(SpyderPluginWidget):
    """Variable Explorer plugin."""

    CONF_SECTION = 'variable_explorer'
    CONFIGWIDGET_CLASS = VariableExplorerConfigPage
    DISABLE_ACTIONS_WHEN_HIDDEN = False
    INITIAL_FREE_MEMORY_TIME_TRIGGER = 60 * 1000  # ms
    SECONDARY_FREE_MEMORY_TIME_TRIGGER = 180 * 1000  # ms

    def __init__(self, parent):
        SpyderPluginWidget.__init__(self, parent)

        # Widgets
        self.stack = QStackedWidget(self)
        self.stack.setStyleSheet("QStackedWidget{padding: 0px; border: 0px}")
        self.shellwidgets = {}

        # Layout
        layout = QVBoxLayout()
        layout.addWidget(self.stack)
        self.setLayout(layout)

    def get_settings(self):
        """
        Retrieve all Variable Explorer configuration settings.
        
        Specifically, return the settings in CONF_SECTION with keys in 
        REMOTE_SETTINGS, and the setting 'dataframe_format'.
        
        Returns:
            dict: settings
        """
        settings = {}
        for name in REMOTE_SETTINGS:
            settings[name] = self.get_option(name)

        # dataframe_format is stored without percent sign in config
        # to avoid interference with ConfigParser's interpolation
        name = 'dataframe_format'
        settings[name] = '%{0}'.format(self.get_option(name))
        return settings

    @Slot(str, object)
    def change_option(self, option_name, new_value):
        """
        Change a config option.

        This function is called if sig_option_changed is received. If the
        option changed is the dataframe format, then the leading '%' character
        is stripped (because it can't be stored in the user config). Then,
        the signal is emitted again, so that the new value is saved in the
        user config.
        """
        if option_name == 'dataframe_format':
            assert new_value.startswith('%')
            new_value = new_value[1:]
        self.sig_option_changed.emit(option_name, new_value)

    @Slot()
    def free_memory(self):
        """Free memory signal."""
        self.main.free_memory()
        QTimer.singleShot(self.INITIAL_FREE_MEMORY_TIME_TRIGGER,
                          lambda: self.main.free_memory())
        QTimer.singleShot(self.SECONDARY_FREE_MEMORY_TIME_TRIGGER,
                          lambda: self.main.free_memory())

    # ----- Stack accesors ----------------------------------------------------
    def set_current_widget(self, nsb):
        self.stack.setCurrentWidget(nsb)
        # We update the actions of the options button (cog menu) and we move
        # it to the layout of the current widget.
        self._refresh_actions()
        nsb.setup_options_button()

    def current_widget(self):
        return self.stack.currentWidget()

    def count(self):
        return self.stack.count()

    def remove_widget(self, nsb):
        self.stack.removeWidget(nsb)

    def add_widget(self, nsb):
        self.stack.addWidget(nsb)

    # ----- Public API --------------------------------------------------------
    def add_shellwidget(self, shellwidget):
        """
        Register shell with variable explorer.

        This function opens a new NamespaceBrowser for browsing the variables
        in the shell.
        """
        shellwidget_id = id(shellwidget)
        if shellwidget_id not in self.shellwidgets:
            self.options_button.setVisible(True)
            nsb = NamespaceBrowser(self, options_button=self.options_button)
            nsb.set_shellwidget(shellwidget)
            nsb.setup(**self.get_settings())
            nsb.sig_option_changed.connect(self.change_option)
            nsb.sig_free_memory.connect(self.free_memory)
            self.add_widget(nsb)
            self.shellwidgets[shellwidget_id] = nsb
            self.set_shellwidget_from_id(shellwidget_id)
            return nsb

    def remove_shellwidget(self, shellwidget_id):
        # If shellwidget_id is not in self.shellwidgets, it simply means
        # that shell was not a Python-based console (it was a terminal)
        if shellwidget_id in self.shellwidgets:
            nsb = self.shellwidgets.pop(shellwidget_id)
            self.remove_widget(nsb)
            nsb.close()

    def set_shellwidget_from_id(self, shellwidget_id):
        if shellwidget_id in self.shellwidgets:
            nsb = self.shellwidgets[shellwidget_id]
            self.set_current_widget(nsb)

    def import_data(self, fname):
        """Import data in current namespace"""
        if self.count():
            nsb = self.current_widget()
            nsb.refresh_table()
            nsb.import_data(filenames=fname)
            if self.dockwidget:
                self.switch_to_plugin()

    #------ SpyderPluginWidget API ---------------------------------------------
    def get_plugin_title(self):
        """Return widget title"""
        return _('Variable explorer')

    def get_plugin_icon(self):
        """Return plugin icon"""
        return ima.icon('dictedit')

    def get_focus_widget(self):
        """
        Return the widget to give focus to when
        this plugin's dockwidget is raised on top-level
        """
        return self.current_widget()

    def get_plugin_actions(self):
        """Return a list of actions related to plugin"""
        return self.current_widget().actions if self.current_widget() else []

    def apply_plugin_settings(self, options):
        """Apply configuration file's plugin settings"""
        for nsb in list(self.shellwidgets.values()):
            nsb.setup(**self.get_settings())
示例#17
0
class ConfigDialog(QDialog):
    """Spyder configuration ('Preferences') dialog box"""
    
    # Signals
    check_settings = Signal()
    size_change = Signal(QSize)
    
    def __init__(self, parent=None):
        QDialog.__init__(self, parent)

        self.main = parent

        # Widgets
        self.pages_widget = QStackedWidget()
        self.pages_widget.setMinimumWidth(600)
        self.contents_widget = QListWidget()
        self.button_reset = QPushButton(_('Reset to defaults'))

        bbox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Apply |
                                QDialogButtonBox.Cancel)
        self.apply_btn = bbox.button(QDialogButtonBox.Apply)

        # Widgets setup
        # Destroying the C++ object right after closing the dialog box,
        # otherwise it may be garbage-collected in another QThread
        # (e.g. the editor's analysis thread in Spyder), thus leading to
        # a segmentation fault on UNIX or an application crash on Windows
        self.setAttribute(Qt.WA_DeleteOnClose)
        self.setWindowTitle(_('Preferences'))
        self.setWindowIcon(ima.icon('configure'))
        self.contents_widget.setMovement(QListView.Static)
        self.contents_widget.setSpacing(1)
        self.contents_widget.setCurrentRow(0)
        self.contents_widget.setMinimumWidth(220)
        self.contents_widget.setMinimumHeight(400)

        # Layout
        hsplitter = QSplitter()
        hsplitter.addWidget(self.contents_widget)
        hsplitter.addWidget(self.pages_widget)
        hsplitter.setStretchFactor(0, 1)
        hsplitter.setStretchFactor(1, 2)

        btnlayout = QHBoxLayout()
        btnlayout.addWidget(self.button_reset)
        btnlayout.addStretch(1)
        btnlayout.addWidget(bbox)

        vlayout = QVBoxLayout()
        vlayout.addWidget(hsplitter)
        vlayout.addLayout(btnlayout)

        self.setLayout(vlayout)

        # Signals and slots
        if self.main:
            self.button_reset.clicked.connect(self.main.reset_spyder)
        self.pages_widget.currentChanged.connect(self.current_page_changed)
        self.contents_widget.currentRowChanged.connect(
                                             self.pages_widget.setCurrentIndex)
        bbox.accepted.connect(self.accept)
        bbox.rejected.connect(self.reject)
        bbox.clicked.connect(self.button_clicked)

        # Ensures that the config is present on spyder first run
        CONF.set('main', 'interface_language', load_lang_conf())

    def get_current_index(self):
        """Return current page index"""
        return self.contents_widget.currentRow()
        
    def set_current_index(self, index):
        """Set current page index"""
        self.contents_widget.setCurrentRow(index)
        
    def get_page(self, index=None):
        """Return page widget"""
        if index is None:
            widget = self.pages_widget.currentWidget()
        else:
            widget = self.pages_widget.widget(index)
        return widget.widget()
    
    @Slot()
    def accept(self):
        """Reimplement Qt method"""
        for index in range(self.pages_widget.count()):
            configpage = self.get_page(index)
            if not configpage.is_valid():
                return
            configpage.apply_changes()
        QDialog.accept(self)
        
    def button_clicked(self, button):
        if button is self.apply_btn:
            # Apply button was clicked
            configpage = self.get_page()
            if not configpage.is_valid():
                return
            configpage.apply_changes()
            
    def current_page_changed(self, index):
        widget = self.get_page(index)
        self.apply_btn.setVisible(widget.apply_callback is not None)
        self.apply_btn.setEnabled(widget.is_modified)
        
    def add_page(self, widget):
        self.check_settings.connect(widget.check_settings)
        widget.show_this_page.connect(lambda row=self.contents_widget.count():
                                      self.contents_widget.setCurrentRow(row))
        widget.apply_button_enabled.connect(self.apply_btn.setEnabled)
        scrollarea = QScrollArea(self)
        scrollarea.setWidgetResizable(True)
        scrollarea.setWidget(widget)
        self.pages_widget.addWidget(scrollarea)
        item = QListWidgetItem(self.contents_widget)
        try:
            item.setIcon(widget.get_icon())
        except TypeError:
            pass
        item.setText(widget.get_name())
        item.setFlags(Qt.ItemIsSelectable|Qt.ItemIsEnabled)
        item.setSizeHint(QSize(0, 25))
        
    def check_all_settings(self):
        """This method is called to check all configuration page settings
        after configuration dialog has been shown"""
        self.check_settings.emit()
    
    def resizeEvent(self, event):
        """
        Reimplement Qt method to be able to save the widget's size from the
        main application
        """
        QDialog.resizeEvent(self, event)
        self.size_change.emit(self.size())
示例#18
0
class PreferencesDialog(QDialog):
    """Preferences Dialog for Napari user settings."""

    ui_schema = {
        "call_order": {
            "ui:widget": "plugins"
        },
    }

    resized = Signal(QSize)
    closed = Signal()

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

        self._list = QListWidget(self)
        self._stack = QStackedWidget(self)

        self._list.setObjectName("Preferences")

        # Set up buttons
        self._button_cancel = QPushButton(trans._("Cancel"))
        self._button_ok = QPushButton(trans._("OK"))
        self._default_restore = QPushButton(trans._("Restore defaults"))

        # Setup
        self.setWindowTitle(trans._("Preferences"))

        # Layout
        left_layout = QVBoxLayout()
        left_layout.addWidget(self._list)
        left_layout.addStretch()
        left_layout.addWidget(self._default_restore)
        left_layout.addWidget(self._button_cancel)
        left_layout.addWidget(self._button_ok)

        main_layout = QHBoxLayout()
        main_layout.addLayout(left_layout, 1)
        main_layout.addWidget(self._stack, 3)

        self.setLayout(main_layout)

        # Signals

        self._list.currentRowChanged.connect(
            lambda index: self._stack.setCurrentIndex(index))
        self._button_cancel.clicked.connect(self.on_click_cancel)
        self._button_ok.clicked.connect(self.on_click_ok)
        self._default_restore.clicked.connect(self.restore_defaults)

        # Make widget

        self.make_dialog()
        self._list.setCurrentRow(0)

    def closeEvent(self, event):
        """Override to emit signal."""
        self.closed.emit()
        super().closeEvent(event)

    def reject(self):
        """Override to handle Escape."""
        super().reject()
        self.close()

    def resizeEvent(self, event):
        """Override to emit signal."""
        self.resized.emit(event.size())
        super().resizeEvent(event)

    def make_dialog(self):
        """Removes settings not to be exposed to user and creates dialog pages."""

        # Because there are multiple pages, need to keep a dictionary of values dicts.
        # One set of keywords are for each page, then in each entry for a page, there are dicts
        # of setting and its value.

        self._values_orig_dict = {}
        self._values_dict = {}
        self._setting_changed_dict = {}

        for page, setting in SETTINGS.schemas().items():
            schema, values, properties = self.get_page_dict(setting)

            self._setting_changed_dict[page] = {}
            self._values_orig_dict[page] = values
            self._values_dict[page] = values

            # Only add pages if there are any properties to add.
            if properties:
                self.add_page(schema, values)

    def get_page_dict(self, setting):
        """Provides the schema, set of values for each setting, and the properties
        for each setting.

        Parameters
        ----------
        setting : dict
            Dictionary of settings for a page within the settings manager.

        Returns
        -------
        schema : dict
            Json schema of the setting page.
        values : dict
            Dictionary of values currently set for each parameter in the settings.
        properties : dict
            Dictionary of properties within the json schema.

        """

        schema = json.loads(setting['json_schema'])
        # Need to remove certain properties that will not be displayed on the GUI
        properties = schema.pop('properties')
        model = setting['model']
        values = model.dict()
        napari_config = getattr(model, "NapariConfig", None)
        if napari_config is not None:
            for val in napari_config.preferences_exclude:
                properties.pop(val)
                values.pop(val)

        schema['properties'] = properties

        return schema, values, properties

    def restore_defaults(self):
        """Launches dialog to confirm restore settings choice."""

        widget = ConfirmDialog(
            parent=self,
            text=trans._("Are you sure you want to restore default settings?"),
        )
        widget.valueChanged.connect(self._reset_widgets)
        widget.exec_()

    def _reset_widgets(self):
        """Deletes the widgets and rebuilds with defaults."""
        self.close()
        self._list.clear()

        for n in range(self._stack.count()):
            widget = self._stack.removeWidget(self._stack.currentWidget())
            del widget

        self.make_dialog()
        self._list.setCurrentRow(0)
        self.show()

    def on_click_ok(self):
        """Keeps the selected preferences saved to SETTINGS."""
        self.close()

    def on_click_cancel(self):
        """Restores the settings in place when dialog was launched."""
        # Need to check differences for each page.
        for n in range(self._stack.count()):
            # Must set the current row so that the proper list is updated
            # in check differences.
            self._list.setCurrentRow(n)
            page = self._list.currentItem().text().split(" ")[0].lower()
            # get new values for settings.  If they were changed from values at beginning
            # of preference dialog session, change them back.
            # Using the settings value seems to be the best way to get the checkboxes right
            # on the plugin call order widget.
            setting = SETTINGS.schemas()[page]
            schema, new_values, properties = self.get_page_dict(setting)
            self.check_differences(self._values_orig_dict[page], new_values)

        self._list.setCurrentRow(0)
        self.close()

    def add_page(self, schema, values):
        """Creates a new page for each section in dialog.

        Parameters
        ----------
        schema : dict
            Json schema including all information to build each page in the
            preferences dialog.
        values : dict
            Dictionary of current values set in preferences.
        """
        widget = self.build_page_dialog(schema, values)
        self._list.addItem(schema["title"])
        self._stack.addWidget(widget)

    def build_page_dialog(self, schema, values):
        """Builds the preferences widget using the json schema builder.

        Parameters
        ----------
        schema : dict
            Json schema including all information to build each page in the
            preferences dialog.
        values : dict
            Dictionary of current values set in preferences.
        """

        builder = WidgetBuilder()
        form = builder.create_form(schema, self.ui_schema)
        # set state values for widget
        form.widget.state = values
        form.widget.on_changed.connect(lambda d: self.check_differences(
            d,
            self._values_dict[schema["title"].lower()],
        ))

        return form

    def _values_changed(self, page, new_dict, old_dict):
        """Loops through each setting in a page to determine if it changed.

        Parameters
        ----------
        new_dict : dict
            Dict that has the most recent changes by user. Each key is a setting value
            and each item is the value.
        old_dict : dict
            Dict wtih values set at the begining of preferences dialog session.

        """
        for setting_name, value in new_dict.items():
            if value != old_dict[setting_name]:
                self._setting_changed_dict[page][setting_name] = value
            elif (value == old_dict[setting_name]
                  and setting_name in self._setting_changed_dict[page]):
                self._setting_changed_dict[page].pop(setting_name)

    def check_differences(self, new_dict, old_dict):
        """Changes settings in settings manager with changes from dialog.

        Parameters
        ----------
        new_dict : dict
            Dict that has the most recent changes by user. Each key is a setting parameter
            and each item is the value.
        old_dict : dict
            Dict wtih values set at the beginning of the preferences dialog session.
        """
        page = self._list.currentItem().text().split(" ")[0].lower()
        self._values_changed(page, new_dict, old_dict)
        different_values = self._setting_changed_dict[page]

        if len(different_values) > 0:
            # change the values in SETTINGS
            for setting_name, value in different_values.items():
                try:
                    setattr(SETTINGS._settings[page], setting_name, value)
                    self._values_dict[page] = new_dict
                except:  # noqa: E722
                    continue
示例#19
0
class FeaturesPlotWidget(CustomWidget):
    call_manage_settings = Signal()

    def __init__(self, *args, **kwargs):

        # define status images
        self.status_icons = {
            -2:
            QPixmap(
                os.path.join(os.path.dirname(__file__), 'icons',
                             'depth_status_delay.png')),
            -1:
            QPixmap(
                os.path.join(os.path.dirname(__file__), 'icons',
                             'depth_status_in_use.png')),
            1:
            QPixmap(
                os.path.join(os.path.dirname(__file__), 'icons',
                             'depth_status_done.png')),
            0:
            QPixmap(
                os.path.join(os.path.dirname(__file__), 'icons',
                             'depth_status_off.png')),
        }

        # Settings
        self.subject_settings = None
        self.procedure_settings = None
        self.depth_settings = None
        self.features_settings = None

        # Plot options
        self.plot_config = {}
        self.y_range = uVRANGE
        self.plot_stack = QStackedWidget()
        # generate a dict {chan_label: {Feature:[stack idx, latest_datum]}}
        self.stack_dict = {}

        # shared memory to display the currently monitored electrode
        self.monitored_channel_mem = QSharedMemory()
        self.monitored_channel_mem.setKey("MonitoredChannelMemory")
        self.monitored_channel_mem.attach(QSharedMemory.ReadOnly)

        # wrap up init
        super(FeaturesPlotWidget, self).__init__(*args, **kwargs)
        self.move(WINDOWDIMS_FEATURES[0], WINDOWDIMS_FEATURES[1])
        self.resize(WINDOWDIMS_FEATURES[2], WINDOWDIMS_FEATURES[3])
        self.setMaximumWidth(WINDOWDIMS_FEATURES[2])

        # initialize plots
        self.layout().addWidget(self.plot_stack)
        self.refresh_axes()  # Extra time on purpose.

        # Define and start processes
        # will only start processes when settings are received
        self.depth_wrapper = ProcessWrapper('Depth_Process')
        self.depth_process_running = False

        self.features_wrapper = ProcessWrapper('Features_Process')
        self.features_process_running = False

    def create_control_panel(self):
        # define Qt GUI elements
        layout = QHBoxLayout()

        layout_L = QVBoxLayout()
        layout_L1 = QHBoxLayout()

        # layout_L1.addSpacing(10)
        layout_L1.addWidget(
            QLabel("Electrode: ", alignment=Qt.AlignVCenter | Qt.AlignRight))
        # Channel selection
        self.chan_select = QComboBox()
        self.chan_select.addItem("None")
        self.chan_select.setMinimumWidth(70)
        self.chan_select.setEnabled(False)
        layout_L1.addWidget(self.chan_select)

        layout_L1.addSpacing(20)

        # features selection
        layout_L1.addWidget(
            QLabel("Feature set: ", alignment=Qt.AlignVCenter | Qt.AlignRight))
        self.feature_select = QComboBox()
        self.feature_select.setMinimumWidth(60)
        self.feature_select.addItems(['Raw', 'Mapping'])
        self.feature_select.setCurrentIndex(0)
        layout_L1.addWidget(self.feature_select)

        layout_L.addLayout(layout_L1)
        layout_L.addSpacing(5)

        layout_L2 = QHBoxLayout()
        layout_L2.addSpacing(10)
        layout_L2.addWidget(
            QLabel("+/- ", alignment=Qt.AlignVCenter | Qt.AlignRight))
        self.range_edit = QLineEdit("{:.2f}".format(uVRANGE))
        self.range_edit.setMaximumWidth(50)
        layout_L2.addWidget(self.range_edit)

        layout_L2.addSpacing(30)

        self.do_hp = QCheckBox('HP')
        self.do_hp.setChecked(True)
        layout_L2.addWidget(self.do_hp)

        layout_L2.addSpacing(30)

        self.sweep_control = QCheckBox("Match SweepGUI.")
        self.sweep_control.setChecked(True)
        self.sweep_control.setEnabled(True)
        layout_L2.addWidget(self.sweep_control)

        layout_L.addLayout(layout_L2)

        layout_R = QHBoxLayout()

        self.bt_refresh = QPushButton("Refresh")
        self.bt_refresh.setMaximumWidth(50)
        layout_R.addWidget(self.bt_refresh)

        layout_R.addSpacing(20)

        self.btn_settings = QPushButton("Settings")
        self.btn_settings.setMaximumWidth(50)
        layout_R.addWidget(self.btn_settings)

        layout_R.addSpacing(20)

        self.features_process_btn = QPushButton('Features')
        self.features_process_btn.setMaximumWidth(50)
        self.features_process_btn.setStyleSheet("QPushButton { color: white; "
                                                "background-color : red; "
                                                "border-color : red; "
                                                "border-width: 2px}")
        self.features_process_btn.clicked.connect(
            self.features_process_btn_callback)
        layout_R.addWidget(self.features_process_btn)

        layout_R.addSpacing(5)

        self.depth_process_btn = QPushButton('Record')
        self.depth_process_btn.setMaximumWidth(50)
        self.depth_process_btn.setStyleSheet("QPushButton { color: white; "
                                             "background-color : red; "
                                             "border-color : red; "
                                             "border-width: 2px}")
        self.depth_process_btn.clicked.connect(self.depth_process_btn_callback)
        layout_R.addWidget(self.depth_process_btn)

        layout_R.addSpacing(20)

        self.status_label = QLabel()
        self.status_label.setPixmap(self.status_icons[0])
        layout_R.addWidget(self.status_label)
        layout_R.addSpacing(10)

        layout.addLayout(layout_L)
        layout.addStretch()
        layout.addLayout(layout_R)

        # layout.addSpacing(10)
        self.layout().addLayout(layout)

        # callbacks
        self.btn_settings.clicked.connect(self.call_manage_settings.emit)
        self.chan_select.currentIndexChanged.connect(
            self.manage_feat_chan_select)
        self.feature_select.currentIndexChanged.connect(
            self.manage_feat_chan_select)
        self.sweep_control.clicked.connect(self.manage_sweep_control)
        self.range_edit.editingFinished.connect(self.manage_range_edit)
        self.bt_refresh.clicked.connect(self.manage_refresh)

    def depth_process_btn_callback(self):
        # kill
        if self.depth_process_running:
            self.manage_depth_process(False)
            self.manage_nsp(False)
        else:
            # if we terminate and re-start the processes, we need to re-enable the shared memory
            self.depth_wrapper.manage_shared_memory()

            # re-send the settings
            self.depth_wrapper.send_settings(self.depth_settings)

            # start nsp recording
            if self.manage_nsp(True) == 0:
                # re-start the worker
                self.manage_depth_process(True)

    def features_process_btn_callback(self):
        # kill
        if self.features_process_running:
            self.manage_feature_process(False)
        else:
            # if we terminate and re-start the processes, we need to re-enable the shared memory
            self.features_wrapper.manage_shared_memory()

            # re-send the settings
            self.features_wrapper.send_settings(self.features_settings)

            # re-start the worker
            self.manage_feature_process(True)

    # GUI Callbacks
    def manage_feat_chan_select(self):
        self.plot_stack.setCurrentIndex(
            self.stack_dict[self.chan_select.currentText()][
                self.feature_select.currentText()][0])

    def manage_sweep_control(self):
        if self.sweep_control.isChecked(
        ) and self.monitored_channel_mem.isAttached():
            self.chan_select.setEnabled(False)
            self.do_hp.setEnabled(False)
            self.range_edit.setEnabled(False)
            self.read_from_shared_memory()
        else:
            self.chan_select.setEnabled(True)
            self.do_hp.setEnabled(True)
            self.range_edit.setEnabled(True)

    def manage_range_edit(self):
        # need to do it like this because if we simply read the QLineEdit.text() on update calls, it breaks during
        # typing the new range values.
        self.y_range = float(self.range_edit.text())

    @staticmethod
    def parse_patient_name(full_name):
        # parse the subject information
        names = full_name.split(' ')
        m_name = ''
        m_idx = -1
        l_idx = -1
        for idx, n in enumerate(names):
            if all([x.isupper() for x in n]):
                m_idx = idx
                l_idx = idx + 1
                m_name = n
                break

        f_name = str.join(' ', names[:m_idx])
        l_name = str.join(' ', names[l_idx:])
        return f_name, m_name, l_name

    def manage_nsp(self, on_off):
        f_name, m_name, l_name = self.parse_patient_name(
            self.subject_settings['name'])
        file_info = {
            'filename':
            os.path.normpath(
                os.path.join(
                    BASEPATH, self.subject_settings['id'],
                    self.procedure_settings['date'].strftime('%m%d%y') + '_' +
                    self.subject_settings['id'] + '_' +
                    self.procedure_settings['target_name'] + '_' +
                    self.procedure_settings['recording_config'])),
            'comment':
            self.subject_settings['NSP_comment'],
            'patient_info': {
                'ID': self.subject_settings['id'],
                # if only single name, returned in l_name
                'firstname': f_name if f_name else l_name,
                'middlename': m_name,  # TODO: implement MiddleName
                'lastname': l_name,
                'DOBMonth': self.subject_settings['birthday'].month,
                'DOBDay': self.subject_settings['birthday'].day,
                'DOBYear': self.subject_settings['birthday'].year
            }
        }

        if not CbSdkConnection().is_connected:
            CbSdkConnection().connect()

        return CbSdkConnection().set_recording_state(on_off, file_info)

    def manage_depth_process(self, on_off):
        # start process
        if on_off and not self.depth_process_running:
            self.depth_wrapper.start_worker()
            self.depth_process_running = True
        else:
            self.depth_wrapper.kill_worker()
            self.depth_process_running = False

    def manage_feature_process(self, on_off):
        if on_off and not self.features_process_running:
            self.features_wrapper.start_worker()
            self.features_process_running = True
        else:
            self.features_wrapper.kill_worker()
            self.features_process_running = False

    def manage_refresh(self):
        self.plot_stack.widget(self.stack_dict[self.chan_select.currentText()][
            self.feature_select.currentText()][0]).clear_plot()
        self.stack_dict[self.chan_select.currentText()][
            self.feature_select.currentText()][1] = 0

    def process_settings(self, sub_sett, proc_sett, depth_sett, feat_sett):
        self.subject_settings = dict(sub_sett)
        self.procedure_settings = dict(proc_sett)
        self.depth_settings = dict(depth_sett)
        self.features_settings = dict(feat_sett)

        # validate that we have some data in the electrode_settings. If the NSP is not connected we will have
        # to load the channel names from the DB. Also we want to keep the FeaturesGUI unaware of the DB channels.
        if len(self.depth_settings['electrode_settings']) == 0:
            self.depth_settings['electrode_settings'] = {}
            for lbl in DBWrapper().list_channel_labels():
                self.depth_settings['electrode_settings'][lbl] = DEPTHSETTINGS
            CbSdkConnection().is_simulating = True

        # set new features
        self.feature_select.setCurrentIndex(0)  # Raw
        while self.feature_select.count() > 2:  # Raw and Mapping
            self.feature_select.removeItem(2)
        self.feature_select.addItems(self.features_settings['features'].keys())

        # set new channels
        self.chan_select.setCurrentIndex(0)  # None
        while self.chan_select.count() > 1:
            self.chan_select.removeItem(1)
        self.chan_select.addItems(
            self.depth_settings['electrode_settings'].keys())

        # clear and update stacked widget
        to_delete = [
            self.plot_stack.widget(x) for x in range(self.plot_stack.count())
        ]
        for wid in to_delete:
            self.plot_stack.removeWidget(wid)
            wid.deleteLater()
            wid = None
        self.stack_dict = {}
        self.create_plots()

        self.depth_wrapper.send_settings(self.depth_settings)
        self.features_wrapper.send_settings(self.features_settings)

        if not self.features_process_running:
            self.manage_feature_process(True)

        # self.clear()
        self.read_from_shared_memory()

    def create_plots(self, theme='dark', **kwargs):
        # Collect PlotWidget configuration
        self.plot_config['theme'] = theme
        self.plot_config['color_iterator'] = -1
        self.plot_config['x_range'] = XRANGE_FEATURES
        self.plot_config['y_range'] = uVRANGE
        self.plot_config['do_hp'] = True

        labels = []
        for ii in range(0, self.chan_select.count()):
            labels.append(self.chan_select.itemText(ii))
        # labels.extend(self.channel_labels)

        features = []
        for ii in range(0, self.feature_select.count()):
            features.append(self.feature_select.itemText(ii))

        stack_idx = 0
        for lbl_idx, lbl in enumerate(labels):
            self.stack_dict[lbl] = {}
            self.plot_config['color_iterator'] = lbl_idx - 1
            self.plot_config['title'] = lbl
            for feat in features:
                self.stack_dict[lbl][feat] = [stack_idx, 0]
                # TODO: not hard-coding??
                if feat == 'Raw':
                    self.plot_stack.addWidget(RawPlots(dict(self.plot_config)))
                elif feat == 'Mapping':
                    self.plot_stack.addWidget(
                        MappingPlots(dict(self.plot_config)))
                elif feat == 'STN':
                    self.plot_stack.addWidget(STNPlots(dict(self.plot_config)))
                elif feat == 'LFP':
                    self.plot_stack.addWidget(LFPPlots(dict(self.plot_config)))
                elif feat == 'Spikes':
                    self.plot_stack.addWidget(
                        SpikePlots(dict(self.plot_config)))
                else:
                    self.plot_stack.addWidget(
                        NullPlotWidget(dict(self.plot_config)))
                stack_idx += 1

        self.plot_stack.setCurrentIndex(0)

    def refresh_axes(self):
        pass

    def clear(self):
        # set the current datum of all stacks to 0
        for lbl in self.stack_dict:
            for feat in self.stack_dict[lbl]:
                self.stack_dict[lbl][feat][1] = 0
                self.plot_stack.widget(
                    self.stack_dict[lbl][feat][0]).clear_plot()

    def update(self):
        # Depth process
        output = self.depth_wrapper.worker_status()
        self.status_label.setPixmap(self.status_icons[output])

        if self.depth_wrapper.is_running():
            self.depth_process_btn.setStyleSheet("QPushButton { color: white; "
                                                 "background-color : green; "
                                                 "border-color : green; "
                                                 "border-width: 2px}")
        else:
            self.depth_process_running = False
            self.depth_process_btn.setStyleSheet("QPushButton { color: white; "
                                                 "background-color : red; "
                                                 "border-color : red; "
                                                 "border-width: 2px}")

        if self.features_wrapper.is_running():
            self.features_process_btn.setStyleSheet(
                "QPushButton { color: white; "
                "background-color : green; "
                "border-color : green; "
                "border-width: 2px}")
        else:
            self.features_process_running = False
            self.features_process_btn.setStyleSheet(
                "QPushButton { color: white; "
                "background-color : red; "
                "border-color : red; "
                "border-width: 2px}")

        if self.sweep_control.isChecked():
            self.read_from_shared_memory()

        # features plot
        curr_chan_lbl = self.chan_select.currentText()
        if curr_chan_lbl != 'None':
            curr_feat = self.feature_select.currentText()
            do_hp = self.do_hp.isChecked()

            if do_hp != self.plot_stack.currentWidget().plot_config['do_hp'] or \
                    self.y_range != self.plot_stack.currentWidget().plot_config['y_range']:
                self.plot_stack.currentWidget().clear_plot()
                self.stack_dict[curr_chan_lbl][curr_feat][1] = 0
                self.plot_stack.currentWidget().plot_config['do_hp'] = do_hp
                self.plot_stack.currentWidget(
                ).plot_config['y_range'] = self.y_range

            curr_datum = self.stack_dict[curr_chan_lbl][curr_feat][1]
            if curr_feat == 'Raw':
                all_data = DBWrapper().load_depth_data(chan_lbl=curr_chan_lbl,
                                                       gt=curr_datum,
                                                       do_hp=do_hp,
                                                       return_uV=True)
            elif curr_feat == 'Mapping':
                all_data = DBWrapper().load_mapping_response(
                    chan_lbl=curr_chan_lbl, gt=curr_datum)
            else:
                all_data = DBWrapper().load_features_data(
                    category=curr_feat, chan_lbl=curr_chan_lbl, gt=curr_datum)
            if all_data:
                self.plot_stack.currentWidget().update_plot(dict(all_data))
                self.stack_dict[curr_chan_lbl][curr_feat][1] = max(
                    all_data.keys())

    def kill_processes(self):
        self.manage_depth_process(False)
        self.manage_feature_process(False)

    def read_from_shared_memory(self):
        if self.monitored_channel_mem.isAttached():
            self.monitored_channel_mem.lock()
            settings = np.frombuffer(self.monitored_channel_mem.data(),
                                     dtype=np.float)[-3:]
            self.chan_select.setCurrentIndex(int(settings[0]))
            self.range_edit.setText(str(settings[1]))
            self.manage_range_edit()
            self.do_hp.setChecked(bool(settings[2]))
            self.monitored_channel_mem.unlock()
        else:
            self.monitored_channel_mem.attach()
            # self.sweep_control.setChecked(False)
            self.manage_sweep_control()
示例#20
0
class PreferencesDialog(QDialog):
    """Preferences Dialog for Napari user settings."""

    valueChanged = Signal()

    ui_schema = {
        "call_order": {
            "ui:widget": "plugins"
        },
        "highlight_thickness": {
            "ui:widget": "highlight"
        },
    }

    resized = Signal(QSize)
    closed = Signal()

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

        self._list = QListWidget(self)
        self._stack = QStackedWidget(self)

        self._list.setObjectName("Preferences")

        # Set up buttons
        self._button_cancel = QPushButton(trans._("Cancel"))
        self._button_ok = QPushButton(trans._("OK"))
        self._default_restore = QPushButton(trans._("Restore defaults"))

        # Setup
        self.setWindowTitle(trans._("Preferences"))

        # Layout
        left_layout = QVBoxLayout()
        left_layout.addWidget(self._list)
        left_layout.addStretch()
        left_layout.addWidget(self._default_restore)
        left_layout.addWidget(self._button_cancel)
        left_layout.addWidget(self._button_ok)

        main_layout = QHBoxLayout()
        main_layout.addLayout(left_layout, 1)
        main_layout.addWidget(self._stack, 3)

        self.setLayout(main_layout)

        # Signals

        self._list.currentRowChanged.connect(
            lambda index: self._stack.setCurrentIndex(index))
        self._button_cancel.clicked.connect(self.on_click_cancel)
        self._button_ok.clicked.connect(self.on_click_ok)
        self._default_restore.clicked.connect(self.restore_defaults)

        # Make widget

        self.make_dialog()
        self._list.setCurrentRow(0)

    def _restart_dialog(self, event=None, extra_str=""):
        """Displays the dialog informing user a restart is required.

        Paramters
        ---------
        event : Event
        extra_str : str
            Extra information to add to the message about needing a restart.
        """

        text_str = trans._(
            "napari requires a restart for image rendering changes to apply.")

        widget = ResetNapariInfoDialog(
            parent=self,
            text=text_str,
        )
        widget.exec_()

    def closeEvent(self, event):
        """Override to emit signal."""
        self.closed.emit()
        super().closeEvent(event)

    def reject(self):
        """Override to handle Escape."""
        super().reject()
        self.close()

    def resizeEvent(self, event):
        """Override to emit signal."""
        self.resized.emit(event.size())
        super().resizeEvent(event)

    def make_dialog(self):
        """Removes settings not to be exposed to user and creates dialog pages."""
        settings = get_settings()
        # Because there are multiple pages, need to keep a dictionary of values dicts.
        # One set of keywords are for each page, then in each entry for a page, there are dicts
        # of setting and its value.
        self._values_orig_dict = {}
        self._values_dict = {}
        self._setting_changed_dict = {}

        for page, setting in settings.schemas().items():
            schema, values, properties = self.get_page_dict(setting)

            self._setting_changed_dict[page] = {}
            self._values_orig_dict[page] = values
            self._values_dict[page] = values

            # Only add pages if there are any properties to add.
            if properties:
                self.add_page(schema, values)

    def get_page_dict(self, setting):
        """Provides the schema, set of values for each setting, and the properties
        for each setting.

        Parameters
        ----------
        setting : dict
            Dictionary of settings for a page within the settings manager.

        Returns
        -------
        schema : dict
            Json schema of the setting page.
        values : dict
            Dictionary of values currently set for each parameter in the settings.
        properties : dict
            Dictionary of properties within the json schema.

        """
        schema = json.loads(setting['json_schema'])

        # Resolve allOf references
        definitions = schema.get("definitions", {})
        if definitions:
            for key, data in schema["properties"].items():
                if "allOf" in data:
                    allof = data["allOf"]
                    allof = [d["$ref"].rsplit("/")[-1] for d in allof]
                    for definition in allof:
                        local_def = definitions[definition]
                        schema["properties"][key]["enum"] = local_def["enum"]
                        schema["properties"][key]["type"] = "string"

        # Need to remove certain properties that will not be displayed on the GUI
        properties = schema.pop('properties')
        model = setting['model']
        values = model.dict()
        napari_config = getattr(model, "NapariConfig", None)
        if napari_config is not None:
            for val in napari_config.preferences_exclude:
                properties.pop(val)
                values.pop(val)

        schema['properties'] = properties

        return schema, values, properties

    def restore_defaults(self):
        """Launches dialog to confirm restore settings choice."""
        self._reset_dialog = ConfirmDialog(
            parent=self,
            text=trans._("Are you sure you want to restore default settings?"),
        )
        self._reset_dialog.valueChanged.connect(self._reset_widgets)
        self._reset_dialog.exec_()

    def _reset_widgets(self):
        """Deletes the widgets and rebuilds with defaults."""
        self.close()
        self.valueChanged.emit()
        self._list.clear()

        for n in range(self._stack.count()):
            widget = self._stack.removeWidget(self._stack.currentWidget())
            del widget

        self.make_dialog()
        self._list.setCurrentRow(0)
        self.show()

    def on_click_ok(self):
        """Keeps the selected preferences saved to settings."""
        self.close()

    def on_click_cancel(self):
        """Restores the settings in place when dialog was launched."""
        # Need to check differences for each page.
        settings = get_settings()
        for n in range(self._stack.count()):
            # Must set the current row so that the proper list is updated
            # in check differences.
            self._list.setCurrentRow(n)
            page = self._list.currentItem().text().split(" ")[0].lower()
            # get new values for settings.  If they were changed from values at beginning
            # of preference dialog session, change them back.
            # Using the settings value seems to be the best way to get the checkboxes right
            # on the plugin call order widget.
            setting = settings.schemas()[page]
            schema, new_values, properties = self.get_page_dict(setting)
            self.check_differences(self._values_orig_dict[page], new_values)

        self._list.setCurrentRow(0)
        self.close()

    def add_page(self, schema, values):
        """Creates a new page for each section in dialog.

        Parameters
        ----------
        schema : dict
            Json schema including all information to build each page in the
            preferences dialog.
        values : dict
            Dictionary of current values set in preferences.
        """
        widget = self.build_page_dialog(schema, values)
        self._list.addItem(schema["title"])
        self._stack.addWidget(widget)

    def build_page_dialog(self, schema, values):
        """Builds the preferences widget using the json schema builder.

        Parameters
        ----------
        schema : dict
            Json schema including all information to build each page in the
            preferences dialog.
        values : dict
            Dictionary of current values set in preferences.
        """
        settings = get_settings()
        builder = WidgetBuilder()
        form = builder.create_form(schema, self.ui_schema)

        # Disable widgets that loaded settings from environment variables
        section = schema["section"]
        form_layout = form.widget.layout()
        for row in range(form.widget.layout().rowCount()):
            widget = form_layout.itemAt(row, form_layout.FieldRole).widget()
            name = widget._name
            disable = bool(
                settings._env_settings.get(section, {}).get(name, None))
            widget.setDisabled(disable)
            try:
                widget.opacity.setOpacity(0.3 if disable else 1)
            except AttributeError:
                # some widgets may not have opacity (such as the QtPluginSorter)
                pass

        # set state values for widget
        form.widget.state = values

        if section == 'experimental':
            # need to disable async if octree is enabled.
            if values['octree'] is True:
                form = self._disable_async(form, values)

        form.widget.on_changed.connect(lambda d: self.check_differences(
            d,
            self._values_dict[schema["title"].lower()],
        ))

        return form

    def _disable_async(self, form, values, disable=True, state=True):
        """Disable async if octree is True."""
        settings = get_settings()
        # need to make sure that if async_ is an environment setting, that we don't
        # enable it here.
        if (settings._env_settings['experimental'].get('async_', None)
                is not None):
            disable = True

        idx = list(values.keys()).index('async_')
        form_layout = form.widget.layout()
        widget = form_layout.itemAt(idx, form_layout.FieldRole).widget()
        widget.opacity.setOpacity(0.3 if disable else 1)
        widget.setDisabled(disable)

        return form

    def _values_changed(self, page, new_dict, old_dict):
        """Loops through each setting in a page to determine if it changed.

        Parameters
        ----------
        new_dict : dict
            Dict that has the most recent changes by user. Each key is a setting value
            and each item is the value.
        old_dict : dict
            Dict wtih values set at the begining of preferences dialog session.

        """
        for setting_name, value in new_dict.items():
            if value != old_dict[setting_name]:
                self._setting_changed_dict[page][setting_name] = value
            elif (value == old_dict[setting_name]
                  and setting_name in self._setting_changed_dict[page]):
                self._setting_changed_dict[page].pop(setting_name)

    def set_current_index(self, index: int):
        """
        Set the current page on the preferences by index.

        Parameters
        ----------
        index : int
            Index of page to set as current one.
        """
        self._list.setCurrentRow(index)

    def check_differences(self, new_dict, old_dict):
        """Changes settings in settings manager with changes from dialog.

        Parameters
        ----------
        new_dict : dict
            Dict that has the most recent changes by user. Each key is a setting parameter
            and each item is the value.
        old_dict : dict
            Dict wtih values set at the beginning of the preferences dialog session.
        """
        settings = get_settings()
        page = self._list.currentItem().text().split(" ")[0].lower()
        self._values_changed(page, new_dict, old_dict)
        different_values = self._setting_changed_dict[page]

        if len(different_values) > 0:
            # change the values in settings
            for setting_name, value in different_values.items():
                try:
                    setattr(settings._settings[page], setting_name, value)
                    self._values_dict[page] = new_dict

                    if page == 'experimental':

                        if setting_name == 'octree':

                            # disable/enable async checkbox
                            widget = self._stack.currentWidget()
                            cstate = True if value is True else False
                            self._disable_async(widget,
                                                new_dict,
                                                disable=cstate)

                            # need to inform user that napari restart needed.
                            self._restart_dialog()

                        elif setting_name == 'async_':
                            # need to inform user that napari restart needed.
                            self._restart_dialog()

                except:  # noqa: E722
                    continue
示例#21
0
class VariableExplorer(QWidget, SpyderPluginMixin):
    """
    Variable Explorer Plugin
    """
    CONF_SECTION = 'variable_explorer'
    CONFIGWIDGET_CLASS = VariableExplorerConfigPage
    sig_option_changed = Signal(str, object)

    def __init__(self, parent):
        QWidget.__init__(self, parent)
        SpyderPluginMixin.__init__(self, parent)

        # Widgets
        self.stack = QStackedWidget(self)
        self.shellwidgets = {}

        # Layout
        layout = QVBoxLayout()
        layout.addWidget(self.stack)
        self.setLayout(layout)

        # Initialize plugin
        self.initialize_plugin()

    @staticmethod
    def get_settings():
        """
        Return Variable Explorer settings dictionary
        (i.e. namespace browser settings according to Spyder's configuration file)
        """
        settings = {}
#        CONF.load_from_ini() # necessary only when called from another process
        for name in REMOTE_SETTINGS:
            settings[name] = CONF.get(VariableExplorer.CONF_SECTION, name)
        return settings

    # ----- Stack accesors ----------------------------------------------------
    def set_current_widget(self, nsb):
        self.stack.setCurrentWidget(nsb)

    def current_widget(self):
        return self.stack.currentWidget()

    def count(self):
        return self.stack.count()

    def remove_widget(self, nsb):
        self.stack.removeWidget(nsb)

    def add_widget(self, nsb):
        self.stack.addWidget(nsb)

    # ----- Public API --------------------------------------------------------
    def add_shellwidget(self, shellwidget):
        shellwidget_id = id(shellwidget)
        # Add shell only once: this method may be called two times in a row
        # by the External console plugin (dev. convenience)
        from spyder.widgets.externalshell import systemshell
        if isinstance(shellwidget, systemshell.ExternalSystemShell):
            return
        if shellwidget_id not in self.shellwidgets:
            nsb = NamespaceBrowser(self)
            nsb.set_shellwidget(shellwidget)
            nsb.setup(**VariableExplorer.get_settings())
            nsb.sig_option_changed.connect(self.sig_option_changed.emit)
            self.add_widget(nsb)
            self.shellwidgets[shellwidget_id] = nsb
            self.set_shellwidget_from_id(shellwidget_id)
            return nsb
        
    def remove_shellwidget(self, shellwidget_id):
        # If shellwidget_id is not in self.shellwidgets, it simply means
        # that shell was not a Python-based console (it was a terminal)
        if shellwidget_id in self.shellwidgets:
            nsb = self.shellwidgets.pop(shellwidget_id)
            self.remove_widget(nsb)
            nsb.close()
    
    def set_shellwidget_from_id(self, shellwidget_id):
        if shellwidget_id in self.shellwidgets:
            nsb = self.shellwidgets[shellwidget_id]
            self.set_current_widget(nsb)
            if self.isvisible:
                nsb.visibility_changed(True)

    def import_data(self, fname):
        """Import data in current namespace"""
        if self.count():
            nsb = self.current_widget()
            nsb.refresh_table()
            nsb.import_data(filenames=fname)
            if self.dockwidget and not self.ismaximized:
                self.dockwidget.setVisible(True)
                self.dockwidget.raise_()

    #------ SpyderPluginMixin API ---------------------------------------------
    def visibility_changed(self, enable):
        """DockWidget visibility has changed"""
        SpyderPluginMixin.visibility_changed(self, enable)
        for nsb in list(self.shellwidgets.values()):
            nsb.visibility_changed(enable and nsb is self.current_widget())
    
    #------ SpyderPluginWidget API ---------------------------------------------
    def get_plugin_title(self):
        """Return widget title"""
        return _('Variable explorer')

    def get_plugin_icon(self):
        """Return plugin icon"""
        return ima.icon('dictedit')
    
    def get_focus_widget(self):
        """
        Return the widget to give focus to when
        this plugin's dockwidget is raised on top-level
        """
        return self.current_widget()
        
    def closing_plugin(self, cancelable=False):
        """Perform actions before parent main window is closed"""
        return True
        
    def refresh_plugin(self):
        """Refresh widget"""
        pass
    
    def get_plugin_actions(self):
        """Return a list of actions related to plugin"""
        return []
    
    def register_plugin(self):
        """Register plugin in Spyder's main window"""
        self.main.extconsole.set_variableexplorer(self)
        self.main.add_dockwidget(self)
        
    def apply_plugin_settings(self, options):
        """Apply configuration file's plugin settings"""
        for nsb in list(self.shellwidgets.values()):
            nsb.setup(**VariableExplorer.get_settings())
        ar_timeout = self.get_option('autorefresh/timeout')
        for shellwidget in self.main.extconsole.shellwidgets:
            shellwidget.set_autorefresh_timeout(ar_timeout)
示例#22
0
class VariableExplorer(QWidget, SpyderPluginMixin):
    """
    Variable Explorer Plugin
    """
    CONF_SECTION = 'variable_explorer'
    CONFIGWIDGET_CLASS = VariableExplorerConfigPage
    sig_option_changed = Signal(str, object)

    def __init__(self, parent):
        QWidget.__init__(self, parent)
        SpyderPluginMixin.__init__(self, parent)

        # Widgets
        self.stack = QStackedWidget(self)
        self.shellwidgets = {}

        # Layout
        layout = QVBoxLayout()
        layout.addWidget(self.stack)
        self.setLayout(layout)

        # Initialize plugin
        self.initialize_plugin()

    @staticmethod
    def get_settings():
        """
        Return Variable Explorer settings dictionary
        (i.e. namespace browser settings according to Spyder's configuration file)
        """
        settings = {}
        #        CONF.load_from_ini() # necessary only when called from another process
        for name in REMOTE_SETTINGS:
            settings[name] = CONF.get(VariableExplorer.CONF_SECTION, name)
        return settings

    # ----- Stack accesors ----------------------------------------------------
    def set_current_widget(self, nsb):
        self.stack.setCurrentWidget(nsb)

    def current_widget(self):
        return self.stack.currentWidget()

    def count(self):
        return self.stack.count()

    def remove_widget(self, nsb):
        self.stack.removeWidget(nsb)

    def add_widget(self, nsb):
        self.stack.addWidget(nsb)

    # ----- Public API --------------------------------------------------------
    def add_shellwidget(self, shellwidget):
        shellwidget_id = id(shellwidget)
        # Add shell only once: this method may be called two times in a row
        # by the External console plugin (dev. convenience)
        from spyderlib.widgets.externalshell import systemshell
        if isinstance(shellwidget, systemshell.ExternalSystemShell):
            return
        if shellwidget_id not in self.shellwidgets:
            nsb = NamespaceBrowser(self)
            nsb.set_shellwidget(shellwidget)
            nsb.setup(**VariableExplorer.get_settings())
            nsb.sig_option_changed.connect(self.sig_option_changed.emit)
            self.add_widget(nsb)
            self.shellwidgets[shellwidget_id] = nsb
            self.set_shellwidget_from_id(shellwidget_id)
            return nsb

    def remove_shellwidget(self, shellwidget_id):
        # If shellwidget_id is not in self.shellwidgets, it simply means
        # that shell was not a Python-based console (it was a terminal)
        if shellwidget_id in self.shellwidgets:
            nsb = self.shellwidgets.pop(shellwidget_id)
            self.remove_widget(nsb)
            nsb.close()

    def set_shellwidget_from_id(self, shellwidget_id):
        if shellwidget_id in self.shellwidgets:
            nsb = self.shellwidgets[shellwidget_id]
            self.set_current_widget(nsb)
            if self.isvisible:
                nsb.visibility_changed(True)

    def import_data(self, fname):
        """Import data in current namespace"""
        if self.count():
            nsb = self.current_widget()
            nsb.refresh_table()
            nsb.import_data(filename=fname)
            if self.dockwidget and not self.ismaximized:
                self.dockwidget.setVisible(True)
                self.dockwidget.raise_()

    #------ SpyderPluginMixin API ---------------------------------------------
    def visibility_changed(self, enable):
        """DockWidget visibility has changed"""
        SpyderPluginMixin.visibility_changed(self, enable)
        for nsb in list(self.shellwidgets.values()):
            nsb.visibility_changed(enable and nsb is self.current_widget())

    #------ SpyderPluginWidget API ---------------------------------------------
    def get_plugin_title(self):
        """Return widget title"""
        return _('Variable explorer')

    def get_plugin_icon(self):
        """Return plugin icon"""
        return ima.icon('dictedit')

    def get_focus_widget(self):
        """
        Return the widget to give focus to when
        this plugin's dockwidget is raised on top-level
        """
        return self.current_widget()

    def closing_plugin(self, cancelable=False):
        """Perform actions before parent main window is closed"""
        return True

    def refresh_plugin(self):
        """Refresh widget"""
        pass

    def get_plugin_actions(self):
        """Return a list of actions related to plugin"""
        return []

    def register_plugin(self):
        """Register plugin in Spyder's main window"""
        self.main.extconsole.set_variableexplorer(self)
        self.main.add_dockwidget(self)

    def apply_plugin_settings(self, options):
        """Apply configuration file's plugin settings"""
        for nsb in list(self.shellwidgets.values()):
            nsb.setup(**VariableExplorer.get_settings())
        ar_timeout = self.get_option('autorefresh/timeout')
        for shellwidget in self.main.extconsole.shellwidgets:
            shellwidget.set_autorefresh_timeout(ar_timeout)
示例#23
0
class VariableExplorer(QWidget, SpyderPluginMixin):
    """
    Variable Explorer Plugin
    """
    CONF_SECTION = 'variable_explorer'
    CONFIGWIDGET_CLASS = VariableExplorerConfigPage
    sig_option_changed = Signal(str, object)

    def __init__(self, parent):
        QWidget.__init__(self, parent)
        SpyderPluginMixin.__init__(self, parent)

        # Widgets
        self.stack = QStackedWidget(self)
        self.shellwidgets = {}

        # Layout
        layout = QVBoxLayout()
        layout.addWidget(self.stack)
        self.setLayout(layout)

        # Initialize plugin
        self.initialize_plugin()

    def get_settings(self):
        """
        Retrieve all Variable Explorer configuration settings.
        
        Specifically, return the settings in CONF_SECTION with keys in 
        REMOTE_SETTINGS, and the setting 'dataframe_format'.
        
        Returns:
            dict: settings
        """
        settings = {}
        for name in REMOTE_SETTINGS:
            settings[name] = self.get_option(name)

        # dataframe_format is stored without percent sign in config
        # to avoid interference with ConfigParser's interpolation
        name = 'dataframe_format'
        settings[name] = '%{0}'.format(self.get_option(name))
        return settings

    @Slot(str, object)
    def change_option(self, option_name, new_value):
        """
        Change a config option.

        This function is called if sig_option_changed is received. If the
        option changed is the dataframe format, then the leading '%' character
        is stripped (because it can't be stored in the user config). Then,
        the signal is emitted again, so that the new value is saved in the
        user config.
        """
        if option_name == 'dataframe_format':
            assert new_value.startswith('%')
            new_value = new_value[1:]
        self.sig_option_changed.emit(option_name, new_value)

    # ----- Stack accesors ----------------------------------------------------
    def set_current_widget(self, nsb):
        self.stack.setCurrentWidget(nsb)

    def current_widget(self):
        return self.stack.currentWidget()

    def count(self):
        return self.stack.count()

    def remove_widget(self, nsb):
        self.stack.removeWidget(nsb)

    def add_widget(self, nsb):
        self.stack.addWidget(nsb)

    # ----- Public API --------------------------------------------------------
    def add_shellwidget(self, shellwidget):
        """
        Register shell with variable explorer.

        This function opens a new NamespaceBrowser for browsing the variables
        in the shell.
        """
        shellwidget_id = id(shellwidget)
        if shellwidget_id not in self.shellwidgets:
            nsb = NamespaceBrowser(self)
            nsb.set_shellwidget(shellwidget)
            nsb.setup(**self.get_settings())
            nsb.sig_option_changed.connect(self.change_option)
            self.add_widget(nsb)
            self.shellwidgets[shellwidget_id] = nsb
            self.set_shellwidget_from_id(shellwidget_id)
            return nsb

    def remove_shellwidget(self, shellwidget_id):
        # If shellwidget_id is not in self.shellwidgets, it simply means
        # that shell was not a Python-based console (it was a terminal)
        if shellwidget_id in self.shellwidgets:
            nsb = self.shellwidgets.pop(shellwidget_id)
            self.remove_widget(nsb)
            nsb.close()

    def set_shellwidget_from_id(self, shellwidget_id):
        if shellwidget_id in self.shellwidgets:
            nsb = self.shellwidgets[shellwidget_id]
            self.set_current_widget(nsb)

    def import_data(self, fname):
        """Import data in current namespace"""
        if self.count():
            nsb = self.current_widget()
            nsb.refresh_table()
            nsb.import_data(filenames=fname)
            if self.dockwidget and not self.ismaximized:
                self.dockwidget.setVisible(True)
                self.dockwidget.raise_()

    #------ SpyderPluginWidget API ---------------------------------------------
    def get_plugin_title(self):
        """Return widget title"""
        return _('Variable explorer')

    def get_plugin_icon(self):
        """Return plugin icon"""
        return ima.icon('dictedit')

    def get_focus_widget(self):
        """
        Return the widget to give focus to when
        this plugin's dockwidget is raised on top-level
        """
        return self.current_widget()

    def closing_plugin(self, cancelable=False):
        """Perform actions before parent main window is closed"""
        return True

    def refresh_plugin(self):
        """Refresh widget"""
        pass

    def get_plugin_actions(self):
        """Return a list of actions related to plugin"""
        return []

    def register_plugin(self):
        """Register plugin in Spyder's main window"""
        self.main.add_dockwidget(self)

    def apply_plugin_settings(self, options):
        """Apply configuration file's plugin settings"""
        for nsb in list(self.shellwidgets.values()):
            nsb.setup(**self.get_settings())
示例#24
0
class ConfigDialog(QDialog):
    """Spyder configuration ('Preferences') dialog box"""

    # Signals
    check_settings = Signal()
    size_change = Signal(QSize)

    def __init__(self, parent=None):
        QDialog.__init__(self, parent)

        self.main = parent

        # Widgets
        self.pages_widget = QStackedWidget()
        self.pages_widget.setMinimumWidth(600)
        self.contents_widget = QListWidget()
        self.button_reset = QPushButton(_('Reset to defaults'))

        bbox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Apply
                                | QDialogButtonBox.Cancel)
        self.apply_btn = bbox.button(QDialogButtonBox.Apply)
        self.ok_btn = bbox.button(QDialogButtonBox.Ok)

        # Widgets setup
        # Destroying the C++ object right after closing the dialog box,
        # otherwise it may be garbage-collected in another QThread
        # (e.g. the editor's analysis thread in Spyder), thus leading to
        # a segmentation fault on UNIX or an application crash on Windows
        self.setAttribute(Qt.WA_DeleteOnClose)
        self.setWindowTitle(_('Preferences'))
        self.setWindowIcon(ima.icon('configure'))
        self.contents_widget.setMovement(QListView.Static)
        self.contents_widget.setSpacing(1)
        self.contents_widget.setCurrentRow(0)
        self.contents_widget.setMinimumWidth(220)
        self.contents_widget.setMinimumHeight(400)

        # Layout
        hsplitter = QSplitter()
        hsplitter.addWidget(self.contents_widget)
        hsplitter.addWidget(self.pages_widget)
        hsplitter.setStretchFactor(0, 1)
        hsplitter.setStretchFactor(1, 2)

        btnlayout = QHBoxLayout()
        btnlayout.addWidget(self.button_reset)
        btnlayout.addStretch(1)
        btnlayout.addWidget(bbox)

        vlayout = QVBoxLayout()
        vlayout.addWidget(hsplitter)
        vlayout.addLayout(btnlayout)

        self.setLayout(vlayout)

        # Signals and slots
        if self.main:
            self.button_reset.clicked.connect(self.main.reset_spyder)
        self.pages_widget.currentChanged.connect(self.current_page_changed)
        self.contents_widget.currentRowChanged.connect(
            self.pages_widget.setCurrentIndex)
        bbox.accepted.connect(self.accept)
        bbox.rejected.connect(self.reject)
        bbox.clicked.connect(self.button_clicked)

        # Ensures that the config is present on spyder first run
        CONF.set('main', 'interface_language', load_lang_conf())

    def get_current_index(self):
        """Return current page index"""
        return self.contents_widget.currentRow()

    def set_current_index(self, index):
        """Set current page index"""
        self.contents_widget.setCurrentRow(index)

    def get_page(self, index=None):
        """Return page widget"""
        if index is None:
            widget = self.pages_widget.currentWidget()
        else:
            widget = self.pages_widget.widget(index)

        if widget:
            return widget.widget()

    def get_index_by_name(self, name):
        """Return page index by CONF_SECTION name."""
        for idx in range(self.pages_widget.count()):
            widget = self.pages_widget.widget(idx)
            widget = widget.widget()
            if widget.CONF_SECTION == name:
                return idx
        else:
            return None

    @Slot()
    def accept(self):
        """Reimplement Qt method"""
        for index in range(self.pages_widget.count()):
            configpage = self.get_page(index)
            if not configpage.is_valid():
                return
            configpage.apply_changes()
        QDialog.accept(self)

    def button_clicked(self, button):
        if button is self.apply_btn:
            # Apply button was clicked
            configpage = self.get_page()
            if not configpage.is_valid():
                return
            configpage.apply_changes()

    def current_page_changed(self, index):
        widget = self.get_page(index)
        self.apply_btn.setVisible(widget.apply_callback is not None)
        self.apply_btn.setEnabled(widget.is_modified)

    def add_page(self, widget):
        self.check_settings.connect(widget.check_settings)
        widget.show_this_page.connect(lambda row=self.contents_widget.count():
                                      self.contents_widget.setCurrentRow(row))
        widget.apply_button_enabled.connect(self.apply_btn.setEnabled)
        scrollarea = QScrollArea(self)
        scrollarea.setWidgetResizable(True)
        scrollarea.setWidget(widget)
        self.pages_widget.addWidget(scrollarea)
        item = QListWidgetItem(self.contents_widget)
        try:
            item.setIcon(widget.get_icon())
        except TypeError:
            pass
        item.setText(widget.get_name())
        item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
        item.setSizeHint(QSize(0, 25))

    def check_all_settings(self):
        """This method is called to check all configuration page settings
        after configuration dialog has been shown"""
        self.check_settings.emit()

    def resizeEvent(self, event):
        """
        Reimplement Qt method to be able to save the widget's size from the
        main application
        """
        QDialog.resizeEvent(self, event)
        self.size_change.emit(self.size())
示例#25
0
class ShellConnectMainWidget(PluginMainWidget):
    """
    Main widget to use in a plugin that shows console-specific content.

    Notes
    -----
    * This is composed of a QStackedWidget to stack widgets associated to each
      shell widget in the console and only show one of them at a time.
    * The current widget in the stack will display the content associated to
      the console with focus.
    """
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        # Widgets
        self._stack = QStackedWidget(self)
        self._shellwidgets = {}

        # Layout
        layout = QVBoxLayout()
        layout.setSpacing(0)
        layout.setContentsMargins(0, 0, 0, 0)
        layout.addWidget(self._stack)
        self.setLayout(layout)

    def update_style(self):
        self._stack.setStyleSheet("QStackedWidget {padding: 0px; border: 0px}")

    # ---- Stack accesors
    # ------------------------------------------------------------------------
    def count(self):
        """
        Return the number of widgets in the stack.

        Returns
        -------
        int
            The number of widgets in the stack.
        """
        return self._stack.count()

    def current_widget(self):
        """
        Return the current figure browser widget in the stack.

        Returns
        -------
        QWidget
            The current widget.
        """
        return self._stack.currentWidget()

    def get_focus_widget(self):
        return self.current_widget()

    # ---- Public API
    # ------------------------------------------------------------------------
    def add_shellwidget(self, shellwidget):
        """
        Create a new widget in the stack and associate it to
        shellwidget.
        """
        shellwidget_id = id(shellwidget)
        if shellwidget_id not in self._shellwidgets:
            widget = self.create_new_widget(shellwidget)
            self._stack.addWidget(widget)
            self._shellwidgets[shellwidget_id] = widget

            # Add all actions to new widget for shortcuts to work.
            for __, action in self.get_actions().items():
                if action:
                    widget_actions = widget.actions()
                    if action not in widget_actions:
                        widget.addAction(action)

            self.set_shellwidget(shellwidget)

    def remove_shellwidget(self, shellwidget):
        """Remove widget associated to shellwidget."""
        shellwidget_id = id(shellwidget)
        if shellwidget_id in self._shellwidgets:
            widget = self._shellwidgets.pop(shellwidget_id)
            self._stack.removeWidget(widget)
            self.close_widget(widget)
            self.update_actions()

    def set_shellwidget(self, shellwidget):
        """
        Set widget associated with shellwidget as the current widget.
        """
        shellwidget_id = id(shellwidget)
        old_widget = self.current_widget()
        if shellwidget_id in self._shellwidgets:
            widget = self._shellwidgets[shellwidget_id]
            self._stack.setCurrentWidget(widget)
            self.switch_widget(widget, old_widget)
            self.update_actions()

    def create_new_widget(self, shellwidget):
        """Create a widget to communicate with shellwidget."""
        raise NotImplementedError

    def close_widget(self, widget):
        """Close the widget."""
        raise NotImplementedError

    def switch_widget(self, widget, old_widget):
        """Switch the current widget."""
        raise NotImplementedError

    def refresh(self):
        """Refresh widgets."""
        if self.count():
            widget = self.current_widget()
            widget.refresh()