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 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 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 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): 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 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 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 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 ExtendedTabWidget(QFrame): ''' Like QTabWidget, except the tab bar is located elsewhere. Intended for use with ExtendedTabBar. ''' def __init__(self): super(ExtendedTabWidget, self).__init__() self._tab_bar = None self._stack = QStackedWidget() self._main_layout = QBoxLayout(QBoxLayout.LeftToRight) self._main_layout.setContentsMargins(0, 0, 0, 0) self._main_layout.addWidget(self._stack) self.setLayout(self._main_layout) def _move_tab(self, from_, to): ''' Handles tab moves so that the tab bar indices stay aligned with the widget stack indices. ''' displaced_widget = self._stack.widget(from_) moved_widget = self._stack.widget(to) self._stack.removeWidget(moved_widget) self._stack.removeWidget(displaced_widget) self._stack.insertWidget(to, displaced_widget) self._stack.insertWidget(from_, moved_widget) self._stack.setCurrentIndex(self._tab_bar.currentIndex()) def setTabBar(self, tab_bar): ''' Sets the tab bar that will be used to switch between tabs. tab_bar The tab bar to set as the controller of this widget. ''' if self._tab_bar is not None: raise Exception('Tab bar already set.') self._tab_bar = tab_bar tab_bar.currentChanged.connect(self._stack.setCurrentIndex) tab_bar.tabCloseRequested.connect(self.closeTab) tab_bar.tabMoved.connect(self._move_tab) def closeTab(self, index): ''' Closes a tab, removing from this widget, the tab bar, and deleting its widget. index Index of the tab to be closed. ''' self._tab_bar.removeTab(index) widget = self._stack.widget(index) self._stack.removeWidget(widget) widget.deleteLater() self._stack.setCurrentIndex(self._tab_bar.currentIndex()) def addTab(self, widget, label): ''' Adds a tab. widget The widget for the tab contents. label The name of the tab to show in the tab bar. Returns the index of the added tab. ''' index = self._tab_bar.addTab(label) self._stack.insertWidget(index, widget) return index def count(self): ''' Returns the number of widgets. ''' return self._stack.count() def widget(self, index): ''' Returns the widget at the given index. ''' return self._stack.widget(index) def indexOf(self, widget): ''' Returns the index of the given widget. ''' return self._stack.indexOf(widget)
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())