class FormComboWidget(QWidget): update_buttons = Signal() def __init__(self, datalist, comment="", parent=None): QWidget.__init__(self, parent) layout = QVBoxLayout() self.setLayout(layout) self.combobox = QComboBox() layout.addWidget(self.combobox) self.stackwidget = QStackedWidget(self) layout.addWidget(self.stackwidget) self.combobox.currentIndexChanged.connect( self.stackwidget.setCurrentIndex) self.widgetlist = [] for data, title, comment in datalist: self.combobox.addItem(title) widget = FormWidget(data, comment=comment, parent=self) self.stackwidget.addWidget(widget) self.widgetlist.append(widget) def setup(self): for widget in self.widgetlist: widget.setup() def get(self): return [ widget.get() for widget in self.widgetlist]
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()
class FormComboWidget(QWidget): update_buttons = Signal() def __init__(self, datalist, comment="", parent=None): QWidget.__init__(self, parent) layout = QVBoxLayout() self.setLayout(layout) self.combobox = QComboBox() layout.addWidget(self.combobox) self.stackwidget = QStackedWidget(self) layout.addWidget(self.stackwidget) self.combobox.currentIndexChanged.connect( self.stackwidget.setCurrentIndex) self.widgetlist = [] for data, title, comment in datalist: self.combobox.addItem(title) widget = FormWidget(data, comment=comment, parent=self) self.stackwidget.addWidget(widget) self.widgetlist.append(widget) def setup(self): for widget in self.widgetlist: widget.setup() def get(self): return [widget.get() for widget in self.widgetlist]
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)
class Editor(QDialog): """Basic scene editor.""" def __init__(self, parent: MainWindow, renderers: List[Renderer]) -> None: """Initialize the Editor.""" super().__init__(parent=parent) self.renderers = renderers self.tree_widget = QTreeWidget() self.tree_widget.setHeaderHidden(True) self.stacked_widget = QStackedWidget() self.layout = QHBoxLayout() self.layout.addWidget(self.tree_widget) self.layout.addWidget(self.stacked_widget) def _selection_callback() -> None: for item in self.tree_widget.selectedItems(): widget_idx = item.data(0, Qt.ItemDataRole.UserRole) self.stacked_widget.setCurrentIndex(widget_idx) self.tree_widget.itemSelectionChanged.connect(_selection_callback) self.setLayout(self.layout) self.setWindowTitle("Editor") self.setModal(True) self.update() def update(self) -> None: """Update the internal widget list.""" self.tree_widget.clear() for idx, renderer in enumerate(self.renderers): actors = renderer._actors # pylint: disable=protected-access widget_idx = self.stacked_widget.addWidget( _get_renderer_widget(renderer)) top_item = QTreeWidgetItem(self.tree_widget, ["Renderer {}".format(idx)]) top_item.setData(0, Qt.ItemDataRole.UserRole, widget_idx) self.tree_widget.addTopLevelItem(top_item) for name, actor in actors.items(): if actor is not None: widget_idx = self.stacked_widget.addWidget( _get_actor_widget(actor)) child_item = QTreeWidgetItem(top_item, [name]) child_item.setData(0, Qt.ItemDataRole.UserRole, widget_idx) top_item.addChild(child_item) self.tree_widget.expandAll() def toggle(self) -> None: """Toggle the editor visibility.""" self.update() if self.isVisible(): self.hide() else: self.show()
def __init__( self, save_register: typing.Dict[str, type(SaveBase)], system_widget=True, base_values: typing.Optional[dict] = None, parent=None, history: typing.Optional[typing.List[str]] = None, file_mode=QFileDialog.AnyFile, ): super().__init__(parent) self.setFileMode(file_mode) self.save_register = { x.get_name_with_suffix(): x for x in save_register.values() } self.setOption(QFileDialog.DontUseNativeDialog, not system_widget) self.setAcceptMode(QFileDialog.AcceptSave) self.filterSelected.connect(self.change_filter) self.setNameFilters(self.save_register.keys()) self.accepted_native = False self.values = {} self.names = [] if history is not None: history = self.history() + history self.setHistory(history) self.base_values = base_values if base_values is not None else {} if not system_widget: widget = QStackedWidget() for name, val in self.save_register.items(): wi = FormWidget(val.get_fields()) if name in self.base_values: wi.set_values(self.base_values[name]) widget.addWidget(wi) self.names.append(name) self.filterSelected.connect(self.change_parameters) layout = self.layout() if isinstance(layout, QGridLayout): # print(layout.columnCount(), layout.rowCount()) # noinspection PyArgumentList layout.addWidget(widget, 0, layout.columnCount(), layout.rowCount(), 1) self.stack_widget = widget else: layout.addWidget(widget) self.stack_widget = widget self.selectNameFilter(self.names[0])
class ImageAdjustmentDialog(QDialog): def __init__(self, image: Image, transform_dict: Dict[str, TransformBase] = None): super().__init__() if transform_dict is None: transform_dict = image_transform_dict self.choose = QComboBox() self.stacked = QStackedWidget() for key, val in transform_dict.items(): self.choose.addItem(key) initial_values = val.calculate_initial(image) form_widget = FormWidget( val.get_fields_per_dimension(image.get_dimension_letters()), initial_values) self.stacked.addWidget(form_widget) self.choose.currentIndexChanged.connect(self.stacked.setCurrentIndex) self.cancel_btn = QPushButton("Cancel") self.cancel_btn.clicked.connect(self.reject) self.process_btn = QPushButton("Process") self.process_btn.clicked.connect(self.process) self.transform_dict = transform_dict self.result_val: ImageAdjustTuple = None layout = QGridLayout() layout.addWidget(self.choose, 0, 0, 1, 3) layout.addWidget(self.stacked, 1, 0, 1, 3) layout.addWidget(self.cancel_btn, 2, 0) layout.addWidget(self.process_btn, 2, 2) self.setLayout(layout) def process(self): values = self.stacked.currentWidget().get_values() algorithm = self.transform_dict[self.choose.currentText()] self.result_val = ImageAdjustTuple(values, algorithm) self.accept()
def _load_core_widget(self): setting = self.params["setting"] hlayout = QHBoxLayout() list_widget = QListWidget() stacked_widget = QStackedWidget() for key in setting.keys(): list_widget.addItem(key) widget = QWidget() widget_layout = QVBoxLayout() for value in setting[key]: self._create_form(widget_layout, [value]) widget_layout.addStretch() widget.setLayout(widget_layout) stacked_widget.addWidget(widget) hlayout.addWidget(list_widget) hlayout.addWidget(stacked_widget) hlayout.setStretch(0, 1) hlayout.setStretch(1, 5) list_widget.currentRowChanged.connect(stacked_widget.setCurrentIndex) list_widget.setCurrentRow(0) return hlayout
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
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)
class StackedCanvasView(CanvasView): """ View that can display intents in their corresponding canvases. Currently, uses a stacked widget with several pages: one for tabview and others for different split views. Can be adapted as long as its internal widgets are CanvasDisplayWidgets. """ def __init__(self, parent=None, model=None): super(StackedCanvasView, self).__init__(parent) if model is not None: self.setModel(model) self.canvas_display_widgets = [ CanvasDisplayTabWidget(), SplitHorizontal(), SplitVertical(), SplitThreeView(), SplitGridView() ] ### Create stacked widget and fill pages with different canvas display widgets self.stackedwidget = QStackedWidget(self) # Create a visual layout section for the buttons that are used to switch the widgets self.buttonpanel = QHBoxLayout() self.buttonpanel.addStretch(10) # Create a logical button grouping that will: # - show the currently selected view (button will be checked/pressed) # - allow for switching the buttons/views in a mutually exclusive manner (only one can be pressed at a time) self.buttongroup = QButtonGroup() def add_canvas_display_widgets(): for i in range(len(self.canvas_display_widgets)): # Add the view to the stacked widget self.stackedwidget.addWidget(self.canvas_display_widgets[i]) # Create a button, using the view's recommended display icon button = QPushButton(self) button.setCheckable(True) button.setIcon(self.canvas_display_widgets[i].icon) # Add the button to the logical button group self.buttongroup.addButton(button, i) # Add the button to the visual layout section self.buttonpanel.addWidget(button) add_canvas_display_widgets() def set_default_canvas_display_widget(): # The first button added to the buttongroup will be the currently selected button (and therefore view) self.buttongroup.button(0).setChecked(True) set_default_canvas_display_widget() # Whenever a button is switched, capture its id (corresponds to integer index in our case); # this will handle switching the view and displaying canvases. self.buttongroup.idToggled.connect(self.switch_view) # define outer layout & add stacked widget and button panel self.layout = QVBoxLayout() self.layout.addWidget(self.stackedwidget) self.layout.addLayout(self.buttonpanel) self.setLayout(self.layout) # INTERFACING WITH TOOLBAR (see XPCSToolbar) def view(self): # from xicam.gui.canvases import ImageIntentCanvas view = self.stackedwidget.currentWidget() return view.getView() # return None # DONE INTERFACING def switch_view(self, id, toggled): # when toggled==True, the the button is the new button that was switched to. # when False, the button is the previous button view = self.canvas_display_widgets[id] if not toggled: ... # TODO: is there anything we need to do here (re: cleanup)? else: self.stackedwidget.setCurrentIndex(id) self.show_canvases() def show_canvases(self): self.stackedwidget.currentWidget().clear_canvases() self.stackedwidget.currentWidget().show_canvases( self._canvas_manager.canvases(self.model()))
class MixerPanel(QFrame): '''A color mixer to hook up to an image. You pass the image you the panel to operate on and it operates on that image in place. You also pass a callback to be called to trigger a refresh. This callback is called every time the mixer modifies your image.''' def __init__(self, img): QFrame.__init__(self) # self.setFrameStyle(QFrame.Box | QFrame.Sunken) self.img = img self.mixer = ColorMixer(self.img) self.callback = None #--------------------------------------------------------------- # ComboBox #--------------------------------------------------------------- self.combo_box_entries = ['RGB Color', 'HSV Color', 'Brightness/Contrast', 'Gamma', 'Gamma (Sigmoidal)'] self.combo_box = QComboBox() for entry in self.combo_box_entries: self.combo_box.addItem(entry) self.combo_box.currentIndexChanged.connect(self.combo_box_changed) #--------------------------------------------------------------- # RGB color sliders #--------------------------------------------------------------- # radio buttons self.rgb_add = QRadioButton('Additive') self.rgb_mul = QRadioButton('Multiplicative') self.rgb_mul.toggled.connect(self.rgb_radio_changed) self.rgb_add.toggled.connect(self.rgb_radio_changed) # sliders rs = IntelligentSlider('R', 0.51, -255, self.rgb_changed) gs = IntelligentSlider('G', 0.51, -255, self.rgb_changed) bs = IntelligentSlider('B', 0.51, -255, self.rgb_changed) self.rs = rs self.gs = gs self.bs = bs self.rgb_widget = QWidget() self.rgb_widget.layout = QGridLayout(self.rgb_widget) self.rgb_widget.layout.addWidget(self.rgb_add, 0, 0, 1, 3) self.rgb_widget.layout.addWidget(self.rgb_mul, 1, 0, 1, 3) self.rgb_widget.layout.addWidget(self.rs, 2, 0) self.rgb_widget.layout.addWidget(self.gs, 2, 1) self.rgb_widget.layout.addWidget(self.bs, 2, 2) #--------------------------------------------------------------- # HSV sliders #--------------------------------------------------------------- # radio buttons self.hsv_add = QRadioButton('Additive') self.hsv_mul = QRadioButton('Multiplicative') self.hsv_mul.toggled.connect(self.hsv_radio_changed) self.hsv_mul.toggled.connect(self.hsv_radio_changed) # sliders hs = IntelligentSlider('H', 0.36, -180, self.hsv_changed) ss = IntelligentSlider('S', 0.002, 0, self.hsv_changed) vs = IntelligentSlider('V', 0.002, 0, self.hsv_changed) self.hs = hs self.ss = ss self.vs = vs self.hsv_widget = QWidget() self.hsv_widget.layout = QGridLayout(self.hsv_widget) self.hsv_widget.layout.addWidget(self.hsv_add, 0, 0, 1, 3) self.hsv_widget.layout.addWidget(self.hsv_mul, 1, 0, 1, 3) self.hsv_widget.layout.addWidget(self.hs, 2, 0) self.hsv_widget.layout.addWidget(self.ss, 2, 1) self.hsv_widget.layout.addWidget(self.vs, 2, 2) #--------------------------------------------------------------- # Brightness/Contrast sliders #--------------------------------------------------------------- # sliders cont = IntelligentSlider('x', 0.002, 0, self.bright_changed) bright = IntelligentSlider('+', 0.51, -255, self.bright_changed) self.cont = cont self.bright = bright # layout self.bright_widget = QWidget() self.bright_widget.layout = QGridLayout(self.bright_widget) self.bright_widget.layout.addWidget(self.cont, 0, 0) self.bright_widget.layout.addWidget(self.bright, 0, 1) #---------------------------------------------------------------------- # Gamma Slider #---------------------------------------------------------------------- gamma = IntelligentSlider('gamma', 0.005, 0, self.gamma_changed) self.gamma = gamma # layout self.gamma_widget = QWidget() self.gamma_widget.layout = QGridLayout(self.gamma_widget) self.gamma_widget.layout.addWidget(self.gamma, 0, 0) #--------------------------------------------------------------- # Sigmoid Gamma sliders #--------------------------------------------------------------- # sliders alpha = IntelligentSlider('alpha', 0.011, 1, self.sig_gamma_changed) beta = IntelligentSlider('beta', 0.012, 0, self.sig_gamma_changed) self.a_gamma = alpha self.b_gamma = beta # layout self.sig_gamma_widget = QWidget() self.sig_gamma_widget.layout = QGridLayout(self.sig_gamma_widget) self.sig_gamma_widget.layout.addWidget(self.a_gamma, 0, 0) self.sig_gamma_widget.layout.addWidget(self.b_gamma, 0, 1) #--------------------------------------------------------------- # Buttons #--------------------------------------------------------------- self.commit_button = QPushButton('Commit') self.commit_button.clicked.connect(self.commit_changes) self.revert_button = QPushButton('Revert') self.revert_button.clicked.connect(self.revert_changes) #--------------------------------------------------------------- # Mixer Layout #--------------------------------------------------------------- self.sliders = QStackedWidget() self.sliders.addWidget(self.rgb_widget) self.sliders.addWidget(self.hsv_widget) self.sliders.addWidget(self.bright_widget) self.sliders.addWidget(self.gamma_widget) self.sliders.addWidget(self.sig_gamma_widget) self.layout = QGridLayout(self) self.layout.addWidget(self.combo_box, 0, 0) self.layout.addWidget(self.sliders, 1, 0) self.layout.addWidget(self.commit_button, 2, 0) self.layout.addWidget(self.revert_button, 3, 0) #--------------------------------------------------------------- # State Initialization #--------------------------------------------------------------- self.combo_box.setCurrentIndex(0) self.rgb_mul.setChecked(True) self.hsv_mul.setChecked(True) def set_callback(self, callback): self.callback = callback def combo_box_changed(self, index): self.sliders.setCurrentIndex(index) self.reset() def rgb_radio_changed(self): self.reset() def hsv_radio_changed(self): self.reset() def reset(self): self.reset_sliders() self.mixer.set_to_stateimg() if self.callback: self.callback() def reset_sliders(self): # handle changing the conversion factors necessary if self.rgb_add.isChecked(): self.rs.set_conv_fac(0.51, -255) self.rs.set_value(0) self.gs.set_conv_fac(0.51, -255) self.gs.set_value(0) self.bs.set_conv_fac(0.51, -255) self.bs.set_value(0) else: self.rs.set_conv_fac(0.002, 0) self.rs.set_value(1.) self.gs.set_conv_fac(0.002, 0) self.gs.set_value(1.) self.bs.set_conv_fac(0.002, 0) self.bs.set_value(1.) self.hs.set_value(0) if self.hsv_add.isChecked(): self.ss.set_conv_fac(0.002, -1) self.ss.set_value(0) self.vs.set_conv_fac(0.002, -1) self.vs.set_value(0) else: self.ss.set_conv_fac(0.002, 0) self.ss.set_value(1.) self.vs.set_conv_fac(0.002, 0) self.vs.set_value(1.) self.bright.set_value(0) self.cont.set_value(1.) self.gamma.set_value(1) self.a_gamma.set_value(1) self.b_gamma.set_value(0.5) def rgb_changed(self, name, val): if name == 'R': channel = self.mixer.RED elif name == 'G': channel = self.mixer.GREEN else: channel = self.mixer.BLUE if self.rgb_mul.isChecked(): self.mixer.multiply(channel, val) elif self.rgb_add.isChecked(): self.mixer.add(channel, val) else: pass if self.callback: self.callback() def hsv_changed(self, name, val): h = self.hs.val() s = self.ss.val() v = self.vs.val() if self.hsv_mul.isChecked(): self.mixer.hsv_multiply(h, s, v) elif self.hsv_add.isChecked(): self.mixer.hsv_add(h, s, v) else: pass if self.callback: self.callback() def bright_changed(self, name, val): b = self.bright.val() c = self.cont.val() self.mixer.brightness(c, b) if self.callback: self.callback() def gamma_changed(self, name, val): self.mixer.gamma(val) if self.callback: self.callback() def sig_gamma_changed(self, name, val): ag = self.a_gamma.val() bg = self.b_gamma.val() self.mixer.sigmoid_gamma(ag, bg) if self.callback: self.callback() def commit_changes(self): self.mixer.commit_changes() self.reset_sliders() def revert_changes(self): self.mixer.revert() self.reset_sliders() if self.callback: self.callback()
class RateLawWidget(QWidget): """ Rate Law widget """ redirect_stdio = Signal(bool) def __init__(self, parent, max_entries=100): "Initialize Various list objects before assignment" displaylist = [] displaynamelist = [] infixmod = [] infixlist = [] desclist = [] parameternamelist = [] parameterdesclist = [] buttonlist = [] xmldoc = minidom.parse( os.path.dirname(os.path.abspath(__file__)) + '\\ratelaw2_0_3.xml') lawlistxml = xmldoc.getElementsByTagName('law') o = 0 for s in lawlistxml: o = o + 1 parameternamelistlist = [0 for x in range(o)] parameterdesclistlist = [0 for x in range(o)] """i is the number of laws currently in the xml file""" i = 0 """ Parsing xml: Acquiring rate law name, description, and list of parameter information """ for s in lawlistxml: """Gets Latec Expression""" displaylist.append(s.getAttribute('display')) """Gets Rate-Law Name""" displaynamelist.append(s.getAttribute('displayName')) """"Gets Raw Rate-Law expression""" infixlist.append(s.getAttribute('infixExpression')) """Gets description statement""" desclist.append(s.getAttribute('description')) """Gets listOfParameters Object""" parameterlist = s.getElementsByTagName('listOfParameters')[0] """Gets a list of parameters within ListOfParameters object""" parameters = parameterlist.getElementsByTagName('parameter') for param in parameters: parameternamelist.append(param.attributes['name'].value) parameterdesclist.append(param.attributes['description'].value) parameternamelistlist[i] = parameternamelist parameterdesclistlist[i] = parameterdesclist parameternamelist = [] parameterdesclist = [] i = i + 1 SLElistlist = [0 for x in range(i)] PLElistlist = [0 for x in range(i)] ILElistlist = [0 for x in range(i)] paramLElistlist = [0 for x in range(i)] numlistlist = [0 for x in range(i)] QWidget.__init__(self, parent) self.setWindowTitle("Rate Law Library") self.output = None self.error_output = None self._last_wdir = None self._last_args = None self._last_pythonpath = None self.lawlist = QListWidget() self.lawpage = QStackedWidget() for j in range(i): item = QListWidgetItem(displaynamelist[j]) self.lawlist.addItem(item) self.lawdetailpage = QWidget() setup_group = QGroupBox(displaynamelist[j]) infixmod.append(infixlist[j].replace("___", " ")) setup_label = QLabel(infixmod[j]) setup_label.setWordWrap(True) desc_group = QGroupBox("Description") desc_label = QLabel(desclist[j]) desc_label.setWordWrap(True) param_label = QGridLayout() nm = QLabel("Name:") des = QLabel("Description:") repl = QLabel("Replace with:") param_label.addWidget(nm, 0, 0) param_label.addWidget(des, 0, 1) param_label.addWidget(repl, 0, 2) """g is the total number of alterable values""" """t is the total number of alterable non-parameters""" t = 1 snum = 0 pnum = 0 inum = 0 """range of N is the max number of possible substrates OR products""" N = 5 for n in range(N): nl = n + 1 if (infixmod[j].find('S%s' % nl) > -1): z = QLabel('S%s is present' % nl) param_label.addWidget(z, t, 0) snum = snum + 1 t = t + 1 for n in range(N): nl = n + 1 if (infixmod[j].find('P%s' % nl) > -1): z = QLabel('P%s is present' % nl) param_label.addWidget(z, t, 0) pnum = pnum + 1 t = t + 1 for n in range(N): nl = n + 1 if (infixmod[j].find('I%s' % nl) > -1): z = QLabel('I%s is present' % nl) param_label.addWidget(z, t, 0) inum = inum + 1 t = t + 1 """Initialize lists of list of parameter lineedit""" length = len(parameternamelistlist[j]) for b in range(length): p = QLabel("%s :" % parameternamelistlist[j][b]) param_label.addWidget(p, b + t, 0) d = QLabel("'%s'" % parameterdesclistlist[j][b]) param_label.addWidget(d, b + t, 1) Slineeditlist = [0 for x in range(snum)] Plineeditlist = [0 for x in range(pnum)] Ilineeditlist = [0 for x in range(inum)] paramlineeditlist = [0 for x in range(length)] editcount = 1 """Place lineedit widgets for parameters""" for s in range(snum): Slineeditlist[s] = QLineEdit() param_label.addWidget(Slineeditlist[s], editcount, 2) editcount = editcount + 1 SLElistlist[j] = Slineeditlist for s in range(pnum): Plineeditlist[s] = QLineEdit() param_label.addWidget(Plineeditlist[s], editcount, 2) editcount = editcount + 1 PLElistlist[j] = Plineeditlist for s in range(inum): Ilineeditlist[s] = QLineEdit() param_label.addWidget(Ilineeditlist[s], editcount, 2) editcount = editcount + 1 ILElistlist[j] = Ilineeditlist for s in range(length): paramlineeditlist[s] = QLineEdit() param_label.addWidget(paramlineeditlist[s], editcount, 2) editcount = editcount + 1 paramLElistlist[j] = paramlineeditlist """Necessary lists for editable parameters. Housekeeping essentially.""" numlistlist[j] = [snum, pnum, inum, length] charlist = ["S", "P", "I"] buttonlist.append(QPushButton(self)) buttonlist[j].setText("Insert Rate Law: %s" % displaynamelist[j]) # Warning: do not try to regroup the following QLabel contents with # widgets above -- this string was isolated here in a single QLabel # on purpose: to fix Issue 863 """Page formatting""" setup_layout = QVBoxLayout() setup_layout.addWidget(setup_label) setup_group.setLayout(setup_layout) desc_group.setLayout(param_label) vlayout2 = QVBoxLayout() vlayout2.addWidget(setup_group) vlayout2.addWidget(desc_group) vlayout2.addWidget(buttonlist[j]) vlayout2.addStretch(1) self.lawdetailpage.setLayout(vlayout2) self.lawpage.addWidget(self.lawdetailpage) """Set up button functionality""" for k in range(47): buttonlist[k].clicked.connect( self.pressbutton(infixmod[k], SLElistlist[k], PLElistlist[k], ILElistlist[k], paramLElistlist[k], parameternamelistlist[k], numlistlist[k], charlist, k)) self.lawlist.currentRowChanged.connect(self.lawpage.setCurrentIndex) self.lawlist.setCurrentRow(0) self.lawlist.setMinimumWidth(self.lawlist.sizeHintForColumn(0)) """Set up high-level widget formatting.""" hsplitter = QSplitter() hsplitter.addWidget(self.lawlist) hsplitter.addWidget(self.lawpage) layout = QVBoxLayout() layout.addWidget(hsplitter) self.setLayout(layout) """Testing LineEdit functionality""" def entertext(self, le, string, stringlist): def entertextreal(): stuff = self.le.text() string = "%s" % stuff stringlist[0].replace("vo", "%s" % string) return entertextreal """Analyze lineedits on page, replace appropriate text, then paste text in console""" def pressbutton(ratelaw, string, SLElist, PLElist, ILElist, paramLElist, paramlist, numlist, charlist, j): def pressbuttoninsert(): stringmod = string for k in range(4): for i in range(numlist[k]): l = i + 1 if (k == 0): stuff = SLElist[i].text() if (stuff.find(" ") != -1 or stuff.find('"') != -1): pass elif (stuff == ""): pass else: stringmod = stringmod.replace( charlist[k] + str(l), "%s" % stuff) if (k == 1): stuff = PLElist[i].text() if (stuff.find(" ") != -1 or stuff.find('"') != -1): pass elif (stuff == ""): pass else: stringmod = stringmod.replace( charlist[k] + str(l), "%s" % stuff) if (k == 2): stuff = ILElist[i].text() if (stuff.find(" ") != -1 or stuff.find('"') != -1): pass elif (stuff == ""): pass else: stringmod = stringmod.replace( charlist[k] + str(l), "%s" % stuff) if (k == 3): stuff = paramLElist[i].text() if (stuff.find(" ") != -1 or stuff.find('"') != -1): pass elif (stuff == ""): pass else: stringmod = stringmod.replace( paramlist[i], "%s" % stuff) ratelaw.insertText(stringmod) return pressbuttoninsert
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())
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)
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()
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())
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())
class PackagesDialog(DialogBase): """Package dependencies dialog.""" sig_setup_ready = Signal() def __init__( self, parent=None, packages=None, pip_packages=None, remove_only=False, update_only=False, ): """About dialog.""" super(PackagesDialog, self).__init__(parent=parent) # Variables self.api = AnacondaAPI() self.actions = None self.packages = packages or [] self.pip_packages = pip_packages or [] # Widgets self.stack = QStackedWidget() self.table = QTableWidget() self.text = QTextEdit() self.label_description = LabelBase() self.label_status = LabelBase() self.progress_bar = QProgressBar() self.button_ok = ButtonPrimary('Apply') self.button_cancel = ButtonNormal('Cancel') # Widget setup self.text.setReadOnly(True) self.stack.addWidget(self.table) self.stack.addWidget(self.text) if remove_only: text = 'The following packages will be removed:<br>' else: text = 'The following packages will be modified:<br>' self.label_description.setText(text) self.label_description.setWordWrap(True) self.label_description.setWordWrap(True) self.label_status.setWordWrap(True) self.table.horizontalScrollBar().setVisible(False) self.table.setSelectionBehavior(QAbstractItemView.SelectRows) self.table.setAlternatingRowColors(True) self.table.setSelectionMode(QAbstractItemView.NoSelection) self.table.setSortingEnabled(True) self._hheader = self.table.horizontalHeader() self._vheader = self.table.verticalHeader() self._hheader.setStretchLastSection(True) self._hheader.setDefaultAlignment(Qt.AlignLeft) self._hheader.setSectionResizeMode(self._hheader.Fixed) self._vheader.setSectionResizeMode(self._vheader.Fixed) self.button_ok.setMinimumWidth(70) self.button_ok.setDefault(True) self.base_minimum_width = 300 if remove_only else 420 if remove_only: self.setWindowTitle("Remove Packages") elif update_only: self.setWindowTitle("Update Packages") else: self.setWindowTitle("Install Packages") self.setMinimumWidth(self.base_minimum_width) # Layouts layout_progress = QHBoxLayout() layout_progress.addWidget(self.label_status) layout_progress.addWidget(SpacerHorizontal()) layout_progress.addWidget(self.progress_bar) layout_buttons = QHBoxLayout() layout_buttons.addStretch() layout_buttons.addWidget(self.button_cancel) layout_buttons.addWidget(SpacerHorizontal()) layout_buttons.addWidget(self.button_ok) layout = QVBoxLayout() layout.addWidget(self.label_description) layout.addWidget(SpacerVertical()) layout.addWidget(self.stack) layout.addWidget(SpacerVertical()) layout.addLayout(layout_progress) layout.addWidget(SpacerVertical()) layout.addWidget(SpacerVertical()) layout.addLayout(layout_buttons) self.setLayout(layout) # Signals self.button_ok.clicked.connect(self.accept) self.button_cancel.clicked.connect(self.reject) self.button_ok.setDisabled(True) # Setup self.table.setDisabled(True) self.update_status('Solving package specifications', value=0, max_value=0) def setup(self, worker, output, error): """Setup the widget to include the list of dependencies.""" if not isinstance(output, dict): output = {} packages = sorted(pkg.split('==')[0] for pkg in self.packages) success = output.get('success') error = output.get('error', '') exception_name = output.get('exception_name', '') actions = output.get('actions', []) prefix = worker.prefix if exception_name: message = exception_name else: # All requested packages already installed message = output.get('message', ' ') navi_deps_error = self.api.check_navigator_dependencies( actions, prefix) description = self.label_description.text() if error: description = 'No packages will be modified.' self.stack.setCurrentIndex(1) self.button_ok.setDisabled(True) if self.api.is_offline(): error = ("Some of the functionality of Anaconda Navigator " "will be limited in <b>offline mode</b>. <br><br>" "Installation and upgrade actions will be subject to " "the packages currently available on your package " "cache.") self.text.setText(error) elif navi_deps_error: description = 'No packages will be modified.' error = ('Downgrading/removing these packages will modify ' 'Anaconda Navigator dependencies.') self.text.setText(error) self.stack.setCurrentIndex(1) message = 'NavigatorDependenciesError' self.button_ok.setDisabled(True) elif success and actions: self.stack.setCurrentIndex(0) # Conda 4.3.x if isinstance(actions, list): actions_link = actions[0].get('LINK', []) actions_unlink = actions[0].get('UNLINK', []) # Conda 4.4.x else: actions_link = actions.get('LINK', []) actions_unlink = actions.get('UNLINK', []) deps = set() deps = deps.union({p['name'] for p in actions_link}) deps = deps.union({p['name'] for p in actions_unlink}) deps = deps - set(packages) deps = sorted(list(deps)) count_total_packages = len(packages) + len(deps) plural_total = 's' if count_total_packages != 1 else '' plural_selected = 's' if len(packages) != 1 else '' self.table.setRowCount(count_total_packages) self.table.setColumnCount(4) if actions_link: description = '{0} package{1} will be installed'.format( count_total_packages, plural_total) self.table.showColumn(2) self.table.showColumn(3) elif actions_unlink and not actions_link: self.table.hideColumn(2) self.table.hideColumn(3) self.table.setHorizontalHeaderLabels( ['Name', 'Unlink', 'Link', 'Channel']) description = '{0} package{1} will be removed'.format( count_total_packages, plural_total) for row, pkg in enumerate(packages + deps): link_item = [p for p in actions_link if p['name'] == pkg] if not link_item: link_item = { 'version': '-'.center(len('link')), 'channel': '-'.center(len('channel')), } else: link_item = link_item[0] unlink_item = [p for p in actions_unlink if p['name'] == pkg] if not unlink_item: unlink_item = { 'version': '-'.center(len('link')), } else: unlink_item = unlink_item[0] unlink_version = str(unlink_item['version']) link_version = str(link_item['version']) item_unlink_v = QTableWidgetItem(unlink_version) item_link_v = QTableWidgetItem(link_version) item_link_c = QTableWidgetItem(link_item['channel']) if pkg in packages: item_name = QTableWidgetItem(pkg) else: item_name = QTableWidgetItem('*' + pkg) items = [item_name, item_unlink_v, item_link_v, item_link_c] for column, item in enumerate(items): item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable) self.table.setItem(row, column, item) if deps: message = ( '<b>*</b> indicates the package is a dependency of a ' 'selected package{0}<br>').format(plural_selected) self.button_ok.setEnabled(True) self.table.resizeColumnsToContents() unlink_width = self.table.columnWidth(1) if unlink_width < 60: self.table.setColumnWidth(1, 60) self.table.setHorizontalHeaderLabels( ['Name ', 'Unlink ', 'Link ', 'Channel ']) self.table.setEnabled(True) self.update_status(message=message) self.label_description.setText(description) # Adjust size after data has populated the table self.table.resizeColumnsToContents() width = sum( self.table.columnWidth(i) for i in range(self.table.columnCount())) delta = (self.width() - self.table.width() + self.table.verticalHeader().width() + 10) new_width = width + delta if new_width < self.base_minimum_width: new_width = self.base_minimum_width self.setMinimumWidth(new_width) self.setMaximumWidth(new_width) self.sig_setup_ready.emit() def update_status(self, message='', value=None, max_value=None): """Update status of packages dialog.""" self.label_status.setText(message) if max_value is None and value is None: self.progress_bar.setVisible(False) else: self.progress_bar.setVisible(True) self.progress_bar.setMaximum(max_value) self.progress_bar.setValue(value)
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())
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))
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
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())
class CalibrationWidget(ParameterWidget): def __init__(self, parent=None): ParameterWidget.__init__(self, _Calibration, parent) def _init_ui(self): # Widgets self._combobox = QComboBox() self._stack = QStackedWidget() # Layouts layout = ParameterWidget._init_ui(self) layout.addRow(self._combobox) layout.addRow(self._stack) # Register classes self._widget_indexes = {} for entry_point in iter_entry_points('pyhmsa_gui.spec.condition.calibration'): widget_class = entry_point.load(require=False) widget = widget_class() self._combobox.addItem(widget.accessibleName().title()) self._widget_indexes[widget.CLASS] = self._stack.addWidget(widget) widget.edited.connect(self.edited) # Signals self._combobox.currentIndexChanged.connect(self._on_combo_box) self._combobox.currentIndexChanged.connect(self.edited) return layout def _on_combo_box(self): # Old values oldwidget = self._stack.currentWidget() try: quantity = oldwidget._txt_quantity.text() except: quantity = None try: unit = oldwidget._txt_unit.text() except: unit = None # Change widget current_index = self._combobox.currentIndex() self._stack.setCurrentIndex(current_index) # Update values widget = self._stack.currentWidget() widget._txt_quantity.setText(quantity) widget._txt_unit.setText(unit) def parameter(self): return self._stack.currentWidget().calibration() def setParameter(self, calibration): index = self._widget_indexes[type(calibration)] self._combobox.setCurrentIndex(index) self._stack.setCurrentIndex(index) self._stack.currentWidget().setParameter(calibration) def calibration(self): return self.parameter() def setCalibration(self, calibration): self.setParameter(calibration) def setReadOnly(self, state): ParameterWidget.setReadOnly(self, state) self._combobox.setEnabled(not state) self._stack.currentWidget().setReadOnly(state) def isReadOnly(self): return ParameterWidget.isReadOnly(self) and \ not self._combobox.isEnabled() and \ self._stack.currentWidget().isReadOnly() def hasAcceptableInput(self): return ParameterWidget.hasAcceptableInput(self) and \ self._stack.currentWidget().hasAcceptableInput()
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 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())
class SimulationPanel(QWidget): def __init__(self, config_file): QWidget.__init__(self) self._config_file = config_file self.setObjectName("Simulation_panel") layout = QVBoxLayout() self._simulation_mode_combo = QComboBox() self._simulation_mode_combo.setObjectName("Simulation_mode") addHelpToWidget(self._simulation_mode_combo, "run/simulation_mode") self._simulation_mode_combo.currentIndexChanged.connect(self.toggleSimulationMode) simulation_mode_layout = QHBoxLayout() simulation_mode_layout.addSpacing(10) simulation_mode_layout.addWidget(QLabel("Simulation mode:"), 0, Qt.AlignVCenter) simulation_mode_layout.addWidget(self._simulation_mode_combo, 0, Qt.AlignVCenter) simulation_mode_layout.addSpacing(20) self.run_button = QToolButton() self.run_button.setObjectName('start_simulation') self.run_button.setIconSize(QSize(32, 32)) self.run_button.setText("Start Simulation") self.run_button.setIcon(resourceIcon("ide/gear_in_play")) self.run_button.clicked.connect(self.runSimulation) self.run_button.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) addHelpToWidget(self.run_button, "run/start_simulation") simulation_mode_layout.addWidget(self.run_button) simulation_mode_layout.addStretch(1) layout.addSpacing(5) layout.addLayout(simulation_mode_layout) layout.addSpacing(10) self._simulation_stack = QStackedWidget() self._simulation_stack.setLineWidth(1) self._simulation_stack.setFrameStyle(QFrame.StyledPanel) layout.addWidget(self._simulation_stack) self._simulation_widgets = OrderedDict() """ :type: OrderedDict[BaseRunModel,SimulationConfigPanel]""" self.addSimulationConfigPanel(SingleTestRunPanel()) self.addSimulationConfigPanel(EnsembleExperimentPanel()) if(ERT.ert.have_observations()): self.addSimulationConfigPanel(EnsembleSmootherPanel()) self.addSimulationConfigPanel(MultipleDataAssimilationPanel()) self.addSimulationConfigPanel(IteratedEnsembleSmootherPanel()) self.setLayout(layout) def addSimulationConfigPanel(self, panel): assert isinstance(panel, SimulationConfigPanel) self._simulation_stack.addWidget(panel) simulation_model = panel.getSimulationModel() self._simulation_widgets[simulation_model] = panel self._simulation_mode_combo.addItem(simulation_model.name(),simulation_model) panel.simulationConfigurationChanged.connect(self.validationStatusChanged) def getActions(self): return [] def getCurrentSimulationModel(self): return self._simulation_mode_combo.itemData(self._simulation_mode_combo.currentIndex(), Qt.UserRole) def getSimulationArguments(self): """ @rtype: dict[str,object]""" simulation_widget = self._simulation_widgets[self.getCurrentSimulationModel()] return simulation_widget.getSimulationArguments() def runSimulation(self): case_name = getCurrentCaseName() message = "Are you sure you want to use case '%s' for initialization of the initial ensemble when running the simulations?" % case_name start_simulations = QMessageBox.question(self, "Start simulations?", message, QMessageBox.Yes | QMessageBox.No ) if start_simulations == QMessageBox.Yes: run_model = self.getCurrentSimulationModel() arguments = self.getSimulationArguments() dialog = RunDialog(self._config_file, run_model(), arguments) dialog.startSimulation() dialog.exec_() ERT.emitErtChange() # simulations may have added new cases. def toggleSimulationMode(self): current_model = self.getCurrentSimulationModel() if current_model is not None: widget = self._simulation_widgets[self.getCurrentSimulationModel()] self._simulation_stack.setCurrentWidget(widget) self.validationStatusChanged() def validationStatusChanged(self): widget = self._simulation_widgets[self.getCurrentSimulationModel()] self.run_button.setEnabled(widget.isConfigurationValid())
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)
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())
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()
class IMCWidget(QWidget): NAME = 'Imaging Mass Cytometry' FULL_NAME = f'napari-imc: {NAME}' AREA = 'right' ALLOWED_AREAS = ['left', 'right'] def __init__(self, napari_viewer: Viewer, parent: Optional[QWidget] = None): super(IMCWidget, self).__init__(parent) self._controller = IMCController(napari_viewer, self) self._imc_file_tree_model = IMCFileTreeModel(self._controller, parent=self) self._imc_file_tree_view = IMCFileTreeView(parent=self) self._imc_file_tree_view.setModel(self._imc_file_tree_model) self._channel_table_model = ChannelTableModel(self._controller, parent=self) self._channel_table_proxy_model = QSortFilterProxyModel(self) self._channel_table_proxy_model.setSourceModel( self._channel_table_model) self._channel_table_proxy_model.sort( self._channel_table_model.LABEL_COLUMN) self._channel_table_view = ChannelTableView(parent=self) self._channel_table_view.setModel(self._channel_table_proxy_model) self._channel_controls_widget = ChannelControlsWidget(self._controller, parent=self) self._channel_controls_container = QStackedWidget(self) self._channel_controls_container.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum) self._channel_controls_container.addWidget(QWidget(self)) self._channel_controls_container.addWidget( self._channel_controls_widget) layout = QVBoxLayout(self) splitter = QSplitter(Qt.Vertical, self) splitter.addWidget(self._imc_file_tree_view) channel_panel = QWidget(self) channel_panel_layout = QVBoxLayout() channel_panel_layout.addWidget(self._channel_table_view) channel_panel_layout.addWidget(self._channel_controls_container) channel_panel.setLayout(channel_panel_layout) splitter.addWidget(channel_panel) layout.addWidget(splitter) self.setLayout(layout) # noinspection PyUnusedLocal # noinspection PyUnresolvedReferences @self._imc_file_tree_model.dataChanged.connect def on_imc_file_tree_model_data_changed(top_left: QModelIndex, bottom_right: QModelIndex, roles: Optional[int] = None): item = top_left.internalPointer() if isinstance(item, IMCFilePanoramaModel): if item.is_shown: self._controller.hide_imc_file_panorama(item) else: self._controller.show_imc_file_panorama(item) elif isinstance(item, IMCFileAcquisitionModel): if item.is_loaded: self._controller.unload_imc_file_acquisition(item) else: self._controller.load_imc_file_acquisition(item) # noinspection PyUnresolvedReferences @self._imc_file_tree_view.events.imc_file_closed.connect def on_imc_file_tree_view_imc_file_closed(imc_file: IMCFileModel): self._controller.close_imc_file(imc_file) # noinspection PyUnusedLocal # noinspection PyUnresolvedReferences @self._channel_table_model.dataChanged.connect def on_channel_table_model_data_changed(top_left: QModelIndex, bottom_right: QModelIndex, roles: Optional[int] = None): channel = self._controller.channels[top_left.row()] if channel.is_shown: self._controller.hide_channel(channel) else: self._controller.show_channel(channel) channel_table_selection_model: QItemSelectionModel = self._channel_table_view.selectionModel( ) # noinspection PyUnusedLocal # noinspection PyUnresolvedReferences @channel_table_selection_model.selectionChanged.connect def on_channel_table_view_selection_changed( selected: QItemSelection, deselected: QItemSelection): selected_channels = [] for index in self._channel_table_view.selectedIndexes(): index = self._channel_table_proxy_model.mapToSource(index) channel = self._controller.channels[index.row()] selected_channels.append(channel) self._controller.selected_channels = selected_channels def select_channel(self, channel_index: int): top_left = self._channel_table_model.index(channel_index, 0) top_left = self._channel_table_proxy_model.mapFromSource(top_left) bottom_right = self._channel_table_model.index( channel_index, self._channel_table_model.columnCount() - 1) bottom_right = self._channel_table_proxy_model.mapFromSource( bottom_right) selection_model: QItemSelectionModel = self._channel_table_view.selectionModel( ) selection_model.clearSelection( ) # first clear the selection, to make sure the channel controls refresh selection_model.select(QItemSelection(top_left, bottom_right), QItemSelectionModel.Select) def refresh_channel_controls_widget(self): self._channel_controls_widget.refresh() if len(self._controller.selected_channels) > 0: self._channel_controls_container.setCurrentIndex(1) else: self._channel_controls_container.setCurrentIndex(0) @property def controller(self): return self._controller @property def imc_file_tree_model(self) -> IMCFileTreeModel: return self._imc_file_tree_model @property def channel_table_model(self) -> ChannelTableModel: return self._channel_table_model
class MixerPanel(QFrame): '''A color mixer to hook up to an image. You pass the image you the panel to operate on and it operates on that image in place. You also pass a callback to be called to trigger a refresh. This callback is called every time the mixer modifies your image.''' def __init__(self, img): QFrame.__init__(self) # self.setFrameStyle(QFrame.Box | QFrame.Sunken) self.img = img self.mixer = ColorMixer(self.img) self.callback = None #--------------------------------------------------------------- # ComboBox #--------------------------------------------------------------- self.combo_box_entries = [ 'RGB Color', 'HSV Color', 'Brightness/Contrast', 'Gamma', 'Gamma (Sigmoidal)' ] self.combo_box = QComboBox() for entry in self.combo_box_entries: self.combo_box.addItem(entry) self.combo_box.currentIndexChanged.connect(self.combo_box_changed) #--------------------------------------------------------------- # RGB color sliders #--------------------------------------------------------------- # radio buttons self.rgb_add = QRadioButton('Additive') self.rgb_mul = QRadioButton('Multiplicative') self.rgb_mul.toggled.connect(self.rgb_radio_changed) self.rgb_add.toggled.connect(self.rgb_radio_changed) # sliders rs = IntelligentSlider('R', 0.51, -255, self.rgb_changed) gs = IntelligentSlider('G', 0.51, -255, self.rgb_changed) bs = IntelligentSlider('B', 0.51, -255, self.rgb_changed) self.rs = rs self.gs = gs self.bs = bs self.rgb_widget = QWidget() self.rgb_widget.layout = QGridLayout(self.rgb_widget) self.rgb_widget.layout.addWidget(self.rgb_add, 0, 0, 1, 3) self.rgb_widget.layout.addWidget(self.rgb_mul, 1, 0, 1, 3) self.rgb_widget.layout.addWidget(self.rs, 2, 0) self.rgb_widget.layout.addWidget(self.gs, 2, 1) self.rgb_widget.layout.addWidget(self.bs, 2, 2) #--------------------------------------------------------------- # HSV sliders #--------------------------------------------------------------- # radio buttons self.hsv_add = QRadioButton('Additive') self.hsv_mul = QRadioButton('Multiplicative') self.hsv_mul.toggled.connect(self.hsv_radio_changed) self.hsv_mul.toggled.connect(self.hsv_radio_changed) # sliders hs = IntelligentSlider('H', 0.36, -180, self.hsv_changed) ss = IntelligentSlider('S', 0.002, 0, self.hsv_changed) vs = IntelligentSlider('V', 0.002, 0, self.hsv_changed) self.hs = hs self.ss = ss self.vs = vs self.hsv_widget = QWidget() self.hsv_widget.layout = QGridLayout(self.hsv_widget) self.hsv_widget.layout.addWidget(self.hsv_add, 0, 0, 1, 3) self.hsv_widget.layout.addWidget(self.hsv_mul, 1, 0, 1, 3) self.hsv_widget.layout.addWidget(self.hs, 2, 0) self.hsv_widget.layout.addWidget(self.ss, 2, 1) self.hsv_widget.layout.addWidget(self.vs, 2, 2) #--------------------------------------------------------------- # Brightness/Contrast sliders #--------------------------------------------------------------- # sliders cont = IntelligentSlider('x', 0.002, 0, self.bright_changed) bright = IntelligentSlider('+', 0.51, -255, self.bright_changed) self.cont = cont self.bright = bright # layout self.bright_widget = QWidget() self.bright_widget.layout = QGridLayout(self.bright_widget) self.bright_widget.layout.addWidget(self.cont, 0, 0) self.bright_widget.layout.addWidget(self.bright, 0, 1) #---------------------------------------------------------------------- # Gamma Slider #---------------------------------------------------------------------- gamma = IntelligentSlider('gamma', 0.005, 0, self.gamma_changed) self.gamma = gamma # layout self.gamma_widget = QWidget() self.gamma_widget.layout = QGridLayout(self.gamma_widget) self.gamma_widget.layout.addWidget(self.gamma, 0, 0) #--------------------------------------------------------------- # Sigmoid Gamma sliders #--------------------------------------------------------------- # sliders alpha = IntelligentSlider('alpha', 0.011, 1, self.sig_gamma_changed) beta = IntelligentSlider('beta', 0.012, 0, self.sig_gamma_changed) self.a_gamma = alpha self.b_gamma = beta # layout self.sig_gamma_widget = QWidget() self.sig_gamma_widget.layout = QGridLayout(self.sig_gamma_widget) self.sig_gamma_widget.layout.addWidget(self.a_gamma, 0, 0) self.sig_gamma_widget.layout.addWidget(self.b_gamma, 0, 1) #--------------------------------------------------------------- # Buttons #--------------------------------------------------------------- self.commit_button = QPushButton('Commit') self.commit_button.clicked.connect(self.commit_changes) self.revert_button = QPushButton('Revert') self.revert_button.clicked.connect(self.revert_changes) #--------------------------------------------------------------- # Mixer Layout #--------------------------------------------------------------- self.sliders = QStackedWidget() self.sliders.addWidget(self.rgb_widget) self.sliders.addWidget(self.hsv_widget) self.sliders.addWidget(self.bright_widget) self.sliders.addWidget(self.gamma_widget) self.sliders.addWidget(self.sig_gamma_widget) self.layout = QGridLayout(self) self.layout.addWidget(self.combo_box, 0, 0) self.layout.addWidget(self.sliders, 1, 0) self.layout.addWidget(self.commit_button, 2, 0) self.layout.addWidget(self.revert_button, 3, 0) #--------------------------------------------------------------- # State Initialization #--------------------------------------------------------------- self.combo_box.setCurrentIndex(0) self.rgb_mul.setChecked(True) self.hsv_mul.setChecked(True) def set_callback(self, callback): self.callback = callback def combo_box_changed(self, index): self.sliders.setCurrentIndex(index) self.reset() def rgb_radio_changed(self): self.reset() def hsv_radio_changed(self): self.reset() def reset(self): self.reset_sliders() self.mixer.set_to_stateimg() if self.callback: self.callback() def reset_sliders(self): # handle changing the conversion factors necessary if self.rgb_add.isChecked(): self.rs.set_conv_fac(0.51, -255) self.rs.set_value(0) self.gs.set_conv_fac(0.51, -255) self.gs.set_value(0) self.bs.set_conv_fac(0.51, -255) self.bs.set_value(0) else: self.rs.set_conv_fac(0.002, 0) self.rs.set_value(1.) self.gs.set_conv_fac(0.002, 0) self.gs.set_value(1.) self.bs.set_conv_fac(0.002, 0) self.bs.set_value(1.) self.hs.set_value(0) if self.hsv_add.isChecked(): self.ss.set_conv_fac(0.002, -1) self.ss.set_value(0) self.vs.set_conv_fac(0.002, -1) self.vs.set_value(0) else: self.ss.set_conv_fac(0.002, 0) self.ss.set_value(1.) self.vs.set_conv_fac(0.002, 0) self.vs.set_value(1.) self.bright.set_value(0) self.cont.set_value(1.) self.gamma.set_value(1) self.a_gamma.set_value(1) self.b_gamma.set_value(0.5) def rgb_changed(self, name, val): if name == 'R': channel = self.mixer.RED elif name == 'G': channel = self.mixer.GREEN else: channel = self.mixer.BLUE if self.rgb_mul.isChecked(): self.mixer.multiply(channel, val) elif self.rgb_add.isChecked(): self.mixer.add(channel, val) else: pass if self.callback: self.callback() def hsv_changed(self, name, val): h = self.hs.val() s = self.ss.val() v = self.vs.val() if self.hsv_mul.isChecked(): self.mixer.hsv_multiply(h, s, v) elif self.hsv_add.isChecked(): self.mixer.hsv_add(h, s, v) else: pass if self.callback: self.callback() def bright_changed(self, name, val): b = self.bright.val() c = self.cont.val() self.mixer.brightness(c, b) if self.callback: self.callback() def gamma_changed(self, name, val): self.mixer.gamma(val) if self.callback: self.callback() def sig_gamma_changed(self, name, val): ag = self.a_gamma.val() bg = self.b_gamma.val() self.mixer.sigmoid_gamma(ag, bg) if self.callback: self.callback() def commit_changes(self): self.mixer.commit_changes() self.reset_sliders() def revert_changes(self): self.mixer.revert() self.reset_sliders() if self.callback: self.callback()
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
class TomographyPlugin(GUIPlugin): name = "Tomography" def __init__(self, *args, **kwargs): self.active_filename = "" self._main_view = QMainWindow() self._main_view.setCentralWidget(QWidget()) self._main_view.centralWidget().hide() self._editors = QStackedWidget() self.workflow_process_selector = QComboBox() self.workflow_process_selector.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed) self.workflow_process_selector.addItem("TomoPy Workflow") self._workflow = None self._tomo_workflow = TomographyWorkflow() # Create a workflow self._workflow = self._tomo_workflow self._workflow_editor = WorkflowEditor(workflow=self._workflow) self._workflow_editor.sigRunWorkflow.connect(self.run_workflow) self._active_workflow_executor = QTreeWidget() self._editors.addWidget(self._workflow_editor) if hasLTT: self._alt_workflow = LTTWorkflow() self.workflow_process_selector.addItem("LTT Workflow") self._alt_workflow_editor = WorkflowEditor(self._alt_workflow) self._alt_workflow_editor.sigRunWorkflow.connect(self.run_workflow) self._editors.addWidget(self._alt_workflow_editor) self.workflow_process_selector.setCurrentIndex(0) self.workflow_process_selector.currentIndexChanged.connect(self.active_workflow_changed) self._active_workflow_widget = QWidget() self._active_layout = QVBoxLayout() self._active_layout.addWidget(self.workflow_process_selector) self._active_layout.addWidget(self._editors) self._active_layout.addWidget(self._active_workflow_executor) self._active_workflow_widget.setLayout(self._active_layout) # self.hdf5_viewer = HDFTreeViewer() # self.hdf5_viewer.load("/Users/hari/20200521_160002_heartbeat_test.h5") self.top_controls = QWidget() self.top_controls_layout = QHBoxLayout() self.top_controls.setLayout(self.top_controls_layout) self.top_controls_add_new_selector = QComboBox() self.top_controls_frequency_selector = QSpinBox() self.top_controls_frequency_selector.setValue(0) self.top_controls_add_new_viewer = QPushButton() self.top_controls_add_new_viewer.setText("Add Viewer") self.top_controls_add_new_viewer.clicked.connect(self.add_new_viewer_selected) self.top_controls_preview = QPushButton() self.top_controls_preview.setText("Preview") self.top_controls_preview.clicked.connect(self.preview_workflows) self.top_controls_execute = QPushButton() self.top_controls_execute.setText("Execute All") self.top_controls_execute.clicked.connect(self.execute_workflows) self.top_controls_add_new_selector.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) self.top_controls_add_new_viewer.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) self.top_controls_execute.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) self.top_controls_frequency_selector.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) self.top_controls_layout.addWidget(self.top_controls_add_new_selector) self.top_controls_layout.addWidget(self.top_controls_add_new_viewer) self.top_controls_layout.addWidget(self.top_controls_frequency_selector) self.top_controls_layout.addWidget(self.top_controls_preview) self.top_controls_layout.addWidget(self.top_controls_execute) self.top_controls.setLayout(self.top_controls_layout) self.status_bar = QLabel("None Loaded...") # Create a layout to organize our widgets # The first argument (which corresponds to the center widget) is required. catalog_viewer_layout = GUILayout(self._main_view, top=self.top_controls, right=self._active_workflow_widget, bottom=self.status_bar) # Create a "View" stage that has the catalog viewer layout self.stages = {"View": catalog_viewer_layout} self.active_workflows = [] # For classes derived from GUIPlugin, this super __init__ must occur at end super(TomographyPlugin, self).__init__(*args, **kwargs) def active_workflow_changed(self, index): self._editors.setCurrentIndex(index) if index == 0: self._workflow = self._tomo_workflow else: self._workflow = self._alt_workflow def preview_workflows(self): if len(self.active_filename) == 0: return preview_sinogram = self.top_controls_frequency_selector.value() workflows = [] for aw in range(self._active_workflow_executor.topLevelItemCount()): widget_item = self._active_workflow_executor.topLevelItem(aw) workflow = widget_item.data(0, Qt.UserRole) workflows.append(workflow) active_workflows = self.active_workflows for workflow in workflows: def results_ready(workflow, *results): output_image = results[0]["recon"] # We want the output_image from the last operation # print("RECON SHAPE", output_image.shape) for active_workflow in active_workflows: if active_workflow[1] == workflow: active_workflow[0].widget().setImage(output_image) # Update the result view widget workflow.execute(callback_slot=partial(results_ready, workflow), path=self.active_filename, sinoindex=preview_sinogram) def execute_workflows(self): if len(self.active_filename) == 0: return workflows = [] for aw in range(self._active_workflow_executor.topLevelItemCount()): widget_item = self._active_workflow_executor.topLevelItem(aw) workflow = widget_item.data(0, Qt.UserRole) workflows.append(workflow) active_workflows = self.active_workflows for workflow in workflows: def results_ready(workflow, *results): output_image = results[0]["recon"] # We want the output_image from the last operation # print("RECON SHAPE", output_image.shape) for active_workflow in active_workflows: if active_workflow[1] == workflow: active_workflow[0].widget().setImage(output_image) # Update the result view widget workflow.execute(callback_slot=partial(results_ready, workflow), path=self.active_filename) def add_new_viewer_selected(self, checked): if self.top_controls_add_new_selector.count() == 0: return selected = self.top_controls_add_new_selector.currentIndex() if selected < 0: return workflow = self.top_controls_add_new_selector.currentData(Qt.UserRole) print("WORKFLOW", workflow, workflow.name) dock_widget = QDockWidget() viewer = CatalogView() dock_widget.setWidget(viewer) dock_widget.setWindowTitle(workflow.name) if len(self.active_workflows) == 0: self._main_view.addDockWidget(Qt.RightDockWidgetArea, dock_widget) else: self._main_view.tabifyDockWidget(self.active_workflows[-1][0], dock_widget) self.active_workflows.append((dock_widget, workflow)) def appendHeader(self, header, **kwargs): filename = header._documents["start"][0]["filename"] self.active_filename = filename self.status_bar.setText("Loading Filename:" + filename) dataset, bkgs, drks = read_als_hdf5(filename) slice_widget = MyCatalogView() dw = QDockWidget() dw.setWidget(slice_widget) dw.setWindowTitle(filename) slice_widget.setImage(dataset) dock_children = self._main_view.findChildren(QDockWidget) print(dock_children) #if len(dock_children) == 0: # self._main_view.addDockWidget(Qt.LeftDockWidgetArea, dw) #else: # self._main_view.tabifyDockWidget(dock_children[0], dw) self._main_view.addDockWidget(Qt.LeftDockWidgetArea, dw) self.status_bar.setText("Done Loading Filename:" + filename) def appendCatalog(self, catalog, **kwargs): """Re-implemented from GUIPlugin - gives us access to a catalog reference You MUST implement this method if you want to load catalog data into your GUIPlugin. """ # Set the catalog viewer's catalog, stream, and field (so it knows what to display) # This is a quick and simple demonstration; stream and field should NOT be hardcoded # stream = "primary" # field = "img" # self._catalog_viewer.setCatalog(catalog, stream, field) print("CATALOG CALLED", catalog) pass def run_workflow(self): """Run the internal workflowi. In this example, this will be called whenever the "Run Workflow" in the WorkflowEditor is clicked. """ item = QTreeWidgetItem() workflow_name = self._workflow.name + ":" + str(self._active_workflow_executor.model().rowCount()) workflow = self._workflow.clone() workflow.name = workflow_name workflow._pretty_print() item.setText(0, workflow_name) item.setData(0, Qt.UserRole, workflow) self._active_workflow_executor.addTopLevelItem(item) self.top_controls_add_new_selector.addItem(workflow_name, userData=workflow)
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)
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)
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())
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()