def __init__(self, name, a, b, callback): QWidget.__init__(self) self.name = name self.callback = callback self.a = a self.b = b self.manually_triggered = False self.slider = QSlider() self.slider.setRange(0, 1000) self.slider.setValue(500) self.slider.valueChanged.connect(self.slider_changed) self.name_label = QLabel() self.name_label.setText(self.name) self.name_label.setAlignment(QtCore.Qt.AlignCenter) self.value_label = QLabel() self.value_label.setText('%2.2f' % (self.slider.value() * self.a + self.b)) self.value_label.setAlignment(QtCore.Qt.AlignCenter) self.layout = QGridLayout(self) self.layout.addWidget(self.name_label, 0, 0) self.layout.addWidget(self.slider, 1, 0, QtCore.Qt.AlignHCenter) self.layout.addWidget(self.value_label, 2, 0)
def fix_size(self, content, extra=50): """ Adjusts the width and height of the file switcher, based on its content. """ # Update size of dialog based on longest shortened path strings = [] if content: for rich_text in content: label = QLabel(rich_text) label.setTextFormat(Qt.PlainText) strings.append(label.text()) fm = label.fontMetrics() # Max width max_width = max([fm.width(s) * 1.3 for s in strings]) self.list.setMinimumWidth(max_width + extra) # Max height if len(strings) < 8: max_entries = len(strings) else: max_entries = 8 max_height = fm.height() * max_entries * 2.5 self.list.setMinimumHeight(max_height) # Set position according to size self.set_dialog_position()
def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.uid_label = QLabel() self.open_individually_button = QPushButton('Open individually') self.open_individually_button.hide() self.open_individually_button.clicked.connect(self._open_individually) self.open_overplotted_button = QPushButton('Open over-plotted') self.open_overplotted_button.hide() self.open_overplotted_button.clicked.connect(self._open_overplotted) self.open_overplotted_on_button = QPushButton('Add to tab...') self.open_overplotted_on_button.hide() self.open_overplotted_on_button.setEnabled(False) self.open_overplotted_on_button.clicked.connect(self._open_overplotted_on) self.copy_uid_button = QPushButton('Copy UID to Clipboard') self.copy_uid_button.hide() self.copy_uid_button.clicked.connect(self._copy_uid) self.streams = QLabel() self.entries = [] uid_layout = QHBoxLayout() uid_layout.addWidget(self.uid_label) uid_layout.addWidget(self.copy_uid_button) layout = QVBoxLayout() layout.addWidget(self.open_individually_button) layout.addWidget(self.open_overplotted_button) layout.addWidget(self.open_overplotted_on_button) layout.addLayout(uid_layout) layout.addWidget(self.streams) self.setLayout(layout) self._tab_titles = ()
def rebuild(self): """ Clear out all existing widgets, and populate the list using the template file and data source.""" self.clear() if (not self.templateFilename) or (not self.data): return self.setUpdatesEnabled(False) layout_class = layout_class_for_type[self.layoutType] if type(self.layout()) != layout_class: if self.layout() is not None: # Trick to remove the existing layout by re-parenting it in an empty widget. QWidget().setLayout(self.layout()) l = layout_class(self) self.setLayout(l) with pydm.data_plugins.connection_queue(): for i, variables in enumerate(self.data): if is_qt_designer() and i > self.countShownInDesigner - 1: break w = self.open_template_file(variables) if w is None: w = QLabel() w.setText("No Template Loaded. Data: {}".format(variables)) w.setParent(self) self.layout().addWidget(w) self.setUpdatesEnabled(True)
def _init_ui(self): # LINE 1: Data component drop down self.component_prompt = QLabel("Data Component:") self.component_prompt.setWordWrap(True) # Add the data component labels to the drop down, with the ComponentID # set as the userData: if self.parent is not None and hasattr(self.parent, 'data_components'): self.label_data = [(str(cid), cid) for cid in self.parent.data_components] else: self.label_data = [(str(cid), cid) for cid in self.data.visible_components] default_index = 0 self.component_combo = QComboBox() self.component_combo.setFixedWidth(200) update_combobox(self.component_combo, self.label_data, default_index=default_index) self.component_combo.currentIndexChanged.connect(self.update_unit_layout) # hbl is short for Horizontal Box Layout hbl1 = QHBoxLayout() hbl1.addWidget(self.component_prompt) hbl1.addWidget(self.component_combo) hbl1.addStretch(1) # LINE 2: Unit conversion layout # This layout is filled by CubeVizUnit self.unit_layout = QHBoxLayout() # this is hbl2 # LINE 3: Message box self.message_box = QLabel("") hbl3 = QHBoxLayout() hbl3.addWidget(self.message_box) hbl3.addStretch(1) # Line 4: Buttons ok_text = "Convert Data" if self.convert_data else "Convert Displayed Units" ok_function = self.convert_data_units if self.convert_data else self.convert_displayed_units self.okButton = QPushButton(ok_text) self.okButton.clicked.connect(ok_function) self.okButton.setDefault(True) self.cancelButton = QPushButton("Cancel") self.cancelButton.clicked.connect(self.cancel) hbl4 = QHBoxLayout() hbl4.addStretch(1) hbl4.addWidget(self.cancelButton) hbl4.addWidget(self.okButton) vbl = QVBoxLayout() vbl.addLayout(hbl1) vbl.addLayout(self.unit_layout) vbl.addLayout(hbl3) vbl.addLayout(hbl4) self.setLayout(vbl) self.vbl = vbl self.update_unit_layout(default_index) self.show()
def setup_page(self): # Connections group connections_group = QGroupBox(_("Automatic connections")) connections_label = QLabel(_("This pane can automatically " "show an object's help information after " "a left parenthesis is written next to it. " "Below you can decide to which plugin " "you want to connect it to turn on this " "feature.")) connections_label.setWordWrap(True) editor_box = self.create_checkbox(_("Editor"), 'connect/editor') rope_installed = programs.is_module_installed('rope') jedi_installed = programs.is_module_installed('jedi', '>=0.8.1') editor_box.setEnabled(rope_installed or jedi_installed) if not rope_installed and not jedi_installed: editor_tip = _("This feature requires the Rope or Jedi libraries.\n" "It seems you don't have either installed.") editor_box.setToolTip(editor_tip) ipython_box = self.create_checkbox(_("IPython Console"), 'connect/ipython_console') connections_layout = QVBoxLayout() connections_layout.addWidget(connections_label) connections_layout.addWidget(editor_box) connections_layout.addWidget(ipython_box) connections_group.setLayout(connections_layout) # Features group features_group = QGroupBox(_("Additional features")) math_box = self.create_checkbox(_("Render mathematical equations"), 'math') req_sphinx = programs.is_module_installed('sphinx', '>=1.1') math_box.setEnabled(req_sphinx) if not req_sphinx: sphinx_ver = programs.get_module_version('sphinx') sphinx_tip = _("This feature requires Sphinx 1.1 or superior.") sphinx_tip += "\n" + _("Sphinx %s is currently installed.") % sphinx_ver math_box.setToolTip(sphinx_tip) features_layout = QVBoxLayout() features_layout.addWidget(math_box) features_group.setLayout(features_layout) # Source code group sourcecode_group = QGroupBox(_("Source code")) wrap_mode_box = self.create_checkbox(_("Wrap lines"), 'wrap') sourcecode_layout = QVBoxLayout() sourcecode_layout.addWidget(wrap_mode_box) sourcecode_group.setLayout(sourcecode_layout) # Final layout vlayout = QVBoxLayout() vlayout.addWidget(connections_group) vlayout.addWidget(features_group) vlayout.addWidget(sourcecode_group) vlayout.addStretch(1) self.setLayout(vlayout)
def _init_ui(self): self.slit_type_label = QLabel('Slit Type') self.slit_type_combo = QComboBox() self.slit_type_combo.currentIndexChanged.connect(self.update_info) hbl1 = QHBoxLayout() hbl1.addWidget(self.slit_type_label) hbl1.addWidget(self.slit_type_combo) self.slit_width_label = QLabel('Slit Width') self.slit_width_input = QLineEdit() self.slit_width_combo = QComboBox() self.slit_width_units = QLabel('arcsec') hbl2 = QHBoxLayout() hbl2.addWidget(self.slit_width_label) hbl2.addWidget(self.slit_width_input) hbl2.addWidget(self.slit_width_combo) hbl2.addWidget(self.slit_width_units) self.slit_length_label = QLabel('Slit Length') self.slit_length_input = QLineEdit() self.slit_length_combo = QComboBox() self.slit_length_units = QLabel('arcsec') hbl3 = QHBoxLayout() hbl3.addWidget(self.slit_length_label) hbl3.addWidget(self.slit_length_input) hbl3.addWidget(self.slit_length_combo) hbl3.addWidget(self.slit_length_units) self.okButton = QPushButton('Apply') self.okButton.clicked.connect(self.apply) self.okButton.setDefault(True) self.cancelButton = QPushButton('Cancel') self.cancelButton.clicked.connect(self.cancel) hbl4 = QHBoxLayout() hbl4.addWidget(self.cancelButton) hbl4.addWidget(self.okButton) vbl = QVBoxLayout() vbl.addLayout(hbl1) vbl.addLayout(hbl2) vbl.addLayout(hbl3) vbl.addLayout(hbl4) self.setLayout(vbl) self.vbl = vbl self._load_selections() self._populate_combo() self.update_info(0) self.show()
class EncodingStatus(StatusBarWidget): def __init__(self, parent, statusbar): StatusBarWidget.__init__(self, parent, statusbar) layout = self.layout() layout.addWidget(QLabel(_("Encoding:"))) self.encoding = QLabel() self.encoding.setFont(self.label_font) layout.addWidget(self.encoding) layout.addSpacing(20) def encoding_changed(self, encoding): self.encoding.setText(str(encoding).upper().ljust(15))
def __init__(self, parent, statusbar): StatusBarWidget.__init__(self, parent, statusbar) layout = self.layout() layout.addWidget(QLabel(_("Line:"))) self.line = QLabel() self.line.setFont(self.label_font) layout.addWidget(self.line) layout.addWidget(QLabel(_("Column:"))) self.column = QLabel() self.column.setFont(self.label_font) layout.addWidget(self.column) self.setLayout(layout)
class QLabeledSlider(QWidget): """ A labeled slider widget """ range = None integer = None def __init__(self, parent=None): super(QLabeledSlider, self).__init__(parent) self._range = range self._slider = QSlider() self._slider.setMinimum(0) self._slider.setMaximum(100) self._slider.setOrientation(Qt.Horizontal) self._label = QLabel('') self._layout = QHBoxLayout() self._layout.setContentsMargins(2, 2, 2, 2) self._layout.addWidget(self._slider) self._layout.addWidget(self._label) self._slider.valueChanged.connect(self._update_label) self.setLayout(self._layout) def _update_label(self, *args): self._label.setText(str(self.value())) @property def valueChanged(self): return self._slider.valueChanged def value(self, layer=None, view=None): value = self._slider.value() / 100. * (self.range[1] - self.range[0]) + self.range[0] if self.integer: return int(value) else: return(value) _in_set_value = False def setValue(self, value): if self._in_set_value: return self._in_set_value = True value = int(100 * (value - self.range[0]) / (self.range[1] - self.range[0])) self._slider.setValue(value) self._in_set_value = False
def fix_size(self, content, extra=50): """Adjusts the width of the file switcher, based on the content.""" # Update size of dialog based on longest shortened path strings = [] if content: for rich_text in content: label = QLabel(rich_text) label.setTextFormat(Qt.PlainText) strings.append(label.text()) fm = label.fontMetrics() max_width = max([fm.width(s)*1.3 for s in strings]) self.list.setMinimumWidth(max_width + extra) self.set_dialog_position()
class ReadWriteStatus(StatusBarWidget): def __init__(self, parent, statusbar): StatusBarWidget.__init__(self, parent, statusbar) layout = self.layout() layout.addWidget(QLabel(_("Permissions:"))) self.readwrite = QLabel() self.readwrite.setFont(self.label_font) layout.addWidget(self.readwrite) layout.addSpacing(20) def readonly_changed(self, readonly): readwrite = "R" if readonly else "RW" self.readwrite.setText(readwrite.ljust(3))
class EOLStatus(StatusBarWidget): def __init__(self, parent, statusbar): StatusBarWidget.__init__(self, parent, statusbar) layout = self.layout() layout.addWidget(QLabel(_("End-of-lines:"))) self.eol = QLabel() self.eol.setFont(self.label_font) layout.addWidget(self.eol) layout.addSpacing(20) def eol_changed(self, os_name): os_name = to_text_string(os_name) self.eol.setText({"nt": "CRLF", "posix": "LF"}.get(os_name, "CR"))
class SearchLineEdit(QLineEdit): """Line edit search widget with icon and remove all button""" def __init__(self, parent=None, icon=True): super(SearchLineEdit, self).__init__(parent) self.setTextMargins(1, 0, 20, 0) if icon: self.setTextMargins(18, 0, 20, 0) self._label = QLabel(self) self._pixmap_icon = QPixmap(get_image_path('conda_search.png')) self._label.setPixmap(self._pixmap_icon) self._label.setStyleSheet('''border: 0px; padding-bottom: 0px; padding-left: 2px;''') self._pixmap = QPixmap(get_image_path('conda_del.png')) self.button_clear = QToolButton(self) self.button_clear.setIcon(QIcon(self._pixmap)) self.button_clear.setIconSize(QSize(18, 18)) self.button_clear.setCursor(Qt.ArrowCursor) self.button_clear.setStyleSheet("""QToolButton {background: transparent; padding-right: 2px; border: none; margin:0px; }""") self.button_clear.setVisible(False) # Layout self._layout = QHBoxLayout(self) self._layout.addWidget(self.button_clear, 0, Qt.AlignRight) self._layout.setSpacing(0) self._layout.setContentsMargins(0, 2, 2, 0) # Signals and slots self.button_clear.clicked.connect(self.clear_text) self.textChanged.connect(self._toggle_visibility) self.textEdited.connect(self._toggle_visibility) def _toggle_visibility(self): """ """ if len(self.text()) == 0: self.button_clear.setVisible(False) else: self.button_clear.setVisible(True) def sizeHint(self): return QSize(200, self._pixmap_icon.height()) # Public api # ---------- def clear_text(self): """ """ self.setText('') self.setFocus()
class CursorPositionStatus(StatusBarWidget): """Status bar widget for the current file cursor postion.""" def __init__(self, parent, statusbar): """Status bar widget for the current file cursor postion.""" super(CursorPositionStatus, self).__init__(parent, statusbar) # Widget self.label_line = QLabel(_("Line:")) self.label_column = QLabel(_("Column:")) self.column = QLabel() self.line = QLabel() # Widget setup self.line.setFont(self.label_font) self.column.setFont(self.label_font) # Layout layout = self.layout() layout.addWidget(self.label_line) layout.addWidget(self.line) layout.addWidget(self.label_column) layout.addWidget(self.column) self.setLayout(layout) def cursor_position_changed(self, line, index): """Update cursos position.""" self.line.setText("%-6d" % (line+1)) self.column.setText("%-4d" % (index+1))
def setup_page(self): settings_group = QGroupBox(_("Settings")) use_color_box = self.create_checkbox( _("Use deterministic colors to differentiate functions"), 'use_colors', default=True) results_group = QGroupBox(_("Results")) results_label1 = QLabel(_("Memory profiler plugin results " "(the output of memory_profiler)\n" "is stored here:")) results_label1.setWordWrap(True) # 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 of Profiler plugon results_label2 = QLabel(MemoryProfilerWidget.DATAPATH) results_label2.setTextInteractionFlags(Qt.TextSelectableByMouse) results_label2.setWordWrap(True) settings_layout = QVBoxLayout() settings_layout.addWidget(use_color_box) settings_group.setLayout(settings_layout) results_layout = QVBoxLayout() results_layout.addWidget(results_label1) results_layout.addWidget(results_label2) results_group.setLayout(results_layout) vlayout = QVBoxLayout() vlayout.addWidget(settings_group) vlayout.addWidget(results_group) vlayout.addStretch(1) self.setLayout(vlayout)
def get_item_size(self, content): """ Get the max size (width and height) for the elements of a list of strings as a QLabel. """ strings = [] if content: for rich_text in content: label = QLabel(rich_text) label.setTextFormat(Qt.PlainText) strings.append(label.text()) fm = label.fontMetrics() return (max([fm.width(s) * 1.3 for s in strings]), fm.height())
class ConnectionInspector(QWidget): def __init__(self, parent=None): super(ConnectionInspector, self).__init__(parent, Qt.Window) connections = self.fetch_data() self.table_view = ConnectionTableView(connections, self) self.setLayout(QVBoxLayout(self)) self.layout().addWidget(self.table_view) button_layout = QHBoxLayout() self.layout().addItem(button_layout) self.save_status_label = QLabel(self) button_layout.addWidget(self.save_status_label) button_layout.addStretch() self.save_button = QPushButton(self) self.save_button.setText("Save list to file...") self.save_button.clicked.connect(self.save_list_to_file) button_layout.addWidget(self.save_button) self.update_timer = QTimer(parent=self) self.update_timer.setInterval(1500) self.update_timer.timeout.connect(self.update_data) self.update_timer.start() def update_data(self): self.table_view.model().connections = self.fetch_data() def fetch_data(self): plugins = data_plugins.plugin_modules return [connection for p in plugins.values() for connection in p.connections.values() ] @Slot() def save_list_to_file(self): filename, filters = QFileDialog.getSaveFileName(self, "Save connection list", "", "Text Files (*.txt)") try: with open(filename, "w") as f: for conn in self.table_view.model().connections: f.write( "{p}://{a}\n".format(p=conn.protocol, a=conn.address)) self.save_status_label.setText("File saved to {}".format(filename)) except Exception as e: msgBox = QMessageBox() msgBox.setText("Couldn't save connection list to file.") msgBox.setInformativeText("Error: {}".format(str(e))) msgBox.setStandardButtons(QMessageBox.Ok) msgBox.exec_()
class StatusBarWidget(QWidget): """Status bar widget base.""" TIP = None def __init__(self, parent, statusbar, icon=None): """Status bar widget base.""" super(StatusBarWidget, self).__init__(parent) # Variables self.value = None # Widget self._icon = icon self._pixmap = icon.pixmap(QSize(16, 16)) if icon is not None else None self.label_icon = QLabel() if icon is not None else None self.label_value = QLabel() # Widget setup if icon is not None: self.label_icon.setPixmap(self._pixmap) self.text_font = QFont(get_font(option='font')) # See Issue #9044 self.text_font.setPointSize(self.font().pointSize()) self.text_font.setBold(True) self.label_value.setAlignment(Qt.AlignRight) self.label_value.setFont(self.text_font) if self.TIP: self.setToolTip(self.TIP) self.label_value.setToolTip(self.TIP) # Layout layout = QHBoxLayout() if icon is not None: layout.addWidget(self.label_icon) layout.addWidget(self.label_value) layout.addSpacing(20) # Layout setup layout.setContentsMargins(0, 0, 0, 0) self.setLayout(layout) # Setup statusbar.addPermanentWidget(self) def set_value(self, value): """Set formatted text value.""" self.value = value if self.isVisible(): self.label_value.setText(value)
def __init__(self, parent=None, init_channel=None): QLabel.__init__(self, parent) PyDMWidget.__init__(self, init_channel=init_channel) if 'Text' not in PyDMLabel.RULE_PROPERTIES: PyDMLabel.RULE_PROPERTIES = PyDMWidget.RULE_PROPERTIES.copy() PyDMLabel.RULE_PROPERTIES.update( {'Text': ['value_changed', str]}) self.app = QApplication.instance() self.setTextFormat(Qt.PlainText) self.setTextInteractionFlags(Qt.NoTextInteraction) self.setText("PyDMLabel") self._display_format_type = self.DisplayFormat.Default self._string_encoding = "utf_8" if is_pydm_app(): self._string_encoding = self.app.get_string_encoding()
class ProjectExplorerTest(QWidget): def __init__(self): QWidget.__init__(self) vlayout = QVBoxLayout() self.setLayout(vlayout) self.explorer = ProjectExplorerWidget(None, show_all=True) self.explorer.setup_project(osp.dirname(osp.abspath(__file__))) vlayout.addWidget(self.explorer) hlayout1 = QHBoxLayout() vlayout.addLayout(hlayout1) label = QLabel("<b>Open file:</b>") label.setAlignment(Qt.AlignRight) hlayout1.addWidget(label) self.label1 = QLabel() hlayout1.addWidget(self.label1) self.explorer.sig_open_file.connect(self.label1.setText) hlayout3 = QHBoxLayout() vlayout.addLayout(hlayout3) label = QLabel("<b>Option changed:</b>") label.setAlignment(Qt.AlignRight) hlayout3.addWidget(label) self.label3 = QLabel() hlayout3.addWidget(self.label3) self.explorer.sig_option_changed.connect( lambda x, y: self.label3.setText('option_changed: %r, %r' % (x, y)))
def __init__(self, parent, statusbar): """Status bar widget base for widgets that update based on timers.""" super(BaseTimerStatus, self).__init__(parent, statusbar) # Widgets self.label = QLabel(self.TITLE) self.value = QLabel() # Widget setup self.setToolTip(self.TIP) self.value.setAlignment(Qt.AlignRight) self.value.setFont(self.label_font) fm = self.value.fontMetrics() self.value.setMinimumWidth(fm.width('000%')) # Layout layout = self.layout() layout.addWidget(self.label) layout.addWidget(self.value) layout.addSpacing(20) # Setup if self.is_supported(): self.timer = QTimer() self.timer.timeout.connect(self.update_label) self.timer.start(2000) else: self.timer = None self.hide()
def __init__(self, parent=None, icon=True): super(SearchLineEdit, self).__init__(parent) self.setTextMargins(1, 0, 20, 0) if icon: self.setTextMargins(18, 0, 20, 0) self._label = QLabel(self) self._pixmap_icon = QPixmap(get_image_path('conda_search.png')) self._label.setPixmap(self._pixmap_icon) self._label.setStyleSheet('''border: 0px; padding-bottom: 0px; padding-left: 2px;''') self._pixmap = QPixmap(get_image_path('conda_del.png')) self.button_clear = QToolButton(self) self.button_clear.setIcon(QIcon(self._pixmap)) self.button_clear.setIconSize(QSize(18, 18)) self.button_clear.setCursor(Qt.ArrowCursor) self.button_clear.setStyleSheet("""QToolButton {background: transparent; padding-right: 2px; border: none; margin:0px; }""") self.button_clear.setVisible(False) # Layout self._layout = QHBoxLayout(self) self._layout.addWidget(self.button_clear, 0, Qt.AlignRight) self._layout.setSpacing(0) self._layout.setContentsMargins(0, 2, 2, 0) # Signals and slots self.button_clear.clicked.connect(self.clear_text) self.textChanged.connect(self._toggle_visibility) self.textEdited.connect(self._toggle_visibility)
def __init__(self, dim_info, number=0, state=State.NONE, parent=None): # hack in a number_of_bins for MDEventWorkspace dim_info['number_of_bins'] = 1000 dim_info['width'] = (dim_info['maximum']-dim_info['minimum'])/1000 self.spinBins = QSpinBox() self.spinBins.setRange(2,9999) self.spinBins.setValue(100) self.spinBins.hide() self.spinBins.setMinimumWidth(110) self.spinThick = QDoubleSpinBox() self.spinThick.setRange(0.001,999) self.spinThick.setValue(0.1) self.spinThick.setSingleStep(0.1) self.spinThick.setDecimals(3) self.spinThick.setMinimumWidth(110) self.rebinLabel = QLabel("thick") self.rebinLabel.setMinimumWidth(44) super(DimensionMDE, self).__init__(dim_info, number, state, parent) self.spinBins.valueChanged.connect(self.binningChanged) self.spinThick.valueChanged.connect(self.valueChanged) self.layout.addWidget(self.spinBins) self.layout.addWidget(self.spinThick) self.layout.addWidget(self.rebinLabel)
def __init__(self, parent=None): super().__init__(parent) self.settings = QSettings() # decimal self.decimalLabel = QLabel(self.tr("decimal:")) self.decimalComboBox = QComboBox() self.decimalLabel.setBuddy(self.decimalComboBox) self.decimalComboBox.addItems([",", "."]) # separator self.separatorLabel = QLabel(self.tr("separator:")) self.separatorComboBox = QComboBox() self.separatorLabel.setBuddy(self.separatorComboBox) self.separatorComboBox.addItem("Semicolon ';'", ';') self.separatorComboBox.addItem("Comma ','", ',') self.separatorComboBox.addItem("Tabulator '\\t'", '\t') self.separatorComboBox.addItem("Whitespace ' '", ' ') # buttons self.buttons = QDialogButtonBox( QDialogButtonBox.Ok | QDialogButtonBox.Cancel ) self.buttons.accepted.connect(self.accept) self.buttons.rejected.connect(self.reject) # layout layout = QGridLayout() layout.addWidget(self.decimalLabel, 0, 0) layout.addWidget(self.decimalComboBox, 0, 1) layout.addWidget(self.separatorLabel, 1, 0) layout.addWidget(self.separatorComboBox, 1, 1) layout.addWidget(self.buttons, 2, 0, 1, 2) self.setLayout(layout) # settings self.decimalComboBox.setCurrentIndex( self.decimalComboBox.findText( self.settings.value(DECIMAL_SETTING, ",")) ) self.separatorComboBox.setCurrentIndex( self.separatorComboBox.findData( self.settings.value(SEPARATOR_SETTING, ";")) ) self.setWindowTitle(self.tr("record settings"))
def __init__(self, parent, statusbar): StatusBarWidget.__init__(self, parent, statusbar) layout = self.layout() layout.addWidget(QLabel(_("End-of-lines:"))) self.eol = QLabel() self.eol.setFont(self.label_font) layout.addWidget(self.eol) layout.addSpacing(20)
def __init__(self, parent, statusbar): """Status bar widget for the current file end of line.""" super(EOLStatus, self).__init__(parent, statusbar) # Widget self.label = QLabel(_("End-of-lines:")) self.eol = QLabel() # Widget setup self.label.setAlignment(Qt.AlignRight) self.eol.setFont(self.label_font) # Layouts layout = self.layout() layout.addWidget(self.label) layout.addWidget(self.eol) layout.addSpacing(20)
def __init__(self, parent, statusbar): StatusBarWidget.__init__(self, parent, statusbar) layout = self.layout() layout.addWidget(QLabel(_("Encoding:"))) self.encoding = QLabel() self.encoding.setFont(self.label_font) layout.addWidget(self.encoding) layout.addSpacing(20)
def __init__(self, parent, statusbar): """Status bar widget for the current file encoding.""" super(EncodingStatus, self).__init__(parent, statusbar) # Widgets self.label = QLabel(_("Encoding:")) self.encoding = QLabel() # Widget setup self.label.setAlignment(Qt.AlignRight) self.encoding.setFont(self.label_font) # Layouts layout = self.layout() layout.addWidget(self.label) layout.addWidget(self.encoding) layout.addSpacing(20)
def __init__(self, parent, statusbar): """Status bar widget for current file read/write mode.""" super(ReadWriteStatus, self).__init__(parent, statusbar) # Widget self.label = QLabel(_("Permissions:")) self.readwrite = QLabel() # Widget setup self.label.setAlignment(Qt.AlignRight) self.readwrite.setFont(self.label_font) # Layouts layout = self.layout() layout.addWidget(self.label) layout.addWidget(self.readwrite) layout.addSpacing(20)
def _get_acq_commom_params_grpbx(self): grp_bx = QGroupBox('Common Parameters', self) fbl = QFormLayout(grp_bx) lbl = QLabel('Non-linear Corr.', grp_bx, alignment=Qt.AlignCenter) wid = self.create_pair_butled(grp_bx, 'PolyCalibration') fbl.addRow(lbl, wid) self._set_detailed([lbl, wid]) lbl = QLabel('Channel Rate', grp_bx, alignment=Qt.AlignCenter) wid = self.create_pair_sel(grp_bx, 'TrigAcqChan') fbl.addRow(lbl, wid) lbl = QLabel('Trigger Type', grp_bx, alignment=Qt.AlignCenter) wid = self.create_pair_sel(grp_bx, 'TrigAcqTrigger') fbl.addRow(lbl, wid) self._set_detailed([lbl, wid]) lbl = QLabel('Repeat', grp_bx, alignment=Qt.AlignCenter) wid = self.create_pair_butled(grp_bx, 'TrigAcqRepeat') fbl.addRow(lbl, wid) self._set_detailed([lbl, wid]) if self.isring: lbl = QLabel('Nr of Shots', grp_bx, alignment=Qt.AlignCenter) wid = self.create_pair(grp_bx, 'TrigNrShots') fbl.addRow(lbl, wid) self._set_detailed([lbl, wid]) lbl = QLabel('SamplesPre', grp_bx, alignment=Qt.AlignCenter) wid = self.create_pair(grp_bx, 'TrigNrSamplesPre') fbl.addRow(lbl, wid) lbl = QLabel('SamplesPost', grp_bx, alignment=Qt.AlignCenter) wid = self.create_pair(grp_bx, 'TrigNrSamplesPost') fbl.addRow(lbl, wid) lbl = QLabel('Acquisition:', grp_bx, alignment=Qt.AlignCenter) strt = PyDMPushButton( grp_bx, label='', init_channel=self.devpref.substitute(propty='TrigAcqCtrl-Sel'), pressValue=self._csorb.TrigAcqCtrl.Start) strt.setToolTip('Start Acquisition') strt.setIcon(qta.icon('fa5s.play')) strt.setObjectName('strt') strt.setStyleSheet( '#strt{min-width:25px; max-width:25px; icon-size:20px;}') stop = PyDMPushButton( grp_bx, label='', init_channel=self.devpref.substitute(propty='TrigAcqCtrl-Sel'), pressValue=self._csorb.TrigAcqCtrl.Stop) stop.setToolTip('Stop Acquisition') stop.setIcon(qta.icon('fa5s.stop')) stop.setObjectName('stop') stop.setStyleSheet( '#stop{min-width:25px; max-width:25px; icon-size:20px;}') abrt = PyDMPushButton( grp_bx, label='', init_channel=self.devpref.substitute(propty='TrigAcqCtrl-Sel'), pressValue=self._csorb.TrigAcqCtrl.Abort) abrt.setToolTip('Abort Acquisition') abrt.setIcon(qta.icon('fa5s.ban')) abrt.setObjectName('abrt') abrt.setStyleSheet( '#abrt{min-width:25px; max-width:25px; icon-size:20px;}') pdmlbl = PyDMLabel(grp_bx, self.devpref.substitute(propty='TrigAcqCtrl-Sts')) pdmlbl.setObjectName('pdmlbl') pdmlbl.setStyleSheet('#pdmlbl{min-width:6em; max-width:6em;}') pdmlbl.setAlignment(Qt.AlignCenter) hbl = QHBoxLayout() fbl.addRow(hbl) hbl.addStretch() hbl.addWidget(lbl) hbl.addWidget(strt) hbl.addWidget(stop) hbl.addWidget(abrt) hbl.addWidget(pdmlbl) conf = PyDMPushButton( grp_bx, pressValue=1, init_channel=self.devpref.substitute(propty='TrigAcqConfig-Cmd')) conf.setToolTip('Resend Configurations') conf.setIcon(qta.icon('fa5s.sync')) conf.setObjectName('conf') conf.setStyleSheet( '#conf{min-width:25px; max-width:25px; icon-size:20px;}') sts = QPushButton('', grp_bx) sts.setIcon(qta.icon('fa5s.list-ul')) sts.setToolTip('Open Detailed Status View') sts.setObjectName('sts') sts.setStyleSheet( '#sts{min-width:25px; max-width:25px; icon-size:20px;}') Window = create_window_from_widget(StatusWidget, title='Orbit Status') connect_window(sts, Window, grp_bx, device=self.device, prefix=self.prefix, acc=self.acc, is_orb=True) pdm_led = SiriusLedAlert( grp_bx, self.devpref.substitute(propty='OrbStatus-Mon')) lbl = QLabel('Status:', grp_bx) hbl = QHBoxLayout() hbl.setSpacing(9) hbl.addStretch() hbl.addWidget(lbl) hbl.addWidget(pdm_led) hbl.addWidget(sts) hbl.addWidget(conf) fbl.addRow(hbl) return grp_bx
def __init__(self, name=None, plugin=None, parent=None): super().__init__(name, plugin, parent=parent) self.set_conf('text_color', MAIN_TEXT_COLOR) self.set_conf('hist_limit', MAX_PATH_HISTORY) # Attributes self.text_color = self.get_conf('text_color') self.supported_encodings = self.get_conf('supported_encodings') self.search_thread = None self.running = False self.more_options_action = None self.extras_toolbar = None search_text = self.get_conf('search_text', '') path_history = self.get_conf('path_history', []) exclude = self.get_conf('exclude') if not isinstance(search_text, (list, tuple)): search_text = [search_text] if not isinstance(exclude, (list, tuple)): exclude = [exclude] if not isinstance(path_history, (list, tuple)): path_history = [path_history] # Widgets self.search_text_edit = PatternComboBox( self, search_text, id_=FindInFilesWidgetToolbarItems.SearchPatternCombo) self.search_text_edit.lineEdit().setPlaceholderText( _('Write text to search')) self.search_in_label = QLabel(_('Search in:')) self.search_in_label.ID = FindInFilesWidgetToolbarItems.SearchInLabel self.exclude_label = QLabel(_('Exclude:')) self.exclude_label.ID = FindInFilesWidgetToolbarItems.ExcludeLabel self.path_selection_combo = SearchInComboBox( path_history, self, id_=FindInFilesWidgetToolbarItems.SearchInCombo) self.exclude_pattern_edit = PatternComboBox( self, exclude, _("Exclude pattern"), id_=FindInFilesWidgetToolbarItems.ExcludePatternCombo) self.result_browser = ResultsBrowser( self, text_color=self.text_color, max_results=self.get_conf('max_results'), ) # Setup self.exclude_label.setBuddy(self.exclude_pattern_edit) exclude_idx = self.get_conf('exclude_index', None) if (exclude_idx is not None and exclude_idx >= 0 and exclude_idx < self.exclude_pattern_edit.count()): self.exclude_pattern_edit.setCurrentIndex(exclude_idx) search_in_index = self.get_conf('search_in_index', None) self.path_selection_combo.set_current_searchpath_index(search_in_index) # Layout layout = QHBoxLayout() layout.addWidget(self.result_browser) self.setLayout(layout) # Signals self.path_selection_combo.sig_redirect_stdio_requested.connect( self.sig_redirect_stdio_requested) self.search_text_edit.valid.connect(lambda valid: self.find()) self.exclude_pattern_edit.valid.connect(lambda valid: self.find()) self.result_browser.sig_edit_goto_requested.connect( self.sig_edit_goto_requested) self.result_browser.sig_max_results_reached.connect( self.sig_max_results_reached) self.result_browser.sig_max_results_reached.connect( self._stop_and_reset_thread) self.search_text_edit.sig_resized.connect(self._update_size)
def __init__(self, parent): super(KiteIntegrationInfo, self).__init__(parent) # Images images_layout = QHBoxLayout() icon_filename = 'kite_completions' image_path = get_image_path(icon_filename) image = QPixmap(image_path) image_label = QLabel() screen = QApplication.primaryScreen() image_label = QLabel() image_height = int(image.height() * self.ICON_SCALE_FACTOR) image_width = int(image.width() * self.ICON_SCALE_FACTOR) image = image.scaled(image_width, image_height, Qt.KeepAspectRatio, Qt.SmoothTransformation) image_label.setPixmap(image) images_layout.addStretch() images_layout.addWidget(image_label) images_layout.addStretch() ilayout = QHBoxLayout() ilayout.addLayout(images_layout) # Label integration_label_title = QLabel( "Get better code completions in Spyder") integration_label_title.setStyleSheet( f"font-size: {self.TITLE_FONT_SIZE}") integration_label_title.setWordWrap(True) integration_label = QLabel( _("Now Spyder can use Kite to provide better code " "completions for key packages in the scientific Python " "Ecosystem. Install Kite for a better editor experience in " "Spyder. <br><br>Kite is free to use but is not open " "source. <a href=\"{kite_url}\">Learn more about Kite </a>"). format(kite_url=KITE_SPYDER_URL)) integration_label.setStyleSheet(f"font-size: {self.CONTENT_FONT_SIZE}") integration_label.setOpenExternalLinks(True) integration_label.setWordWrap(True) integration_label.setFixedWidth(360) label_layout = QVBoxLayout() label_layout.addWidget(integration_label_title) label_layout.addWidget(integration_label) # Buttons buttons_layout = QHBoxLayout() install_button = QPushButton(_('Install Kite')) install_button.setAutoDefault(False) install_button.setStyleSheet( f"background-color: {QStylePalette.COLOR_ACCENT_3};" f"font-size: {self.BUTTONS_FONT_SIZE};" f"padding: {self.BUTTONS_PADDING}") dismiss_button = QPushButton(_('Dismiss')) dismiss_button.setAutoDefault(False) dismiss_button.setStyleSheet( f"background-color: {QStylePalette.COLOR_BACKGROUND_6};" f"font-size: {self.BUTTONS_FONT_SIZE};" f"padding: {self.BUTTONS_PADDING}") buttons_layout.addStretch() buttons_layout.addWidget(install_button) if not MAC: buttons_layout.addSpacing(10) buttons_layout.addWidget(dismiss_button) # Buttons with label vertical_layout = QVBoxLayout() if not MAC: vertical_layout.addStretch() vertical_layout.addLayout(label_layout) vertical_layout.addSpacing(20) vertical_layout.addLayout(buttons_layout) vertical_layout.addStretch() else: vertical_layout.addLayout(label_layout) vertical_layout.addLayout(buttons_layout) general_layout = QHBoxLayout() general_layout.addStretch() general_layout.addLayout(ilayout) general_layout.addSpacing(15) general_layout.addLayout(vertical_layout) general_layout.addStretch() self.setLayout(general_layout) # Signals install_button.clicked.connect(self.sig_install_button_clicked) dismiss_button.clicked.connect(self.sig_dismiss_button_clicked) self.setStyleSheet( f"background-color: {QStylePalette.COLOR_BACKGROUND_2}") self.setContentsMargins(18, 40, 18, 40) if not MAC: self.setFixedSize(800, 350)
class ParamSweepWidget(QDialog): """ A dialog box containing a workflow finished message. Also includes a button to open the output folder and a button to close the dialog box Params: output_folder (Path): The output folder to open when the corresponding button is clicked by the user. """ def __init__(self, param_set: Dict[str, Any], step_number, controller): super().__init__() # Track UI elements self.live_count: QLabel = None self.progress_bar: QProgressBar = None self.inputs: Dict[str, QFrame] = dict() self.default_sweep_values: Dict = dict() self.layout: QVBoxLayout = QVBoxLayout() # State self.controller = controller self.step_number: int = step_number self.param_set: Dict[str, Any] = param_set rows: List[FormRow] = self._create_sweep_ui() # Format UI on init self.layout.setContentsMargins(0, 0, 0, 0) self.layout.addWidget(self._create_buttons()) self.setLayout(Form(rows)) self.setStyleSheet(get_stylesheet(self.controller.viewer.get_theme())) self.setWindowTitle("Parameter Sweep") def _create_sweep_ui(self) -> List[FormRow]: """ Populate the sweep widget with this workflow step's parameters and their default values as well as other UI elements needed to run a sweep. Params: none """ rows: List[FormRow] = list() # add ui elements in order rows.append(FormRow("", widget=self.create_sweep_headers())) # convert parameter set to form rows default_params: Dict = self.controller.model.active_workflow.workflow_definition.steps[ self.step_number].function.parameters if self.param_set: for key, value in self.param_set.items(): # sometimes multiple unique params are in one list, need to separate out for UI if isinstance(value, list): if not isinstance(value[0], str): i = 1 for _ in value: sweep_inputs: QFrame = QFrame() sweep_inputs.setLayout(QHBoxLayout()) # get default value min_value: int = default_params[key][i - 1].min_value max_value: int = default_params[key][i - 1].max_value step_size: float = (max_value - min_value) / 2 # Create UI Elements and populate with default values min_input: QLineEdit = QLineEdit() min_input.setText(str(min_value)) min_input.editingFinished.connect( self._on_change_textbox) sweep_inputs.layout().addWidget(min_input) max_input: QLineEdit = QLineEdit() max_input.setText(str(max_value)) max_input.editingFinished.connect( self._on_change_textbox) sweep_inputs.layout().addWidget(max_input) step_input = QLineEdit() step_input.setText(str(step_size)) step_input.editingFinished.connect( self._on_change_textbox) sweep_inputs.layout().addWidget(step_input) # Reset button for row (reset to default values) reset_button: QPushButton = QPushButton("reset") reset_button.setStyleSheet("border: none;") # pass the key and value as values for calling function later on reset_button.clicked.connect( partial( lambda k, val: self._reset_row_to_default( k, val), key, i)) sweep_inputs.layout().addWidget(reset_button) # store the index of this parameter in its list appended to the parameter key self.inputs[key + str(i)] = sweep_inputs rows.append( FormRow(f"{key} {i}", widget=sweep_inputs)) i = i + 1 else: # most params are single entries in the param dictionary # for params that are a dropdown if default_params[key][0].widget_type.name == "DROPDOWN": dropdown = QComboBox() dropdown.setStyleSheet( "QComboBox { combobox-popup: 0; }") dropdown.addItems(default_params[key][0].options) self.inputs[key] = dropdown rows.append(FormRow(key, widget=dropdown)) else: # for typical sweep params sweep_inputs: QFrame = QFrame() sweep_inputs.setLayout(QHBoxLayout()) # get the default values min_value: int = default_params[key][0].min_value max_value: int = default_params[key][0].max_value step_size: float = (max_value - min_value) / 2 # Create UI elements and populate with default values min_input: QLineEdit = QLineEdit() min_input.setText(str(min_value)) min_input.editingFinished.connect( self._on_change_textbox) sweep_inputs.layout().addWidget(min_input) max_input: QLineEdit = QLineEdit() max_input.setText(str(max_value)) max_input.editingFinished.connect( self._on_change_textbox) sweep_inputs.layout().addWidget(max_input) step_input: QLineEdit = QLineEdit() step_input.setText(str(step_size)) step_input.editingFinished.connect( self._on_change_textbox) sweep_inputs.layout().addWidget(step_input) self.inputs[key] = sweep_inputs # Create button to reset back to default values reset_button: QPushButton = QPushButton("reset") reset_button.setStyleSheet("border: none;") reset_button.clicked.connect( lambda: self._reset_row_to_default(key)) sweep_inputs.layout().addWidget(reset_button) rows.append(FormRow(key, widget=sweep_inputs)) # Grab user input as str def_params_values: List[List[str]] = self.grab_ui_values( grab_combo=False) # Display how many images will be create with this sweep def_count: int = self.get_live_count(def_params_values) self.live_count = QLabel(f"{def_count} images will be created") self.live_count.setAlignment(qtpy.QtCore.Qt.AlignCenter) # Create progress bar with length of sweep self.create_progress_bar(bar_len=def_count) rows.append(FormRow("", widget=self.live_count)) rows.append(FormRow("", widget=self.progress_bar)) rows.append(FormRow("", widget=self._create_buttons())) return rows def _create_buttons(self) -> QFrame: """ Creates buttons for the bottom of the dialog box, one for opening the output folder, and one for closing the dialog box Params: None Returns: (QFrame): A QFrame that has two horizontally laid out buttons, first button is Open output directory, second button is close. """ buttons: QFrame = QFrame() buttons.setLayout(QHBoxLayout()) self.run_sweep_button = QPushButton("Start Sweep") self.run_sweep_button.setToolTip( "Start sweep using the selected napari layer as the input image.") self.run_sweep_button.clicked.connect(self._run_sweep) self.run_sweep_button.setAutoDefault(False) close_button: QPushButton = QPushButton("Cancel") close_button.setToolTip("Cancel an active parameter sweep.") close_button.clicked.connect(self.cancel) close_button.setAutoDefault(False) buttons.layout().addWidget(self.run_sweep_button) buttons.layout().addWidget(close_button) return buttons def _run_sweep(self) -> None: """ Initiate a sweep (called when run sweep button is pressed) with the values provided in the UI Params: none """ inputs: List[List[str]] = self.grab_ui_values(grab_combo=False) count: int = self.get_live_count(inputs) if count > 20: # warn if too many images will be generated if self.warn_images_created(count) == 1024: self.set_run_in_progress() self.controller.run_step_sweep(self, self.grab_ui_values()) else: self.set_run_in_progress() self.controller.run_step_sweep(self, self.grab_ui_values()) def sanitize_ui_inputs(self, ui_inputs: List[List[str]]) -> None: """ Check that all user inputs in the UI (passed as str) can be converted to a valid float Params: ui_inputs (List[List[str]]): inputs recieved from the UI as str """ for i in ui_inputs: for j in i: try: # ensure user input (passed as str) is a valid number float(j) except ValueError: raise ValueError( "Please enter a single number or the min:step:max notation for sweeps" ) def warn_images_created(self, count: int) -> None: """ Create a popup to warn a user that they will be creating a large number of layers by running a sweep. Params: count (int): number of images that user will create for warning """ message: QMessageBox = QMessageBox() message.setText(f"{int(count)} result image layers will be created.") message.setStyleSheet( get_stylesheet(self.controller.viewer.get_theme())) message.setWindowTitle("Running Sweep") message.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel) return message.exec_() def update_live_count(self, count: int) -> None: """ Update the Live count on the param sweep widget Params: count (int): new live count to update with """ if self.live_count: self.live_count.setText(f"{count } images will be created") def get_live_count(self, inputs: List[List[str]]) -> int: """ Calculate how many images will be created with the user provided values for this sweep Params: inputs (List[List[str]]): user inputs from the paramsweepwidget to calculate how many total result layers will be created """ # make sure all user inputs are valid numbers self.sanitize_ui_inputs(inputs) length: int = 1 for sweeps in inputs: # multiplying the result layers for all parameters will give total result layers # (ex: if param1 creates 5 combinations and param2 creates 5 combinations, there will be # 25 result layers created 5x5=25) length = length * self.get_sweep_len(float( sweeps[0]), float(sweeps[1]), float(sweeps[2])) return length def get_sweep_len(self, min: float, step: float, max: float) -> int: """ Calculate how many result layers will be created for one parameter's sweep values Params: min (float): starting range of sweep provided by user step (float): increment value of sweep provided by user max (float): end value of sweep provided by user """ i: float = min count: int = 0 while i <= max: i = i + step count = count + 1 return count def grab_ui_values(self, grab_combo=True) -> List[List[str]]: """ Update the Live count on the param sweep widget Params: count (int): new live count to update with """ inputs: List[List[str]] = list() for widget in self.inputs.values(): # grab values from combobox (when calling run_sweep function) if grab_combo: # ui is min, max, step # transform into min, step, max try: # is a set of numbers for sweep inputs.append([ widget.children()[1].text(), widget.children()[3].text(), widget.children()[2].text() ]) except IndexError: # is a combobox(string parameters) inputs.append(widget.currentText()) except AttributeError: inputs.append(widget.currentText()) else: # do not grab values from combobox (for checking inputs and getting size of sweeps) try: inputs.append([ widget.children()[1].text(), widget.children()[3].text(), widget.children()[2].text() ]) except Exception: pass return inputs def create_progress_bar(self, bar_len: int = 10) -> QProgressBar: """ Create a progress bar with the given length Params: bar_len (int): length of progress bar to be created """ self.progress_bar: QProgressBar = QProgressBar() self.progress_bar.setRange(0, bar_len) self.progress_bar.setValue(0) self.progress_bar.setTextVisible(False) return self.progress_bar def update_progress_bar_len(self, new_len: int) -> None: """ Update the progress bar so it has the given length Params: new_len (int): new length to update progress bar """ if self.progress_bar: self.progress_bar.setRange(0, new_len) self.progress_bar.setValue(0) def increment_progress_bar(self) -> None: """ Increment the progress bar by one step Params: none """ if self.controller.run_lock: self.progress_bar.setValue(self.progress_bar.value() + 1) def reset_progress_bar(self) -> None: """ Reset the progress bar to 0 Params: none """ self.progress_bar.setValue(0) def set_progress_bar(self, val: int) -> None: """ Set the progress bar to the value provided Params: val: value to set the progess bar to """ self.progress_bar.setValue(val) def create_sweep_headers(self) -> QFrame: """ Create headers for the sweep UI Params: none """ # headers header: QFrame = QFrame() header.setLayout(QHBoxLayout()) label: QLabel = QLabel("min") label.setAlignment(Qt.AlignCenter) header.layout().addWidget(label) label: QLabel = QLabel("max") label.setAlignment(Qt.AlignCenter) header.layout().addWidget(label) label: QLabel = QLabel("step size") label.setAlignment(Qt.AlignCenter) header.layout().addWidget(label) return header def _on_change_textbox(self) -> None: """ Event listener called when textboxes in widget are edited Params: none """ # grab new values inputs: List[List[str]] = self.grab_ui_values(grab_combo=False) # calculate new count new_count: int = self.get_live_count(inputs) # update ui self.update_live_count(new_count) self.update_progress_bar_len(new_count) def set_run_in_progress(self) -> None: """ function called when a run is in progress Params: none """ self.run_sweep_button.setText("Cancel") def set_run_finished(self) -> None: """ function called when a run is finished Params: none """ self.run_sweep_button.setText("Run Sweep") self.run_sweep_button.clicked.connect(self._run_sweep) def cancel(self) -> None: """ Cancel the current sweep Params: none """ if self.controller.run_lock: # if running, cancel self.controller.cancel_run_all() else: # if not running, close window self.close() def _reset_row_to_default(self, key_of_row: str, list_index: int = None): """ Reset a parameter row to its default values Params: key_of_row (str): Name of row in ui to change back to default values list_index (int): index of parameter if there are multiple with the same name """ default_param_set: Dict = self.controller.model.active_workflow.workflow_definition.steps[ self.step_number].function.parameters if list_index: input_boxes: List = self.inputs[key_of_row + str(list_index)].children() else: input_boxes = self.inputs[key_of_row].children() if list_index: default_params = default_param_set[key_of_row][list_index - 1] else: default_params = default_param_set[key_of_row][0] # reset min value input_boxes[1].setText(str(default_params.min_value)) # reset max value input_boxes[2].setText(str(default_params.max_value)) # reset step_size value input_boxes[3].setText( str((default_params.max_value - default_params.min_value) / 2)) updated_values: List[List[str]] = self.grab_ui_values(grab_combo=False) self.update_live_count(self.get_live_count(updated_values))
def _create_sweep_ui(self) -> List[FormRow]: """ Populate the sweep widget with this workflow step's parameters and their default values as well as other UI elements needed to run a sweep. Params: none """ rows: List[FormRow] = list() # add ui elements in order rows.append(FormRow("", widget=self.create_sweep_headers())) # convert parameter set to form rows default_params: Dict = self.controller.model.active_workflow.workflow_definition.steps[ self.step_number].function.parameters if self.param_set: for key, value in self.param_set.items(): # sometimes multiple unique params are in one list, need to separate out for UI if isinstance(value, list): if not isinstance(value[0], str): i = 1 for _ in value: sweep_inputs: QFrame = QFrame() sweep_inputs.setLayout(QHBoxLayout()) # get default value min_value: int = default_params[key][i - 1].min_value max_value: int = default_params[key][i - 1].max_value step_size: float = (max_value - min_value) / 2 # Create UI Elements and populate with default values min_input: QLineEdit = QLineEdit() min_input.setText(str(min_value)) min_input.editingFinished.connect( self._on_change_textbox) sweep_inputs.layout().addWidget(min_input) max_input: QLineEdit = QLineEdit() max_input.setText(str(max_value)) max_input.editingFinished.connect( self._on_change_textbox) sweep_inputs.layout().addWidget(max_input) step_input = QLineEdit() step_input.setText(str(step_size)) step_input.editingFinished.connect( self._on_change_textbox) sweep_inputs.layout().addWidget(step_input) # Reset button for row (reset to default values) reset_button: QPushButton = QPushButton("reset") reset_button.setStyleSheet("border: none;") # pass the key and value as values for calling function later on reset_button.clicked.connect( partial( lambda k, val: self._reset_row_to_default( k, val), key, i)) sweep_inputs.layout().addWidget(reset_button) # store the index of this parameter in its list appended to the parameter key self.inputs[key + str(i)] = sweep_inputs rows.append( FormRow(f"{key} {i}", widget=sweep_inputs)) i = i + 1 else: # most params are single entries in the param dictionary # for params that are a dropdown if default_params[key][0].widget_type.name == "DROPDOWN": dropdown = QComboBox() dropdown.setStyleSheet( "QComboBox { combobox-popup: 0; }") dropdown.addItems(default_params[key][0].options) self.inputs[key] = dropdown rows.append(FormRow(key, widget=dropdown)) else: # for typical sweep params sweep_inputs: QFrame = QFrame() sweep_inputs.setLayout(QHBoxLayout()) # get the default values min_value: int = default_params[key][0].min_value max_value: int = default_params[key][0].max_value step_size: float = (max_value - min_value) / 2 # Create UI elements and populate with default values min_input: QLineEdit = QLineEdit() min_input.setText(str(min_value)) min_input.editingFinished.connect( self._on_change_textbox) sweep_inputs.layout().addWidget(min_input) max_input: QLineEdit = QLineEdit() max_input.setText(str(max_value)) max_input.editingFinished.connect( self._on_change_textbox) sweep_inputs.layout().addWidget(max_input) step_input: QLineEdit = QLineEdit() step_input.setText(str(step_size)) step_input.editingFinished.connect( self._on_change_textbox) sweep_inputs.layout().addWidget(step_input) self.inputs[key] = sweep_inputs # Create button to reset back to default values reset_button: QPushButton = QPushButton("reset") reset_button.setStyleSheet("border: none;") reset_button.clicked.connect( lambda: self._reset_row_to_default(key)) sweep_inputs.layout().addWidget(reset_button) rows.append(FormRow(key, widget=sweep_inputs)) # Grab user input as str def_params_values: List[List[str]] = self.grab_ui_values( grab_combo=False) # Display how many images will be create with this sweep def_count: int = self.get_live_count(def_params_values) self.live_count = QLabel(f"{def_count} images will be created") self.live_count.setAlignment(qtpy.QtCore.Qt.AlignCenter) # Create progress bar with length of sweep self.create_progress_bar(bar_len=def_count) rows.append(FormRow("", widget=self.live_count)) rows.append(FormRow("", widget=self.progress_bar)) rows.append(FormRow("", widget=self._create_buttons())) return rows
class MainWindow(QMainWindow): """MNELAB main window.""" def __init__(self, model): """Initialize MNELAB main window. Parameters ---------- model : mnelab.model.Model instance The main window needs to connect to a model containing all data sets. This decouples the GUI from the data (model/view). """ super().__init__() self.model = model # data model self.setWindowTitle("MNELAB") # restore settings settings = read_settings() self.recent = settings["recent"] # list of recent files self.resize(settings["size"]) self.move(settings["pos"]) # trigger theme setting QIcon.setThemeSearchPaths([str(Path(__file__).parent / "icons")]) self.event(QEvent(QEvent.PaletteChange)) self.actions = {} # contains all actions # initialize menus file_menu = self.menuBar().addMenu("&File") icon = QIcon.fromTheme("open-file") self.actions["open_file"] = file_menu.addAction( icon, "&Open...", self.open_data, QKeySequence.Open) self.recent_menu = file_menu.addMenu("Open recent") self.recent_menu.aboutToShow.connect(self._update_recent_menu) self.recent_menu.triggered.connect(self._load_recent) if not self.recent: self.recent_menu.setEnabled(False) self.actions["close_file"] = file_menu.addAction( "&Close", self.model.remove_data, QKeySequence.Close) self.actions["close_all"] = file_menu.addAction( "Close all", self.close_all) file_menu.addSeparator() icon = QIcon.fromTheme("meta-info") self.actions["meta_info"] = file_menu.addAction( icon, "Show information...", self.meta_info) file_menu.addSeparator() self.actions["import_bads"] = file_menu.addAction( "Import bad channels...", lambda: self.import_file( model.import_bads, "Import bad channels", "*.csv")) self.actions["import_events"] = file_menu.addAction( "Import events...", lambda: self.import_file( model.import_events, "Import events", "*.csv")) self.actions["import_annotations"] = file_menu.addAction( "Import annotations...", lambda: self.import_file( model.import_annotations, "Import annotations", "*.csv")) self.actions["import_ica"] = file_menu.addAction( "Import &ICA...", lambda: self.open_file( model.import_ica, "Import ICA", "*.fif *.fif.gz")) file_menu.addSeparator() self.export_menu = file_menu.addMenu("Export data") for ext, description in writers.items(): action = "export_data" + ext.replace(".", "_") self.actions[action] = self.export_menu.addAction( f"{ext[1:].upper()} ({description[1]})...", partial(self.export_file, model.export_data, "Export data", ext)) self.actions["export_bads"] = file_menu.addAction( "Export &bad channels...", lambda: self.export_file( model.export_bads, "Export bad channels", "*.csv")) self.actions["export_events"] = file_menu.addAction( "Export &events...", lambda: self.export_file( model.export_events, "Export events", "*.csv")) self.actions["export_annotations"] = file_menu.addAction( "Export &annotations...", lambda: self.export_file( model.export_annotations, "Export annotations", "*.csv")) self.actions["export_ica"] = file_menu.addAction( "Export ICA...", lambda: self.export_file( model.export_ica, "Export ICA", "*.fif *.fif.gz")) file_menu.addSeparator() self.actions["quit"] = file_menu.addAction("&Quit", self.close, QKeySequence.Quit) edit_menu = self.menuBar().addMenu("&Edit") self.actions["pick_chans"] = edit_menu.addAction( "P&ick channels...", self.pick_channels) icon = QIcon.fromTheme("chan-props") self.actions["chan_props"] = edit_menu.addAction( icon, "Channel &properties...", self.channel_properties) self.actions["set_montage"] = edit_menu.addAction( "Set &montage...", self.set_montage) edit_menu.addSeparator() self.actions["set_ref"] = edit_menu.addAction("Set &reference...", self.set_reference) edit_menu.addSeparator() self.actions["annotations"] = edit_menu.addAction( "&Annotations...", self.edit_annotations) self.actions["events"] = edit_menu.addAction("&Events...", self.edit_events) edit_menu.addSeparator() self.actions["crop"] = edit_menu.addAction("&Crop data...", self.crop) self.actions["append_data"] = edit_menu.addAction( "Appen&d data...", self.append_data) plot_menu = self.menuBar().addMenu("&Plot") icon = QIcon.fromTheme("plot-data") self.actions["plot_data"] = plot_menu.addAction( icon, "&Data...", self.plot_data) icon = QIcon.fromTheme("plot-psd") self.actions["plot_psd"] = plot_menu.addAction( icon, "&Power spectral density...", self.plot_psd) icon = QIcon.fromTheme("plot-locations") self.actions["plot_locations"] = plot_menu.addAction( icon, "&Channel locations...", self.plot_locations) self.actions["plot_erds"] = plot_menu.addAction( "&ERDS maps...", self.plot_erds) plot_menu.addSeparator() self.actions["plot_ica_components"] = plot_menu.addAction( "ICA &components...", self.plot_ica_components) self.actions["plot_ica_sources"] = plot_menu.addAction( "ICA &sources...", self.plot_ica_sources) tools_menu = self.menuBar().addMenu("&Tools") icon = QIcon.fromTheme("filter-data") self.actions["filter"] = tools_menu.addAction(icon, "&Filter data...", self.filter_data) icon = QIcon.fromTheme("find-events") self.actions["find_events"] = tools_menu.addAction( icon, "Find &events...", self.find_events) self.actions["events_from_annotations"] = tools_menu.addAction( "Create events from annotations", self.events_from_annotations) self.actions["annotations_from_events"] = tools_menu.addAction( "Create annotations from events", self.annotations_from_events) tools_menu.addSeparator() nirs_menu = tools_menu.addMenu("NIRS") self.actions["convert_od"] = nirs_menu.addAction( "Convert to &optical density", self.convert_od) self.actions["convert_bl"] = nirs_menu.addAction( "Convert to &haemoglobin", self.convert_bl) tools_menu.addSeparator() icon = QIcon.fromTheme("run-ica") self.actions["run_ica"] = tools_menu.addAction(icon, "Run &ICA...", self.run_ica) self.actions["apply_ica"] = tools_menu.addAction( "Apply &ICA", self.apply_ica) tools_menu.addSeparator() self.actions["interpolate_bads"] = tools_menu.addAction( "Interpolate bad channels...", self.interpolate_bads) tools_menu.addSeparator() icon = QIcon.fromTheme("epoch-data") self.actions["epoch_data"] = tools_menu.addAction( icon, "Create Epochs...", self.epoch_data) view_menu = self.menuBar().addMenu("&View") self.actions["history"] = view_menu.addAction("&History...", self.show_history) self.actions["toolbar"] = view_menu.addAction("&Toolbar", self._toggle_toolbar) self.actions["toolbar"].setCheckable(True) self.actions["statusbar"] = view_menu.addAction( "&Statusbar", self._toggle_statusbar) self.actions["statusbar"].setCheckable(True) help_menu = self.menuBar().addMenu("&Help") self.actions["about"] = help_menu.addAction("&About", self.show_about) self.actions["about_qt"] = help_menu.addAction("About &Qt", self.show_about_qt) # actions that are always enabled self.always_enabled = [ "open_file", "about", "about_qt", "quit", "toolbar", "statusbar" ] # set up toolbar self.toolbar = self.addToolBar("toolbar") self.toolbar.setObjectName("toolbar") self.toolbar.addAction(self.actions["open_file"]) self.toolbar.addAction(self.actions["meta_info"]) self.toolbar.addSeparator() self.toolbar.addAction(self.actions["chan_props"]) self.toolbar.addSeparator() self.toolbar.addAction(self.actions["plot_data"]) self.toolbar.addAction(self.actions["plot_psd"]) self.toolbar.addAction(self.actions["plot_locations"]) self.toolbar.addSeparator() self.toolbar.addAction(self.actions["filter"]) self.toolbar.addAction(self.actions["find_events"]) self.toolbar.addAction(self.actions["epoch_data"]) self.toolbar.addAction(self.actions["run_ica"]) self.setUnifiedTitleAndToolBarOnMac(True) if settings["toolbar"]: self.toolbar.show() self.actions["toolbar"].setChecked(True) else: self.toolbar.hide() self.actions["toolbar"].setChecked(False) # set up data model for sidebar (list of open files) self.names = QStringListModel() self.names.dataChanged.connect(self._update_names) splitter = QSplitter() self.sidebar = QListView() self.sidebar.setFrameStyle(QFrame.NoFrame) self.sidebar.setFocusPolicy(Qt.NoFocus) self.sidebar.setModel(self.names) self.sidebar.clicked.connect(self._update_data) splitter.addWidget(self.sidebar) self.infowidget = InfoWidget() splitter.addWidget(self.infowidget) width = splitter.size().width() splitter.setSizes((int(width * 0.3), int(width * 0.7))) self.setCentralWidget(splitter) self.status_label = QLabel() self.statusBar().addPermanentWidget(self.status_label) if settings["statusbar"]: self.statusBar().show() self.actions["statusbar"].setChecked(True) else: self.statusBar().hide() self.actions["statusbar"].setChecked(False) self.setAcceptDrops(True) self.data_changed() def data_changed(self): # update sidebar self.names.setStringList(self.model.names) self.sidebar.setCurrentIndex(self.names.index(self.model.index)) # update info widget if self.model.data: self.infowidget.set_values(self.model.get_info()) else: self.infowidget.clear() # update status bar if self.model.data: mb = self.model.nbytes / 1024**2 self.status_label.setText(f"Total Memory: {mb:.2f} MB") else: self.status_label.clear() # toggle actions if len(self.model) == 0: # disable if no data sets are currently open enabled = False else: enabled = True for name, action in self.actions.items(): # toggle if name not in self.always_enabled: action.setEnabled(enabled) if self.model.data: # toggle if specific conditions are met bads = bool(self.model.current["data"].info["bads"]) self.actions["export_bads"].setEnabled(enabled and bads) events = self.model.current["events"] is not None self.actions["export_events"].setEnabled(enabled and events) if self.model.current["dtype"] == "raw": annot = bool(self.model.current["data"].annotations) else: annot = False self.actions["export_annotations"].setEnabled(enabled and annot) self.actions["annotations"].setEnabled(enabled and annot) locations = has_locations(self.model.current["data"].info) self.actions["plot_locations"].setEnabled(enabled and locations) ica = bool(self.model.current["ica"]) self.actions["apply_ica"].setEnabled(enabled and ica) self.actions["export_ica"].setEnabled(enabled and ica) self.actions["plot_erds"].setEnabled( enabled and self.model.current["dtype"] == "epochs") self.actions["plot_ica_components"].setEnabled(enabled and ica and locations) self.actions["plot_ica_sources"].setEnabled(enabled and ica) self.actions["interpolate_bads"].setEnabled(enabled and locations and bads) self.actions["events"].setEnabled(enabled and events) self.actions["events_from_annotations"].setEnabled(enabled and annot) self.actions["annotations_from_events"].setEnabled(enabled and events) self.actions["find_events"].setEnabled( enabled and self.model.current["dtype"] == "raw") self.actions["epoch_data"].setEnabled( enabled and events and self.model.current["dtype"] == "raw") self.actions["crop"].setEnabled( enabled and self.model.current["dtype"] == "raw") append = bool(self.model.get_compatibles()) self.actions["append_data"].setEnabled( enabled and append and (self.model.current["dtype"] in ("raw", "epochs"))) self.actions["meta_info"].setEnabled( enabled and self.model.current["ftype"] in ["XDF", "XDFZ", "XDF.GZ"]) self.actions["convert_od"].setEnabled( len( mne.pick_types(self.model.current["data"].info, fnirs="fnirs_raw"))) self.actions["convert_bl"].setEnabled( len( mne.pick_types(self.model.current["data"].info, fnirs="fnirs_od"))) # disable unsupported exporters for epochs (all must support raw) if self.model.current["dtype"] == "epochs": for ext, description in writers.items(): action = "export_data" + ext.replace(".", "_") if "epoch" in description[2]: self.actions[action].setEnabled(True) else: self.actions[action].setEnabled(False) # add to recent files if len(self.model) > 0: self._add_recent(self.model.current["fname"]) def open_data(self, fname=None): """Open raw file.""" if fname is None: fname = QFileDialog.getOpenFileName(self, "Open raw")[0] if fname: if not (Path(fname).is_file() or Path(fname).is_dir()): self._remove_recent(fname) QMessageBox.critical(self, "File does not exist", f"File {fname} does not exist anymore.") return ext = "".join(Path(fname).suffixes) if ext in [".xdf", ".xdfz", ".xdf.gz"]: rows, disabled = [], [] for idx, s in enumerate(get_streams(fname)): rows.append([ s["stream_id"], s["name"], s["type"], s["channel_count"], s["channel_format"], s["nominal_srate"] ]) is_marker = (s["nominal_srate"] == 0 or s["channel_format"] == "string") if is_marker: # disable marker streams disabled.append(idx) enabled = list(set(range(len(rows))) - set(disabled)) if enabled: selected = enabled[0] else: selected = None dialog = XDFStreamsDialog(self, rows, selected=selected, disabled=disabled) if dialog.exec_(): row = dialog.view.selectionModel().selectedRows()[0].row() stream_id = dialog.model.data(dialog.model.index(row, 0)) self.model.load(fname, stream_id=stream_id) else: # all other file formats try: self.model.load(fname) except FileNotFoundError as e: QMessageBox.critical(self, "File not found", str(e)) except ValueError as e: QMessageBox.critical(self, "Unknown file type", str(e)) def open_file(self, f, text, ffilter="*"): """Open file.""" fname = QFileDialog.getOpenFileName(self, text, filter=ffilter)[0] if fname: f(fname) def export_file(self, f, text, ffilter="*"): """Export to file.""" fname = QFileDialog.getSaveFileName(self, text, filter=ffilter)[0] if fname: try: f(fname, ffilter) except TypeError: f(fname) def import_file(self, f, text, ffilter="*"): """Import file.""" fname = QFileDialog.getOpenFileName(self, text, filter=ffilter)[0] if fname: try: f(fname) except LabelsNotFoundError as e: QMessageBox.critical(self, "Channel labels not found", str(e)) except InvalidAnnotationsError as e: QMessageBox.critical(self, "Invalid annotations", str(e)) def close_all(self): """Close all currently open data sets.""" msg = QMessageBox.question(self, "Close all data sets", "Close all data sets?") if msg == QMessageBox.Yes: while len(self.model) > 0: self.model.remove_data() def meta_info(self): """Show XDF meta info.""" xml = get_xml(self.model.current["fname"]) dialog = MetaInfoDialog(self, xml) dialog.exec_() def pick_channels(self): """Pick channels in current data set.""" channels = self.model.current["data"].info["ch_names"] dialog = PickChannelsDialog(self, channels, selected=channels) if dialog.exec_(): picks = [item.data(0) for item in dialog.channels.selectedItems()] drops = set(channels) - set(picks) if drops: self.auto_duplicate() self.model.drop_channels(drops) self.model.history.append(f"raw.drop({drops})") def channel_properties(self): """Show channel properties dialog.""" info = self.model.current["data"].info dialog = ChannelPropertiesDialog(self, info) if dialog.exec_(): dialog.model.sort(0) bads = [] renamed = {} types = {} for i in range(dialog.model.rowCount()): new_label = dialog.model.item(i, 1).data(Qt.DisplayRole) old_label = info["ch_names"][i] if new_label != old_label: renamed[old_label] = new_label new_type = dialog.model.item(i, 2).data(Qt.DisplayRole).lower() old_type = channel_type(info, i).lower() if new_type != old_type: types[new_label] = new_type if dialog.model.item(i, 3).checkState() == Qt.Checked: bads.append(info["ch_names"][i]) self.model.set_channel_properties(bads, renamed, types) def set_montage(self): """Set montage.""" montages = mne.channels.get_builtin_montages() # TODO: currently it is not possible to remove an existing montage dialog = MontageDialog(self, montages) if dialog.exec_(): name = dialog.montages.selectedItems()[0].data(0) montage = mne.channels.make_standard_montage(name) ch_names = self.model.current["data"].info["ch_names"] # check if at least one channel name matches a name in the montage if set(ch_names) & set(montage.ch_names): self.model.set_montage(name) else: QMessageBox.critical( self, "No matching channel names", "Channel names defined in the montage do " "not match any channel name in the data.") def edit_annotations(self): fs = self.model.current["data"].info["sfreq"] pos = self.model.current["data"].annotations.onset pos = (pos * fs).astype(int).tolist() dur = self.model.current["data"].annotations.duration dur = (dur * fs).astype(int).tolist() desc = self.model.current["data"].annotations.description.tolist() dialog = AnnotationsDialog(self, pos, dur, desc) if dialog.exec_(): rows = dialog.table.rowCount() onset, duration, description = [], [], [] for i in range(rows): data = dialog.table.item(i, 0).data(Qt.DisplayRole) onset.append(float(data) / fs) data = dialog.table.item(i, 1).data(Qt.DisplayRole) duration.append(float(data) / fs) data = dialog.table.item(i, 2).data(Qt.DisplayRole) description.append(data) self.model.set_annotations(onset, duration, description) def edit_events(self): pos = self.model.current["events"][:, 0].tolist() desc = self.model.current["events"][:, 2].tolist() dialog = EventsDialog(self, pos, desc) if dialog.exec_(): rows = dialog.table.rowCount() events = np.zeros((rows, 3), dtype=int) for i in range(rows): pos = int(dialog.table.item(i, 0).data(Qt.DisplayRole)) desc = int(dialog.table.item(i, 1).data(Qt.DisplayRole)) events[i] = pos, 0, desc self.model.set_events(events) def crop(self): """Crop data.""" fs = self.model.current["data"].info["sfreq"] length = self.model.current["data"].n_times / fs dialog = CropDialog(self, 0, length) if dialog.exec_(): self.auto_duplicate() self.model.crop(dialog.start or 0, dialog.stop) def append_data(self): """Concatenate raw data objects to current one.""" compatibles = self.model.get_compatibles() dialog = AppendDialog(self, compatibles) if dialog.exec_(): self.auto_duplicate() self.model.append_data(dialog.names) def plot_data(self): """Plot data.""" # self.bad is needed to update history if bad channels are selected in # the interactive plot window (see also self.eventFilter) self.bads = self.model.current["data"].info["bads"] events = self.model.current["events"] nchan = self.model.current["data"].info["nchan"] fig = self.model.current["data"].plot(events=events, n_channels=nchan, title=self.model.current["name"], scalings="auto", show=False) if events is not None: hist = f"data.plot(events=events, n_channels={nchan})" else: hist = f"data.plot(n_channels={nchan})" self.model.history.append(hist) win = fig.canvas.manager.window win.setWindowTitle(self.model.current["name"]) win.statusBar().hide() # not necessary since matplotlib 3.3 win.installEventFilter(self) # detect if the figure is closed # prevent closing the window with the escape key try: fig._mne_params["close_key"] = None except AttributeError: # does not exist in older MNE versions pass fig.show() def plot_psd(self): """Plot power spectral density (PSD).""" kwds = {} if self.model.current["dtype"] == "raw": kwds.update({"average": False, "spatial_colors": False}) fig = self.model.current["data"].plot_psd(show=False, **kwds) if kwds: tmp = ", ".join(f"{key}={value}" for key, value in kwds.items()) hist = f"data.plot_psd({tmp})" else: hist = "data.plot_psd()" self.model.history.append(hist) win = fig.canvas.manager.window win.setWindowTitle("Power spectral density") fig.show() def plot_locations(self): """Plot current montage.""" fig = self.model.current["data"].plot_sensors(show_names=True, show=False) win = fig.canvas.manager.window win.setWindowTitle("Montage") win.statusBar().hide() # not necessary since matplotlib 3.3 fig.show() def plot_ica_components(self): self.model.current["ica"].plot_components( inst=self.model.current["data"]) def plot_ica_sources(self): self.model.current["ica"].plot_sources(inst=self.model.current["data"]) def plot_erds(self): """Plot ERDS maps.""" data = self.model.current["data"] t_range = [data.tmin, data.tmax] f_range = [1, data.info["sfreq"] / 2] dialog = ERDSDialog(self, t_range, f_range) if dialog.exec_(): freqs = np.arange(dialog.f1, dialog.f2, dialog.step) baseline = [dialog.b1, dialog.b2] times = [dialog.t1, dialog.t2] figs = plot_erds(data, freqs, freqs, baseline, times) for fig in figs: fig.show() def run_ica(self): """Run ICA calculation.""" methods = ["Infomax"] if have["picard"]: methods.insert(0, "Picard") if have["sklearn"]: methods.append("FastICA") dialog = RunICADialog(self, self.model.current["data"].info["nchan"], methods) if dialog.exec_(): calc = CalcDialog(self, "Calculating ICA", "Calculating ICA.") method = dialog.method.currentText().lower() exclude_bad_segments = dialog.exclude_bad_segments.isChecked() fit_params = {} if dialog.extended.isEnabled(): fit_params["extended"] = dialog.extended.isChecked() if dialog.ortho.isEnabled(): fit_params["ortho"] = dialog.ortho.isChecked() ica = mne.preprocessing.ICA(method=method, fit_params=fit_params) history = f"ica = mne.preprocessing.ICA(method='{method}'" if fit_params: history += f", fit_params={fit_params})" else: history += ")" self.model.history.append(history) pool = mp.Pool(processes=1) res = pool.apply_async( func=ica.fit, args=(self.model.current["data"], ), kwds={"reject_by_annotation": exclude_bad_segments}, callback=lambda x: calc.accept()) pool.close() if not calc.exec_(): pool.terminate() else: self.model.current["ica"] = res.get(timeout=1) self.model.history.append(f"ica.fit(inst=raw, " f"reject_by_annotation=" f"{exclude_bad_segments})") self.data_changed() def apply_ica(self): """Apply current fitted ICA.""" self.auto_duplicate() self.model.apply_ica() def interpolate_bads(self): """Interpolate bad channels""" dialog = InterpolateBadsDialog(self) if dialog.exec_(): duplicated = self.auto_duplicate() try: self.model.interpolate_bads(dialog.reset_bads, dialog.mode, dialog.origin) except ValueError as e: if duplicated: # undo self.model.remove_data() self.model.index -= 1 self.data_changed() msgbox = ErrorMessageBox(self, "Could not interpolate bad channels", str(e), traceback.format_exc()) msgbox.show() def filter_data(self): """Filter data.""" dialog = FilterDialog(self) if dialog.exec_(): self.auto_duplicate() self.model.filter(dialog.low, dialog.high) def find_events(self): info = self.model.current["data"].info # use first stim channel as default in dialog default_stim = 0 for i in range(info["nchan"]): if mne.io.pick.channel_type(info, i) == "stim": default_stim = i break dialog = FindEventsDialog(self, info["ch_names"], default_stim) if dialog.exec_(): stim_channel = dialog.stimchan.currentText() consecutive = dialog.consecutive.isChecked() initial_event = dialog.initial_event.isChecked() uint_cast = dialog.uint_cast.isChecked() min_dur = dialog.minduredit.value() shortest_event = dialog.shortesteventedit.value() self.model.find_events(stim_channel=stim_channel, consecutive=consecutive, initial_event=initial_event, uint_cast=uint_cast, min_duration=min_dur, shortest_event=shortest_event) def events_from_annotations(self): self.model.events_from_annotations() def annotations_from_events(self): self.model.annotations_from_events() def epoch_data(self): """Epoch raw data.""" dialog = EpochDialog(self, self.model.current["events"]) if dialog.exec_(): events = [ int(item.text()) for item in dialog.events.selectedItems() ] tmin = dialog.tmin.value() tmax = dialog.tmax.value() if dialog.baseline.isChecked(): baseline = dialog.a.value(), dialog.b.value() else: baseline = None duplicated = self.auto_duplicate() try: self.model.epoch_data(events, tmin, tmax, baseline) except ValueError as e: if duplicated: # undo self.model.remove_data() self.model.index -= 1 self.data_changed() msgbox = ErrorMessageBox(self, "Could not create epochs", str(e), traceback.format_exc()) msgbox.show() def convert_od(self): """Convert to optical density.""" self.auto_duplicate() self.model.convert_od() def convert_bl(self): """Convert to haemoglobin.""" self.auto_duplicate() self.model.convert_beer_lambert() def set_reference(self): """Set reference.""" dialog = ReferenceDialog(self) if dialog.exec_(): self.auto_duplicate() if dialog.average.isChecked(): self.model.set_reference("average") else: ref = [c.strip() for c in dialog.channellist.text().split(",")] self.model.set_reference(ref) def show_history(self): """Show history.""" dialog = HistoryDialog(self, "\n".join(self.model.history)) dialog.exec_() def show_about(self): """Show About dialog.""" msg_box = QMessageBox(self) text = (f"<img src='{image_path('mnelab_logo.png')}'>" f"<p>MNELAB {__version__}</p>") msg_box.setText(text) mnelab_url = "github.com/cbrnr/mnelab" mne_url = "github.com/mne-tools/mne-python" pkgs = [] for key, value in have.items(): if value: pkgs.append(f"{key} ({value})") else: pkgs.append(f"{key} (not installed)") text = (f'<nobr><p>This program uses Python ' f'{".".join(str(k) for k in version_info[:3])} and the ' f'following packages:</p></nobr>' f'<p>{", ".join(pkgs)}</p>' f'<nobr><p>MNELAB repository: ' f'<a href=https://{mnelab_url}>{mnelab_url}</a></p></nobr>' f'<nobr><p>MNE repository: ' f'<a href=https://{mne_url}>{mne_url}</a></p></nobr>' f'<p>Licensed under the BSD 3-clause license.</p>' f'<p>Copyright 2017-2020 by Clemens Brunner.</p>') msg_box.setInformativeText(text) msg_box.exec_() def show_about_qt(self): """Show About Qt dialog.""" QMessageBox.aboutQt(self, "About Qt") def auto_duplicate(self): """Automatically duplicate current data set. If the current data set is stored in a file (i.e. was loaded directly from a file), a new data set is automatically created. If the current data set is not stored in a file (i.e. was created by operations in MNELAB), a dialog box asks the user if the current data set should be overwritten or duplicated. Returns ------- duplicated : bool True if the current data set was automatically duplicated, False if the current data set was overwritten. """ # if current data is stored in a file create a new data set if self.model.current["fname"]: self.model.duplicate_data() return True # otherwise ask the user else: msg = QMessageBox.question(self, "Overwrite existing data set", "Overwrite existing data set?") if msg == QMessageBox.No: # create new data set self.model.duplicate_data() return True return False def _add_recent(self, fname): """Add a file to recent file list. Parameters ---------- fname : str File name. """ if fname in self.recent: # avoid duplicates self.recent.remove(fname) self.recent.insert(0, fname) while len(self.recent) > MAX_RECENT: # prune list self.recent.pop() write_settings(recent=self.recent) if not self.recent_menu.isEnabled(): self.recent_menu.setEnabled(True) def _remove_recent(self, fname): """Remove file from recent file list. Parameters ---------- fname : str File name. """ if fname in self.recent: self.recent.remove(fname) write_settings(recent=self.recent) if not self.recent: self.recent_menu.setEnabled(False) @Slot(QModelIndex) def _update_data(self, selected): """Update index and information based on the state of the sidebar. Parameters ---------- selected : QModelIndex Index of the selected row. """ if selected.row() != self.model.index: self.model.index = selected.row() self.data_changed() self.model.history.append(f"data = datasets[{self.model.index}]") @Slot(QModelIndex, QModelIndex) def _update_names(self, start, stop): """Update names in DataSets after changes in sidebar.""" for index in range(start.row(), stop.row() + 1): self.model.data[index]["name"] = self.names.stringList()[index] @Slot() def _update_recent_menu(self): self.recent_menu.clear() for recent in self.recent: self.recent_menu.addAction(recent) @Slot(QAction) def _load_recent(self, action): self.open_data(fname=action.text()) @Slot() def _toggle_toolbar(self): if self.toolbar.isHidden(): self.toolbar.show() else: self.toolbar.hide() write_settings(toolbar=not self.toolbar.isHidden()) @Slot() def _toggle_statusbar(self): if self.statusBar().isHidden(): self.statusBar().show() else: self.statusBar().hide() write_settings(statusbar=not self.statusBar().isHidden()) @Slot(QDropEvent) def dragEnterEvent(self, event): if event.mimeData().hasUrls(): event.acceptProposedAction() @Slot(QDropEvent) def dropEvent(self, event): mime = event.mimeData() if mime.hasUrls(): urls = mime.urls() for url in urls: try: self.open_data(url.toLocalFile()) except FileNotFoundError as e: QMessageBox.critical(self, "File not found", str(e)) @Slot(QEvent) def closeEvent(self, event): """Close application. Parameters ---------- event : QEvent Close event. """ write_settings(size=self.size(), pos=self.pos()) if self.model.history: print("\nCommand History") print("===============") print("\n".join(self.model.history)) QApplication.quit() def eventFilter(self, source, event): # currently the only source is the raw plot window if event.type() == QEvent.Close: self.data_changed() bads = self.model.current["data"].info["bads"] if self.bads != bads: self.model.history.append(f"data.info['bads'] = {bads}") return QObject.eventFilter(self, source, event) def event(self, ev): """Catch system events.""" if ev.type() == QEvent.PaletteChange: # detect theme switches style = interface_style() # light or dark if style is not None: QIcon.setThemeName(style) else: QIcon.setThemeName("light") # fallback return super().event(ev)
def __init__(self, model): """Initialize MNELAB main window. Parameters ---------- model : mnelab.model.Model instance The main window needs to connect to a model containing all data sets. This decouples the GUI from the data (model/view). """ super().__init__() self.model = model # data model self.setWindowTitle("MNELAB") # restore settings settings = read_settings() self.recent = settings["recent"] # list of recent files self.resize(settings["size"]) self.move(settings["pos"]) # trigger theme setting QIcon.setThemeSearchPaths([str(Path(__file__).parent / "icons")]) self.event(QEvent(QEvent.PaletteChange)) self.actions = {} # contains all actions # initialize menus file_menu = self.menuBar().addMenu("&File") icon = QIcon.fromTheme("open-file") self.actions["open_file"] = file_menu.addAction( icon, "&Open...", self.open_data, QKeySequence.Open) self.recent_menu = file_menu.addMenu("Open recent") self.recent_menu.aboutToShow.connect(self._update_recent_menu) self.recent_menu.triggered.connect(self._load_recent) if not self.recent: self.recent_menu.setEnabled(False) self.actions["close_file"] = file_menu.addAction( "&Close", self.model.remove_data, QKeySequence.Close) self.actions["close_all"] = file_menu.addAction( "Close all", self.close_all) file_menu.addSeparator() icon = QIcon.fromTheme("meta-info") self.actions["meta_info"] = file_menu.addAction( icon, "Show information...", self.meta_info) file_menu.addSeparator() self.actions["import_bads"] = file_menu.addAction( "Import bad channels...", lambda: self.import_file( model.import_bads, "Import bad channels", "*.csv")) self.actions["import_events"] = file_menu.addAction( "Import events...", lambda: self.import_file( model.import_events, "Import events", "*.csv")) self.actions["import_annotations"] = file_menu.addAction( "Import annotations...", lambda: self.import_file( model.import_annotations, "Import annotations", "*.csv")) self.actions["import_ica"] = file_menu.addAction( "Import &ICA...", lambda: self.open_file( model.import_ica, "Import ICA", "*.fif *.fif.gz")) file_menu.addSeparator() self.export_menu = file_menu.addMenu("Export data") for ext, description in writers.items(): action = "export_data" + ext.replace(".", "_") self.actions[action] = self.export_menu.addAction( f"{ext[1:].upper()} ({description[1]})...", partial(self.export_file, model.export_data, "Export data", ext)) self.actions["export_bads"] = file_menu.addAction( "Export &bad channels...", lambda: self.export_file( model.export_bads, "Export bad channels", "*.csv")) self.actions["export_events"] = file_menu.addAction( "Export &events...", lambda: self.export_file( model.export_events, "Export events", "*.csv")) self.actions["export_annotations"] = file_menu.addAction( "Export &annotations...", lambda: self.export_file( model.export_annotations, "Export annotations", "*.csv")) self.actions["export_ica"] = file_menu.addAction( "Export ICA...", lambda: self.export_file( model.export_ica, "Export ICA", "*.fif *.fif.gz")) file_menu.addSeparator() self.actions["quit"] = file_menu.addAction("&Quit", self.close, QKeySequence.Quit) edit_menu = self.menuBar().addMenu("&Edit") self.actions["pick_chans"] = edit_menu.addAction( "P&ick channels...", self.pick_channels) icon = QIcon.fromTheme("chan-props") self.actions["chan_props"] = edit_menu.addAction( icon, "Channel &properties...", self.channel_properties) self.actions["set_montage"] = edit_menu.addAction( "Set &montage...", self.set_montage) edit_menu.addSeparator() self.actions["set_ref"] = edit_menu.addAction("Set &reference...", self.set_reference) edit_menu.addSeparator() self.actions["annotations"] = edit_menu.addAction( "&Annotations...", self.edit_annotations) self.actions["events"] = edit_menu.addAction("&Events...", self.edit_events) edit_menu.addSeparator() self.actions["crop"] = edit_menu.addAction("&Crop data...", self.crop) self.actions["append_data"] = edit_menu.addAction( "Appen&d data...", self.append_data) plot_menu = self.menuBar().addMenu("&Plot") icon = QIcon.fromTheme("plot-data") self.actions["plot_data"] = plot_menu.addAction( icon, "&Data...", self.plot_data) icon = QIcon.fromTheme("plot-psd") self.actions["plot_psd"] = plot_menu.addAction( icon, "&Power spectral density...", self.plot_psd) icon = QIcon.fromTheme("plot-locations") self.actions["plot_locations"] = plot_menu.addAction( icon, "&Channel locations...", self.plot_locations) self.actions["plot_erds"] = plot_menu.addAction( "&ERDS maps...", self.plot_erds) plot_menu.addSeparator() self.actions["plot_ica_components"] = plot_menu.addAction( "ICA &components...", self.plot_ica_components) self.actions["plot_ica_sources"] = plot_menu.addAction( "ICA &sources...", self.plot_ica_sources) tools_menu = self.menuBar().addMenu("&Tools") icon = QIcon.fromTheme("filter-data") self.actions["filter"] = tools_menu.addAction(icon, "&Filter data...", self.filter_data) icon = QIcon.fromTheme("find-events") self.actions["find_events"] = tools_menu.addAction( icon, "Find &events...", self.find_events) self.actions["events_from_annotations"] = tools_menu.addAction( "Create events from annotations", self.events_from_annotations) self.actions["annotations_from_events"] = tools_menu.addAction( "Create annotations from events", self.annotations_from_events) tools_menu.addSeparator() nirs_menu = tools_menu.addMenu("NIRS") self.actions["convert_od"] = nirs_menu.addAction( "Convert to &optical density", self.convert_od) self.actions["convert_bl"] = nirs_menu.addAction( "Convert to &haemoglobin", self.convert_bl) tools_menu.addSeparator() icon = QIcon.fromTheme("run-ica") self.actions["run_ica"] = tools_menu.addAction(icon, "Run &ICA...", self.run_ica) self.actions["apply_ica"] = tools_menu.addAction( "Apply &ICA", self.apply_ica) tools_menu.addSeparator() self.actions["interpolate_bads"] = tools_menu.addAction( "Interpolate bad channels...", self.interpolate_bads) tools_menu.addSeparator() icon = QIcon.fromTheme("epoch-data") self.actions["epoch_data"] = tools_menu.addAction( icon, "Create Epochs...", self.epoch_data) view_menu = self.menuBar().addMenu("&View") self.actions["history"] = view_menu.addAction("&History...", self.show_history) self.actions["toolbar"] = view_menu.addAction("&Toolbar", self._toggle_toolbar) self.actions["toolbar"].setCheckable(True) self.actions["statusbar"] = view_menu.addAction( "&Statusbar", self._toggle_statusbar) self.actions["statusbar"].setCheckable(True) help_menu = self.menuBar().addMenu("&Help") self.actions["about"] = help_menu.addAction("&About", self.show_about) self.actions["about_qt"] = help_menu.addAction("About &Qt", self.show_about_qt) # actions that are always enabled self.always_enabled = [ "open_file", "about", "about_qt", "quit", "toolbar", "statusbar" ] # set up toolbar self.toolbar = self.addToolBar("toolbar") self.toolbar.setObjectName("toolbar") self.toolbar.addAction(self.actions["open_file"]) self.toolbar.addAction(self.actions["meta_info"]) self.toolbar.addSeparator() self.toolbar.addAction(self.actions["chan_props"]) self.toolbar.addSeparator() self.toolbar.addAction(self.actions["plot_data"]) self.toolbar.addAction(self.actions["plot_psd"]) self.toolbar.addAction(self.actions["plot_locations"]) self.toolbar.addSeparator() self.toolbar.addAction(self.actions["filter"]) self.toolbar.addAction(self.actions["find_events"]) self.toolbar.addAction(self.actions["epoch_data"]) self.toolbar.addAction(self.actions["run_ica"]) self.setUnifiedTitleAndToolBarOnMac(True) if settings["toolbar"]: self.toolbar.show() self.actions["toolbar"].setChecked(True) else: self.toolbar.hide() self.actions["toolbar"].setChecked(False) # set up data model for sidebar (list of open files) self.names = QStringListModel() self.names.dataChanged.connect(self._update_names) splitter = QSplitter() self.sidebar = QListView() self.sidebar.setFrameStyle(QFrame.NoFrame) self.sidebar.setFocusPolicy(Qt.NoFocus) self.sidebar.setModel(self.names) self.sidebar.clicked.connect(self._update_data) splitter.addWidget(self.sidebar) self.infowidget = InfoWidget() splitter.addWidget(self.infowidget) width = splitter.size().width() splitter.setSizes((int(width * 0.3), int(width * 0.7))) self.setCentralWidget(splitter) self.status_label = QLabel() self.statusBar().addPermanentWidget(self.status_label) if settings["statusbar"]: self.statusBar().show() self.actions["statusbar"].setChecked(True) else: self.statusBar().hide() self.actions["statusbar"].setChecked(False) self.setAcceptDrops(True) self.data_changed()
def addLabel(x): label = QLabel("A", toolbar) toolbar.addWidget(label)
class StatusBarWidget(QWidget): """Status bar widget base.""" # Signals sig_clicked = Signal() def __init__(self, parent, statusbar, icon=None, spinner=False): """Status bar widget base.""" super(StatusBarWidget, self).__init__(parent) # Variables self.value = None # Widget self._status_bar = statusbar self._icon = None self._pixmap = None self._icon_size = QSize(16, 16) # Should this be adjustable? self.label_icon = QLabel() self.label_value = QLabel() self.spinner = None if spinner: self.spinner = create_waitspinner(size=14, parent=self) # Layout setup layout = QHBoxLayout(self) layout.setSpacing(0) # Reduce space between icon and label layout.addWidget(self.label_icon) layout.addWidget(self.label_value) if spinner: layout.addWidget(self.spinner) self.spinner.hide() layout.addSpacing(20) layout.setContentsMargins(0, 0, 0, 0) # Widget setup self.set_icon(icon) # See spyder-ide/spyder#9044. self.text_font = QFont(QFont().defaultFamily(), weight=QFont.Normal) self.label_value.setAlignment(Qt.AlignRight) self.label_value.setFont(self.text_font) # Setup statusbar.addPermanentWidget(self) self.set_value('') self.update_tooltip() # --- Status bar widget API # ------------------------------------------------------------------------ def set_icon(self, icon): """Set the icon for the status bar widget.""" self.label_icon.setVisible(icon is not None) if icon is not None and isinstance(icon, QIcon): self._icon = icon self._pixmap = icon.pixmap(self._icon_size) self.label_icon.setPixmap(self._pixmap) def set_value(self, value): """Set formatted text value.""" self.value = value self.label_value.setText(value) def update_tooltip(self): """Update tooltip for widget.""" tooltip = self.get_tooltip() if tooltip: self.label_value.setToolTip(tooltip) if self.label_icon: self.label_icon.setToolTip(tooltip) self.setToolTip(tooltip) def mouseReleaseEvent(self, event): """Override Qt method to allow for click signal.""" super(StatusBarWidget, self).mousePressEvent(event) self.sig_clicked.emit() # --- API to be defined by user # ------------------------------------------------------------------------ def get_tooltip(self): """Return the widget tooltip text.""" return '' def get_icon(self): """Return the widget tooltip text.""" return None
class MessageBox(DialogBase): """Base message box dialog.""" QUESTION_BOX = 100 INFORMATION_BOX = 101 ERROR_BOX = 102 REMOVE_BOX = 103 sig_url_clicked = Signal(object) def __init__(self, type_, error='', title='', text='', learn_more=None): """Base message box dialog.""" super(MessageBox, self).__init__() from anaconda_navigator.utils.analytics import GATracker self.tracker = GATracker() self.label_text = QLabel(to_text_string(text)) self.textbox_error = QTextEdit() self.button_ok = ButtonPrimary('Ok') self.button_yes = ButtonPrimary('Yes') self.button_no = ButtonNormal('No') self.button_copy = ButtonNormal('Copy text') self.button_learn = ButtonNormal('Learn more') self.button_remove = ButtonDanger('Remove') self.button_cancel = ButtonNormal('Cancel') self.button_send = ButtonNormal('Report Issue', parent=self) self.label_text.setOpenExternalLinks(False) self.label_text.setWordWrap(True) self.label_text.linkActivated.connect(self.url_clicked) self.textbox_error.setReadOnly(True) self.textbox_error.setFrameStyle(QTextEdit.Plain) self.textbox_error.setFrameShape(QTextEdit.NoFrame) self.setMinimumWidth(260) self.textbox_error.verticalScrollBar().show() self.setWindowTitle(to_text_string(title)) error = to_text_string(error).split('\n') error = '<br>'.join(error) self.textbox_error.setText(error) # Layouts layout = QVBoxLayout() layout.addWidget(self.label_text) layout.addWidget(SpacerVertical()) if error: layout.addWidget(self.textbox_error) layout.addWidget(SpacerVertical()) layout.addWidget(self.button_copy) layout.addWidget(SpacerVertical()) layout.addWidget(SpacerVertical()) layout_buttons = QHBoxLayout() layout_buttons.addStretch() layout.addLayout(layout_buttons) self.layout = layout self.setLayout(layout) # Signals self.button_copy.clicked.connect(self.copy_text) self.button_ok.clicked.connect(self.accept) self.button_yes.clicked.connect(self.accept) self.button_no.clicked.connect(self.reject) self.button_remove.clicked.connect(self.accept) self.button_cancel.clicked.connect(self.reject) self.button_send.clicked.connect(self.send) # Setup self.button_learn.setVisible(bool(learn_more)) if bool(learn_more): layout_buttons.addWidget(self.button_learn) layout_buttons.addWidget(SpacerHorizontal()) self.button_learn.clicked.connect( lambda: self.show_url(learn_more)) if type_ == self.ERROR_BOX: layout_buttons.addWidget(self.button_send) layout_buttons.addWidget(SpacerHorizontal()) layout_buttons.addWidget(self.button_ok) self.button_yes.setVisible(False) self.button_no.setVisible(False) self.button_remove.setVisible(False) self.button_cancel.setVisible(False) elif type_ == self.INFORMATION_BOX: layout_buttons.addWidget(self.button_ok) self.button_yes.setVisible(False) self.button_no.setVisible(False) self.textbox_error.setVisible(False) self.button_copy.setVisible(False) self.button_remove.setVisible(False) self.button_cancel.setVisible(False) elif type_ == self.QUESTION_BOX: layout_buttons.addStretch() layout_buttons.addWidget(self.button_no) layout_buttons.addWidget(SpacerHorizontal()) layout_buttons.addWidget(self.button_yes) layout_buttons.addWidget(SpacerHorizontal()) self.textbox_error.setVisible(False) self.button_ok.setVisible(False) self.button_copy.setVisible(False) self.button_remove.setVisible(False) self.button_cancel.setVisible(False) elif type_ == self.REMOVE_BOX: layout_buttons.addStretch() layout_buttons.addWidget(self.button_cancel) layout_buttons.addWidget(SpacerHorizontal()) layout_buttons.addWidget(self.button_remove) layout_buttons.addWidget(SpacerHorizontal()) self.textbox_error.setVisible(False) self.button_ok.setVisible(False) self.button_copy.setVisible(False) self.button_yes.setVisible(False) self.button_no.setVisible(False) self.button_send.setVisible(False) self.layout_buttons = layout_buttons def url_clicked(self, url): """Emit url interaction.""" self.sig_url_clicked.emit(url) def copy_text(self): """Copy all the content of the displayed error message.""" self.textbox_error.selectAll() self.textbox_error.copy() def show_url(self, url=None): """Open url in default browser.""" if url: qurl = QUrl(url) QDesktopServices.openUrl(qurl) self.tracker.track_event('help', 'documentation', url) def send(self): """Send error report to github and create an issue with a template.""" import webbrowser from anaconda_navigator.utils.analytics import GATracker base = "https://github.com/ContinuumIO/anaconda-issues/issues/new?{0}" template = ''' ## Main error {text} ## Traceback ``` {trace} ``` ## System information ``` {info} ``` ''' info = GATracker().info info = '\n'.join('{}: {}'.format(k, v) for k, v in info.items()) query = parse.urlencode({ 'title': "Navigator Error", 'labels': "tag:navigator", 'body': template.format(text=self.text, trace=self.error, info=info) }) url = base.format(query) webbrowser.open_new_tab(url)
class StatusBarWidget(QWidget, SpyderWidgetMixin): """ Base class for status bar widgets. These widgets consist by default of an icon, a label and a spinner, which are organized from left to right on that order. You can also add any other QWidget to this layout by setting the CUSTOM_WIDGET_CLASS class attribute. It'll be put between the label and the spinner. """ ID = None """ Unique string widget identifier. """ CUSTOM_WIDGET_CLASS = None """ Custom widget class to add to the default layout. """ sig_clicked = Signal() """ This signal is emmitted when the widget is clicked. """ def __init__(self, parent=None, show_icon=True, show_label=True, show_spinner=False): """ Base class for status bar widgets. These are composed of the following widgets, which are arranged in a QHBoxLayout from left to right: * Icon * Label * Custom QWidget * Spinner Parameters ---------- show_icon: bool Show an icon in the widget. show_label: bool Show a label in the widget. show_spinner: bool Show a spinner. Notes ----- 1. To use an icon, you need to redefine the ``get_icon`` method. 2. To use a label, you need to call ``set_value``. """ super().__init__(parent, class_parent=parent) self._parent = parent self.show_icon = show_icon self.show_label = show_label self.show_spinner = show_spinner self.value = None self.label_icon = None self.label_value = None self.spinner = None self.custom_widget = None self.set_layout() self.setStyleSheet(self._stylesheet()) def set_layout(self): """Set layout for default widgets.""" # Icon if self.show_icon: self._icon = self.get_icon() self._pixmap = None self._icon_size = QSize(16, 16) # Should this be adjustable? self.label_icon = QLabel() self.set_icon() # Label if self.show_label: self.label_value = QLabel() self.set_value('') # See spyder-ide/spyder#9044. self.text_font = QFont(QFont().defaultFamily(), weight=QFont.Normal) self.label_value.setAlignment(Qt.AlignRight | Qt.AlignVCenter) self.label_value.setFont(self.text_font) # Custom widget if self.CUSTOM_WIDGET_CLASS: if not issubclass(self.CUSTOM_WIDGET_CLASS, QWidget): raise SpyderAPIError( 'Any custom status widget must subclass QWidget!' ) self.custom_widget = self.CUSTOM_WIDGET_CLASS(self._parent) # Spinner if self.show_spinner: self.spinner = create_waitspinner(size=14, parent=self) self.spinner.hide() # Layout setup layout = QHBoxLayout(self) layout.setSpacing(0) # Reduce space between icon and label if self.show_icon: layout.addWidget(self.label_icon) if self.show_label: layout.addWidget(self.label_value) if self.custom_widget: layout.addWidget(self.custom_widget) if self.show_spinner: layout.addWidget(self.spinner) layout.addSpacing(0) layout.setContentsMargins(0, 0, 0, 0) layout.setAlignment(Qt.AlignVCenter) # Setup self.update_tooltip() # ---- Status bar widget API def set_icon(self): """Set the icon for the status bar widget.""" if self.label_icon: icon = self._icon self.label_icon.setVisible(icon is not None) if icon is not None and isinstance(icon, QIcon): self._pixmap = icon.pixmap(self._icon_size) self.label_icon.setPixmap(self._pixmap) def set_value(self, value): """Set formatted text value.""" if self.label_value: self.value = value self.label_value.setText(value) def update_tooltip(self): """Update tooltip for widget.""" tooltip = self.get_tooltip() if tooltip: if self.label_value: self.label_value.setToolTip(tooltip) if self.label_icon: self.label_icon.setToolTip(tooltip) self.setToolTip(tooltip) def mouseReleaseEvent(self, event): """Override Qt method to allow for click signal.""" super(StatusBarWidget, self).mousePressEvent(event) self.sig_clicked.emit() # ---- API to be defined by user def get_tooltip(self): """Return the widget tooltip text.""" return '' def get_icon(self): """Return the widget tooltip text.""" return None def _stylesheet(self): stylesheet = ("QToolTip {{background-color: {background_color};" "color: {color};" "border: none}}").format( background_color=QStylePalette.COLOR_ACCENT_2, color=QStylePalette.COLOR_TEXT_1 ) return stylesheet
def __init__(self, type_, error='', title='', text='', learn_more=None): """Base message box dialog.""" super(MessageBox, self).__init__() from anaconda_navigator.utils.analytics import GATracker self.tracker = GATracker() self.label_text = QLabel(to_text_string(text)) self.textbox_error = QTextEdit() self.button_ok = ButtonPrimary('Ok') self.button_yes = ButtonPrimary('Yes') self.button_no = ButtonNormal('No') self.button_copy = ButtonNormal('Copy text') self.button_learn = ButtonNormal('Learn more') self.button_remove = ButtonDanger('Remove') self.button_cancel = ButtonNormal('Cancel') self.button_send = ButtonNormal('Report Issue', parent=self) self.label_text.setOpenExternalLinks(False) self.label_text.setWordWrap(True) self.label_text.linkActivated.connect(self.url_clicked) self.textbox_error.setReadOnly(True) self.textbox_error.setFrameStyle(QTextEdit.Plain) self.textbox_error.setFrameShape(QTextEdit.NoFrame) self.setMinimumWidth(260) self.textbox_error.verticalScrollBar().show() self.setWindowTitle(to_text_string(title)) error = to_text_string(error).split('\n') error = '<br>'.join(error) self.textbox_error.setText(error) # Layouts layout = QVBoxLayout() layout.addWidget(self.label_text) layout.addWidget(SpacerVertical()) if error: layout.addWidget(self.textbox_error) layout.addWidget(SpacerVertical()) layout.addWidget(self.button_copy) layout.addWidget(SpacerVertical()) layout.addWidget(SpacerVertical()) layout_buttons = QHBoxLayout() layout_buttons.addStretch() layout.addLayout(layout_buttons) self.layout = layout self.setLayout(layout) # Signals self.button_copy.clicked.connect(self.copy_text) self.button_ok.clicked.connect(self.accept) self.button_yes.clicked.connect(self.accept) self.button_no.clicked.connect(self.reject) self.button_remove.clicked.connect(self.accept) self.button_cancel.clicked.connect(self.reject) self.button_send.clicked.connect(self.send) # Setup self.button_learn.setVisible(bool(learn_more)) if bool(learn_more): layout_buttons.addWidget(self.button_learn) layout_buttons.addWidget(SpacerHorizontal()) self.button_learn.clicked.connect( lambda: self.show_url(learn_more)) if type_ == self.ERROR_BOX: layout_buttons.addWidget(self.button_send) layout_buttons.addWidget(SpacerHorizontal()) layout_buttons.addWidget(self.button_ok) self.button_yes.setVisible(False) self.button_no.setVisible(False) self.button_remove.setVisible(False) self.button_cancel.setVisible(False) elif type_ == self.INFORMATION_BOX: layout_buttons.addWidget(self.button_ok) self.button_yes.setVisible(False) self.button_no.setVisible(False) self.textbox_error.setVisible(False) self.button_copy.setVisible(False) self.button_remove.setVisible(False) self.button_cancel.setVisible(False) elif type_ == self.QUESTION_BOX: layout_buttons.addStretch() layout_buttons.addWidget(self.button_no) layout_buttons.addWidget(SpacerHorizontal()) layout_buttons.addWidget(self.button_yes) layout_buttons.addWidget(SpacerHorizontal()) self.textbox_error.setVisible(False) self.button_ok.setVisible(False) self.button_copy.setVisible(False) self.button_remove.setVisible(False) self.button_cancel.setVisible(False) elif type_ == self.REMOVE_BOX: layout_buttons.addStretch() layout_buttons.addWidget(self.button_cancel) layout_buttons.addWidget(SpacerHorizontal()) layout_buttons.addWidget(self.button_remove) layout_buttons.addWidget(SpacerHorizontal()) self.textbox_error.setVisible(False) self.button_ok.setVisible(False) self.button_copy.setVisible(False) self.button_yes.setVisible(False) self.button_no.setVisible(False) self.button_send.setVisible(False) self.layout_buttons = layout_buttons
class QtImageControls(QtBaseImageControls): """Qt view and controls for the napari Image layer. Parameters ---------- layer : napari.layers.Image An instance of a napari Image layer. Attributes ---------- attenuationSlider : qtpy.QtWidgets.QSlider Slider controlling attenuation rate for `attenuated_mip` mode. attenuationLabel : qtpy.QtWidgets.QLabel Label for the attenuation slider widget. grid_layout : qtpy.QtWidgets.QGridLayout Layout of Qt widget controls for the layer. interpComboBox : qtpy.QtWidgets.QComboBox Dropdown menu to select the interpolation mode for image display. interpLabel : qtpy.QtWidgets.QLabel Label for the interpolation dropdown menu. isoThresholdSlider : qtpy.QtWidgets.QSlider Slider controlling the isosurface threshold value for rendering. isoThresholdLabel : qtpy.QtWidgets.QLabel Label for the isosurface threshold slider widget. layer : napari.layers.Image An instance of a napari Image layer. renderComboBox : qtpy.QtWidgets.QComboBox Dropdown menu to select the rendering mode for image display. renderLabel : qtpy.QtWidgets.QLabel Label for the rendering mode dropdown menu. """ def __init__(self, layer): super().__init__(layer) self.layer.events.interpolation.connect(self._on_interpolation_change) self.layer.events.rendering.connect(self._on_rendering_change) self.layer.events.iso_threshold.connect(self._on_iso_threshold_change) self.layer.events.attenuation.connect(self._on_attenuation_change) self.layer._dims.events.ndisplay.connect(self._on_ndisplay_change) self.interpComboBox = QComboBox(self) self.interpComboBox.activated[str].connect(self.changeInterpolation) self.interpLabel = QLabel('interpolation:') renderComboBox = QComboBox(self) renderComboBox.addItems(Rendering.keys()) index = renderComboBox.findText(self.layer.rendering, Qt.MatchFixedString) renderComboBox.setCurrentIndex(index) renderComboBox.activated[str].connect(self.changeRendering) self.renderComboBox = renderComboBox self.renderLabel = QLabel('rendering:') sld = QSlider(Qt.Horizontal, parent=self) sld.setFocusPolicy(Qt.NoFocus) sld.setMinimum(0) sld.setMaximum(100) sld.setSingleStep(1) sld.setValue(int(self.layer.iso_threshold * 100)) sld.valueChanged.connect(self.changeIsoThreshold) self.isoThresholdSlider = sld self.isoThresholdLabel = QLabel('iso threshold:') sld = QSlider(Qt.Horizontal, parent=self) sld.setFocusPolicy(Qt.NoFocus) sld.setMinimum(0) sld.setMaximum(100) sld.setSingleStep(1) sld.setValue(int(self.layer.attenuation * 200)) sld.valueChanged.connect(self.changeAttenuation) self.attenuationSlider = sld self.attenuationLabel = QLabel('attenuation:') self._on_ndisplay_change() colormap_layout = QHBoxLayout() if hasattr(self.layer, 'rgb') and self.layer.rgb: colormap_layout.addWidget(QLabel("RGB")) self.colormapComboBox.setVisible(False) self.colorbarLabel.setVisible(False) else: colormap_layout.addWidget(self.colorbarLabel) colormap_layout.addWidget(self.colormapComboBox) colormap_layout.addStretch(1) # grid_layout created in QtLayerControls # addWidget(widget, row, column, [row_span, column_span]) self.grid_layout.addWidget(QLabel('opacity:'), 0, 0) self.grid_layout.addWidget(self.opacitySlider, 0, 1) self.grid_layout.addWidget(QLabel('contrast limits:'), 1, 0) self.grid_layout.addWidget(self.contrastLimitsSlider, 1, 1) self.grid_layout.addWidget(QLabel('gamma:'), 2, 0) self.grid_layout.addWidget(self.gammaSlider, 2, 1) self.grid_layout.addWidget(QLabel('colormap:'), 3, 0) self.grid_layout.addLayout(colormap_layout, 3, 1) self.grid_layout.addWidget(QLabel('blending:'), 4, 0) self.grid_layout.addWidget(self.blendComboBox, 4, 1) self.grid_layout.addWidget(self.interpLabel, 5, 0) self.grid_layout.addWidget(self.interpComboBox, 5, 1) self.grid_layout.addWidget(self.renderLabel, 6, 0) self.grid_layout.addWidget(self.renderComboBox, 6, 1) self.grid_layout.addWidget(self.isoThresholdLabel, 7, 0) self.grid_layout.addWidget(self.isoThresholdSlider, 7, 1) self.grid_layout.addWidget(self.attenuationLabel, 8, 0) self.grid_layout.addWidget(self.attenuationSlider, 8, 1) self.grid_layout.setRowStretch(9, 1) self.grid_layout.setColumnStretch(1, 1) self.grid_layout.setSpacing(4) def changeInterpolation(self, text): """Change interpolation mode for image display. Parameters ---------- text : str Interpolation mode used by vispy. Must be one of our supported modes: 'bessel', 'bicubic', 'bilinear', 'blackman', 'catrom', 'gaussian', 'hamming', 'hanning', 'hermite', 'kaiser', 'lanczos', 'mitchell', 'nearest', 'spline16', 'spline36' """ self.layer.interpolation = text def changeRendering(self, text): """Change rendering mode for image display. Parameters ---------- text : str Rendering mode used by vispy. Selects a preset rendering mode in vispy that determines how volume is displayed: * translucent: voxel colors are blended along the view ray until the result is opaque. * mip: maximum intensity projection. Cast a ray and display the maximum value that was encountered. * additive: voxel colors are added along the view ray until the result is saturated. * iso: isosurface. Cast a ray until a certain threshold is encountered. At that location, lighning calculations are performed to give the visual appearance of a surface. * attenuated_mip: attenuated maximum intensity projection. Cast a ray and attenuate values based on integral of encountered values, display the maximum value that was encountered after attenuation. This will make nearer objects appear more prominent. """ self.layer.rendering = text self._toggle_rendering_parameter_visbility() def changeIsoThreshold(self, value): """Change isosurface threshold on the layer model. Parameters ---------- value : float Threshold for isosurface. """ with self.layer.events.blocker(self._on_iso_threshold_change): self.layer.iso_threshold = value / 100 def _on_iso_threshold_change(self, event): """Receive layer model isosurface change event and update the slider. Parameters ---------- event : napari.utils.event.Event The napari event that triggered this method. """ with self.layer.events.iso_threshold.blocker(): self.isoThresholdSlider.setValue( int(self.layer.iso_threshold * 100)) def changeAttenuation(self, value): """Change attenuation rate for attenuated maximum intensity projection. Parameters ---------- value : Float Attenuation rate for attenuated maximum intensity projection. """ with self.layer.events.blocker(self._on_attenuation_change): self.layer.attenuation = value / 200 def _on_attenuation_change(self, event): """Receive layer model attenuation change event and update the slider. Parameters ---------- event : napari.utils.event.Event The napari event that triggered this method. """ with self.layer.events.attenuation.blocker(): self.attenuationSlider.setValue(int(self.layer.attenuation * 200)) def _on_interpolation_change(self, event): """Receive layer interpolation change event and update dropdown menu. Parameters ---------- event : napari.utils.event.Event The napari event that triggered this method. """ with self.layer.events.interpolation.blocker(): index = self.interpComboBox.findText(self.layer.interpolation, Qt.MatchFixedString) self.interpComboBox.setCurrentIndex(index) def _on_rendering_change(self, event): """Receive layer model rendering change event and update dropdown menu. Parameters ---------- event : napari.utils.event.Event The napari event that triggered this method. """ with self.layer.events.rendering.blocker(): index = self.renderComboBox.findText(self.layer.rendering, Qt.MatchFixedString) self.renderComboBox.setCurrentIndex(index) self._toggle_rendering_parameter_visbility() def _toggle_rendering_parameter_visbility(self): """Hide isosurface rendering parameters if they aren't needed.""" rendering = Rendering(self.layer.rendering) if rendering == Rendering.ISO: self.isoThresholdSlider.show() self.isoThresholdLabel.show() else: self.isoThresholdSlider.hide() self.isoThresholdLabel.hide() if rendering == Rendering.ATTENUATED_MIP: self.attenuationSlider.show() self.attenuationLabel.show() else: self.attenuationSlider.hide() self.attenuationLabel.hide() def _update_interpolation_combo(self): self.interpComboBox.clear() interp_enum = (Interpolation3D if self.layer._dims.ndisplay == 3 else Interpolation) self.interpComboBox.addItems(interp_enum.keys()) index = self.interpComboBox.findText(self.layer.interpolation, Qt.MatchFixedString) self.interpComboBox.setCurrentIndex(index) def _on_ndisplay_change(self, event=None): """Toggle between 2D and 3D visualization modes. Parameters ---------- event : napari.utils.event.Event, optional The napari event that triggered this method, default is None. """ self._update_interpolation_combo() if self.layer._dims.ndisplay == 2: self.isoThresholdSlider.hide() self.isoThresholdLabel.hide() self.attenuationSlider.hide() self.attenuationLabel.hide() self.renderComboBox.hide() self.renderLabel.hide() else: self.renderComboBox.show() self.renderLabel.show() self._toggle_rendering_parameter_visbility()
class DialogUpdateApplication(DialogBase): """Update application dialog.""" WIDTH = 460 def __init__(self, version, config=CONF, startup=False): """ Update application dialog. Parameter --------- version: str New version of update available. """ super(DialogUpdateApplication, self).__init__() self.tracker = GATracker() self.label = QLabel( "There's a new version of Anaconda Navigator available. " "We strongly recommend you to update. <br><br>" "If you click yes, you Anaconda Navigator will close and the " "Anaconda Navigator Updater will start.<br><br><br>" "Do you wish to update to <b>Anaconda Navigator {0}</b> now?" "<br><br>".format(version)) self.button_yes = ButtonPrimary('Yes') self.button_no = ButtonNormal('No, remind me later') self.button_no_show = ButtonNormal("No, don't show again") self.config = config if not startup: self.button_no_show.setVisible(False) self.button_no.setText('No') # Widgets setup self.label.setWordWrap(True) self.setMinimumWidth(self.WIDTH) self.setMaximumWidth(self.WIDTH) self.setWindowTitle('Update Application') # Layouts layout_buttons = QHBoxLayout() layout_buttons.addStretch() layout_buttons.addWidget(self.button_no_show) layout_buttons.addWidget(SpacerHorizontal()) layout_buttons.addWidget(self.button_no) layout_buttons.addWidget(SpacerHorizontal()) layout_buttons.addWidget(self.button_yes) layout = QVBoxLayout() layout.addWidget(self.label) layout_buttons.addWidget(SpacerVertical()) layout_buttons.addWidget(SpacerVertical()) layout.addLayout(layout_buttons) self.setLayout(layout) # Signals self.button_yes.clicked.connect(self.accept) self.button_no.clicked.connect(self.reject) self.button_no_show.clicked.connect(self.no_show) self.button_yes.setFocus() def no_show(self): """Handle not showing updates on startup.""" self.config.set('main', 'hide_update_dialog', True) self.reject()
def _init_selection_ui(self): # LINE 1: Radio box spatial vs spectral axis self.axes_prompt = QLabel("Smoothing Axis:") self.axes_prompt.setMinimumWidth(150) self.spatial_radio = QRadioButton("Spatial") self.spatial_radio.setChecked(True) self.current_axis = "spatial" self.spatial_radio.toggled.connect(self.spatial_radio_checked) self.spectral_radio = QRadioButton("Spectral") self.spectral_radio.toggled.connect(self.spectral_radio_checked) # hbl is short for Horizontal Box Layout hbl1 = QHBoxLayout() hbl1.addWidget(self.axes_prompt) hbl1.addWidget(self.spatial_radio) hbl1.addWidget(self.spectral_radio) # LINE 2: Kernel Type prompt self.k_type_prompt = QLabel("Kernel Type:") self.k_type_prompt.setMinimumWidth(150) # Load kernel types + names and add to drop down self._load_options() self.combo = QComboBox() self.combo.setMinimumWidth(150) self.combo.addItems(self.options[self.current_axis]) hbl2 = QHBoxLayout() hbl2.addWidget(self.k_type_prompt) hbl2.addWidget(self.combo) # LINE 3: Kernel size self.size_prompt = QLabel( self.smooth_cube.get_kernel_size_prompt(self.current_kernel_type)) self.size_prompt.setWordWrap(True) self.size_prompt.setMinimumWidth(150) self.unit_label = QLabel( self.smooth_cube.get_kernel_unit(self.current_kernel_type)) self.k_size = QLineEdit("1") # Default Kernel size set here hbl3 = QHBoxLayout() hbl3.addWidget(self.size_prompt) hbl3.addWidget(self.k_size) hbl3.addWidget(self.unit_label) # LINE 4: Data component drop down self.component_prompt = QLabel("Data Component:") self.component_prompt.setWordWrap(True) self.component_prompt.setMinimumWidth(150) # Load component_ids and add to drop down # Add the data component labels to the drop down, with the ComponentID # set as the userData: if self.parent is not None and hasattr(self.parent, 'data_components'): labeldata = [(str(cid), cid) for cid in self.parent.data_components] else: labeldata = [(str(cid), cid) for cid in self.data.main_components()] self.component_combo = QComboBox() update_combobox(self.component_combo, labeldata) self.component_combo.setMaximumWidth(150) self.component_combo.setCurrentIndex(0) if self.allow_preview: self.component_combo.currentIndexChanged.connect( self.update_preview_button) hbl4 = QHBoxLayout() hbl4.addWidget(self.component_prompt) hbl4.addWidget(self.component_combo) # Line 5: Preview Message message = "Info: Smoothing previews are displayed on " \ "CubeViz's left and single image viewers." self.preview_message = QLabel(message) self.preview_message.setWordWrap(True) self.preview_message.hide() hbl5 = QHBoxLayout() hbl5.addWidget(self.preview_message) # LINE 6: preview ok cancel buttons self.previewButton = QPushButton("Preview Slice") self.previewButton.clicked.connect(self.call_preview) self.okButton = QPushButton("Smooth Cube") self.okButton.clicked.connect(self.call_main) self.okButton.setDefault(True) self.cancelButton = QPushButton("Cancel") self.cancelButton.clicked.connect(self.cancel) hbl6 = QHBoxLayout() hbl6.addStretch(1) if self.allow_preview: hbl6.addWidget(self.previewButton) hbl6.addWidget(self.cancelButton) hbl6.addWidget(self.okButton) # Add Lines to Vertical Layout # vbl is short for Vertical Box Layout vbl = QVBoxLayout() if self.allow_spectral_axes: vbl.addLayout(hbl1) vbl.addLayout(hbl2) vbl.addLayout(hbl3) vbl.addLayout(hbl4) vbl.addLayout(hbl5) vbl.addLayout(hbl6) self.setLayout(vbl) self.setMaximumWidth(330) # Connect kernel combo box to event handler self.combo.currentIndexChanged.connect(self.selection_changed) self.selection_changed(0) self.show()
class ShortcutEditor(QDialog): """A dialog for entering key sequences.""" def __init__(self, parent, context, name, sequence, shortcuts): super(ShortcutEditor, self).__init__(parent) self._parent = parent self.context = context self.name = name self.shortcuts = shortcuts self.current_sequence = sequence self._qsequences = list() self.setup() self.update_warning() @property def new_sequence(self): """Return a string representation of the new key sequence.""" return ', '.join(self._qsequences) @property def new_qsequence(self): """Return the QKeySequence object of the new key sequence.""" return QKeySequence(self.new_sequence) def setup(self): """Setup the ShortcutEditor with the provided arguments.""" # Widgets icon_info = HelperToolButton() icon_info.setIcon(get_std_icon('MessageBoxInformation')) layout_icon_info = QVBoxLayout() layout_icon_info.setContentsMargins(0, 0, 0, 0) layout_icon_info.setSpacing(0) layout_icon_info.addWidget(icon_info) layout_icon_info.addStretch(100) self.label_info = QLabel() self.label_info.setText( _("Press the new shortcut and select 'Ok' to confirm, " "click 'Cancel' to revert to the previous state, " "or use 'Clear' to unbind the command from a shortcut.")) self.label_info.setAlignment(Qt.AlignTop | Qt.AlignLeft) self.label_info.setWordWrap(True) layout_info = QHBoxLayout() layout_info.setContentsMargins(0, 0, 0, 0) layout_info.addLayout(layout_icon_info) layout_info.addWidget(self.label_info) layout_info.setStretch(1, 100) self.label_current_sequence = QLabel(_("Current shortcut:")) self.text_current_sequence = QLabel(self.current_sequence) self.label_new_sequence = QLabel(_("New shortcut:")) self.text_new_sequence = ShortcutLineEdit(self) self.text_new_sequence.setPlaceholderText(_("Press shortcut.")) self.helper_button = HelperToolButton() self.helper_button.setIcon(QIcon()) self.label_warning = QLabel() self.label_warning.setWordWrap(True) self.label_warning.setAlignment(Qt.AlignTop | Qt.AlignLeft) self.button_default = QPushButton(_('Default')) self.button_ok = QPushButton(_('Ok')) self.button_ok.setEnabled(False) self.button_clear = QPushButton(_('Clear')) self.button_cancel = QPushButton(_('Cancel')) button_box = QHBoxLayout() button_box.addWidget(self.button_default) button_box.addStretch(100) button_box.addWidget(self.button_ok) button_box.addWidget(self.button_clear) button_box.addWidget(self.button_cancel) # New Sequence button box self.btn_clear_sequence = create_toolbutton( self, icon=ima.icon('editclear'), tip=_("Clear all entered key sequences"), triggered=self.clear_new_sequence) self.button_back_sequence = create_toolbutton( self, icon=ima.icon('ArrowBack'), tip=_("Remove last key sequence entered"), triggered=self.back_new_sequence) newseq_btnbar = QHBoxLayout() newseq_btnbar.setSpacing(0) newseq_btnbar.setContentsMargins(0, 0, 0, 0) newseq_btnbar.addWidget(self.button_back_sequence) newseq_btnbar.addWidget(self.btn_clear_sequence) # Setup widgets self.setWindowTitle(_('Shortcut: {0}').format(self.name)) self.helper_button.setToolTip('') style = """ QToolButton { margin:1px; border: 0px solid grey; padding:0px; border-radius: 0px; }""" self.helper_button.setStyleSheet(style) icon_info.setToolTip('') icon_info.setStyleSheet(style) # Layout layout_sequence = QGridLayout() layout_sequence.setContentsMargins(0, 0, 0, 0) layout_sequence.addLayout(layout_info, 0, 0, 1, 4) layout_sequence.addItem(QSpacerItem(15, 15), 1, 0, 1, 4) layout_sequence.addWidget(self.label_current_sequence, 2, 0) layout_sequence.addWidget(self.text_current_sequence, 2, 2) layout_sequence.addWidget(self.label_new_sequence, 3, 0) layout_sequence.addWidget(self.helper_button, 3, 1) layout_sequence.addWidget(self.text_new_sequence, 3, 2) layout_sequence.addLayout(newseq_btnbar, 3, 3) layout_sequence.addWidget(self.label_warning, 4, 2, 1, 2) layout_sequence.setColumnStretch(2, 100) layout_sequence.setRowStretch(4, 100) layout = QVBoxLayout() layout.addLayout(layout_sequence) layout.addSpacing(5) layout.addLayout(button_box) self.setLayout(layout) # Signals self.button_ok.clicked.connect(self.accept_override) self.button_clear.clicked.connect(self.unbind_shortcut) self.button_cancel.clicked.connect(self.reject) self.button_default.clicked.connect(self.set_sequence_to_default) # Set all widget to no focus so that we can register <Tab> key # press event. widgets = (self.label_warning, self.helper_button, self.text_new_sequence, self.button_clear, self.button_default, self.button_cancel, self.button_ok, self.btn_clear_sequence, self.button_back_sequence) for w in widgets: w.setFocusPolicy(Qt.NoFocus) w.clearFocus() @Slot() def reject(self): """Slot for rejected signal.""" # Added for spyder-ide/spyder#5426. Due to the focusPolicy of # Qt.NoFocus for the buttons, if the cancel button was clicked without # first setting focus to the button, it would cause a seg fault crash. self.button_cancel.setFocus() super(ShortcutEditor, self).reject() @Slot() def accept(self): """Slot for accepted signal.""" # Added for spyder-ide/spyder#5426. Due to the focusPolicy of # Qt.NoFocus for the buttons, if the cancel button was clicked without # first setting focus to the button, it would cause a seg fault crash. self.button_ok.setFocus() super(ShortcutEditor, self).accept() def event(self, event): """Qt method override.""" if event.type() in (QEvent.Shortcut, QEvent.ShortcutOverride): return True else: return super(ShortcutEditor, self).event(event) def keyPressEvent(self, event): """Qt method override.""" event_key = event.key() if not event_key or event_key == Qt.Key_unknown: return if len(self._qsequences) == 4: # QKeySequence accepts a maximum of 4 different sequences. return if event_key in [ Qt.Key_Control, Qt.Key_Shift, Qt.Key_Alt, Qt.Key_Meta ]: # The event corresponds to just and only a special key. return translator = ShortcutTranslator() event_keyseq = translator.keyevent_to_keyseq(event) event_keystr = event_keyseq.toString(QKeySequence.PortableText) self._qsequences.append(event_keystr) self.update_warning() def check_conflicts(self): """Check shortcuts for conflicts.""" conflicts = [] if len(self._qsequences) == 0: return conflicts new_qsequence = self.new_qsequence for shortcut in self.shortcuts: shortcut_qsequence = QKeySequence.fromString(str(shortcut.key)) if shortcut_qsequence.isEmpty(): continue if (shortcut.context, shortcut.name) == (self.context, self.name): continue if shortcut.context in [self.context, '_'] or self.context == '_': if (shortcut_qsequence.matches(new_qsequence) or new_qsequence.matches(shortcut_qsequence)): conflicts.append(shortcut) return conflicts def check_ascii(self): """ Check that all characters in the new sequence are ascii or else the shortcut will not work. """ try: self.new_sequence.encode('ascii') except UnicodeEncodeError: return False else: return True def check_singlekey(self): """Check if the first sub-sequence of the new key sequence is valid.""" if len(self._qsequences) == 0: return True else: keystr = self._qsequences[0] valid_single_keys = (EDITOR_SINGLE_KEYS if self.context == 'editor' else SINGLE_KEYS) if any((m in keystr for m in ('Ctrl', 'Alt', 'Shift', 'Meta'))): return True else: # This means that the the first subsequence is composed of # a single key with no modifier. valid_single_keys = (EDITOR_SINGLE_KEYS if self.context == 'editor' else SINGLE_KEYS) if any((k == keystr for k in valid_single_keys)): return True else: return False def update_warning(self): """Update the warning label, buttons state and sequence text.""" new_qsequence = self.new_qsequence new_sequence = self.new_sequence self.text_new_sequence.setText( new_qsequence.toString(QKeySequence.NativeText)) conflicts = self.check_conflicts() if len(self._qsequences) == 0: warning = SEQUENCE_EMPTY tip = '' icon = QIcon() elif conflicts: warning = SEQUENCE_CONFLICT template = '<i>{0}<b>{1}</b>{2}</i>' tip_title = _('The new shortcut conflicts with:') + '<br>' tip_body = '' for s in conflicts: tip_body += ' - {0}: {1}<br>'.format(s.context, s.name) tip_body = tip_body[:-4] # Removing last <br> tip_override = '<br>Press <b>OK</b> to unbind ' tip_override += 'it' if len(conflicts) == 1 else 'them' tip_override += ' and assign it to <b>{}</b>'.format(self.name) tip = template.format(tip_title, tip_body, tip_override) icon = get_std_icon('MessageBoxWarning') elif new_sequence in BLACKLIST: warning = IN_BLACKLIST template = '<i>{0}<b>{1}</b></i>' tip_title = _('Forbidden key sequence!') + '<br>' tip_body = '' use = BLACKLIST[new_sequence] if use is not None: tip_body = use tip = template.format(tip_title, tip_body) icon = get_std_icon('MessageBoxWarning') elif self.check_singlekey() is False or self.check_ascii() is False: warning = INVALID_KEY template = '<i>{0}</i>' tip = _('Invalid key sequence entered') + '<br>' icon = get_std_icon('MessageBoxWarning') else: warning = NO_WARNING tip = 'This shortcut is valid.' icon = get_std_icon('DialogApplyButton') self.warning = warning self.conflicts = conflicts self.helper_button.setIcon(icon) self.button_ok.setEnabled( self.warning in [NO_WARNING, SEQUENCE_CONFLICT]) self.label_warning.setText(tip) # Everytime after update warning message, update the label height new_height = self.label_warning.sizeHint().height() self.label_warning.setMaximumHeight(new_height) def set_sequence_from_str(self, sequence): """ This is a convenience method to set the new QKeySequence of the shortcut editor from a string. """ self._qsequences = [QKeySequence(s) for s in sequence.split(', ')] self.update_warning() def set_sequence_to_default(self): """Set the new sequence to the default value defined in the config.""" sequence = CONF.get_default('shortcuts', "{}/{}".format(self.context, self.name)) self._qsequences = sequence.split(', ') self.update_warning() def back_new_sequence(self): """Remove the last subsequence from the sequence compound.""" self._qsequences = self._qsequences[:-1] self.update_warning() def clear_new_sequence(self): """Clear the new sequence.""" self._qsequences = [] self.update_warning() def unbind_shortcut(self): """Unbind the shortcut.""" self._qsequences = [] self.accept() def accept_override(self): """Unbind all conflicted shortcuts, and accept the new one""" conflicts = self.check_conflicts() if conflicts: for shortcut in conflicts: shortcut.key = '' self.accept()
class SelectSmoothing(QDialog): """ SelectSmoothing launches a GUI and executes smoothing. Any output is added to the input data as a new component. """ def __init__(self, data, parent=None, smooth_cube=None, allow_preview=False, allow_spectral_axes=False): super(SelectSmoothing, self).__init__(parent) self.setWindowFlags(self.windowFlags() | Qt.Tool) self.parent = parent self.title = "Smoothing Selection" self.data = data # Glue data to be smoothed # Check if smooth object is the caller if smooth_cube is None: self.smooth_cube = SmoothCube(data=self.data) else: self.smooth_cube = smooth_cube self.allow_spectral_axes = allow_spectral_axes self.allow_preview = allow_preview self.is_preview_active = False # Flag to show if smoothing preview is active self.abort_window = None # Small window pop up when smoothing. self.component_id = None # Glue data component to smooth over self.current_axis = None # Selected smoothing_axis self.current_kernel_type = None # Selected kernel type, a key in SmoothCube.kernel_registry self.current_kernel_name = None # Name of selected kernel self._init_selection_ui() # Format and show gui def _init_selection_ui(self): # LINE 1: Radio box spatial vs spectral axis self.axes_prompt = QLabel("Smoothing Axis:") self.axes_prompt.setMinimumWidth(150) self.spatial_radio = QRadioButton("Spatial") self.spatial_radio.setChecked(True) self.current_axis = "spatial" self.spatial_radio.toggled.connect(self.spatial_radio_checked) self.spectral_radio = QRadioButton("Spectral") self.spectral_radio.toggled.connect(self.spectral_radio_checked) # hbl is short for Horizontal Box Layout hbl1 = QHBoxLayout() hbl1.addWidget(self.axes_prompt) hbl1.addWidget(self.spatial_radio) hbl1.addWidget(self.spectral_radio) # LINE 2: Kernel Type prompt self.k_type_prompt = QLabel("Kernel Type:") self.k_type_prompt.setMinimumWidth(150) # Load kernel types + names and add to drop down self._load_options() self.combo = QComboBox() self.combo.setMinimumWidth(150) self.combo.addItems(self.options[self.current_axis]) hbl2 = QHBoxLayout() hbl2.addWidget(self.k_type_prompt) hbl2.addWidget(self.combo) # LINE 3: Kernel size self.size_prompt = QLabel( self.smooth_cube.get_kernel_size_prompt(self.current_kernel_type)) self.size_prompt.setWordWrap(True) self.size_prompt.setMinimumWidth(150) self.unit_label = QLabel( self.smooth_cube.get_kernel_unit(self.current_kernel_type)) self.k_size = QLineEdit("1") # Default Kernel size set here hbl3 = QHBoxLayout() hbl3.addWidget(self.size_prompt) hbl3.addWidget(self.k_size) hbl3.addWidget(self.unit_label) # LINE 4: Data component drop down self.component_prompt = QLabel("Data Component:") self.component_prompt.setWordWrap(True) self.component_prompt.setMinimumWidth(150) # Load component_ids and add to drop down # Add the data component labels to the drop down, with the ComponentID # set as the userData: if self.parent is not None and hasattr(self.parent, 'data_components'): labeldata = [(str(cid), cid) for cid in self.parent.data_components] else: labeldata = [(str(cid), cid) for cid in self.data.main_components()] self.component_combo = QComboBox() update_combobox(self.component_combo, labeldata) self.component_combo.setMaximumWidth(150) self.component_combo.setCurrentIndex(0) if self.allow_preview: self.component_combo.currentIndexChanged.connect( self.update_preview_button) hbl4 = QHBoxLayout() hbl4.addWidget(self.component_prompt) hbl4.addWidget(self.component_combo) # Line 5: Preview Message message = "Info: Smoothing previews are displayed on " \ "CubeViz's left and single image viewers." self.preview_message = QLabel(message) self.preview_message.setWordWrap(True) self.preview_message.hide() hbl5 = QHBoxLayout() hbl5.addWidget(self.preview_message) # LINE 6: preview ok cancel buttons self.previewButton = QPushButton("Preview Slice") self.previewButton.clicked.connect(self.call_preview) self.okButton = QPushButton("Smooth Cube") self.okButton.clicked.connect(self.call_main) self.okButton.setDefault(True) self.cancelButton = QPushButton("Cancel") self.cancelButton.clicked.connect(self.cancel) hbl6 = QHBoxLayout() hbl6.addStretch(1) if self.allow_preview: hbl6.addWidget(self.previewButton) hbl6.addWidget(self.cancelButton) hbl6.addWidget(self.okButton) # Add Lines to Vertical Layout # vbl is short for Vertical Box Layout vbl = QVBoxLayout() if self.allow_spectral_axes: vbl.addLayout(hbl1) vbl.addLayout(hbl2) vbl.addLayout(hbl3) vbl.addLayout(hbl4) vbl.addLayout(hbl5) vbl.addLayout(hbl6) self.setLayout(vbl) self.setMaximumWidth(330) # Connect kernel combo box to event handler self.combo.currentIndexChanged.connect(self.selection_changed) self.selection_changed(0) self.show() def _load_options(self): """Extract names + types of kernels from SmoothCube.kernel_registry""" kernel_registry = self.smooth_cube.get_kernel_registry() self.options = {"spatial": [], "spectral": []} for k in kernel_registry: axis = kernel_registry[k]["axis"] for a in axis: if "spatial" == a: self.options["spatial"].append(kernel_registry[k]["name"]) elif "spectral" == a: self.options["spectral"].append(kernel_registry[k]["name"]) self.options["spectral"].sort() self.options["spatial"].sort() self.current_kernel_name = self.options[self.current_axis][0] self.current_kernel_type = self.smooth_cube.name_to_kernel_type( self.options[self.current_axis][0]) def selection_changed(self, i): """ Update kernel type, units, etc... when kernel name changes in combo box. """ keys = self.options[self.current_axis] name = keys[i] self.current_kernel_name = name self.current_kernel_type = self.smooth_cube.name_to_kernel_type(name) self.unit_label.setText( self.smooth_cube.get_kernel_unit(self.current_kernel_type)) self.size_prompt.setText( self.smooth_cube.get_kernel_size_prompt(self.current_kernel_type)) def spatial_radio_checked(self): self.current_axis = "spatial" self.update_preview_button() self.combo.clear() self.combo.addItems(self.options[self.current_axis]) def spectral_radio_checked(self): self.current_axis = "spectral" self.update_preview_button() self.combo.clear() self.combo.addItems(self.options[self.current_axis]) def input_validation(self): """ Check if input will break Smoothing :return: bool: True if no errors """ red = "background-color: rgba(255, 0, 0, 128);" success = True # Check 1: k_size if self.k_size == "": self.k_size.setStyleSheet(red) success = False else: try: if self.current_kernel_type == "median": k_size = int(self.k_size.text()) else: k_size = float(self.k_size.text()) if k_size <= 0: self.k_size.setStyleSheet(red) success = False else: self.k_size.setStyleSheet("") except ValueError: if self.current_kernel_type == "median": info = QMessageBox.critical( self, "Error", "Kernel size must be integer for median") self.k_size.setStyleSheet(red) success = False return success def call_main(self): try: self.main() except Exception as e: info = QMessageBox.critical(self, "Error", str(e)) self.cancel() raise def main(self): """ Main function to process input and call smoothing function """ success = self.input_validation() if not success: return self.hide() self.abort_window = AbortWindow(self) QApplication.processEvents() # Add smoothing parameters self.smooth_cube.abort_window = self.abort_window if self.smooth_cube.parent is None and self.parent is not self.smooth_cube: self.smooth_cube.parent = self.parent if self.parent is not self.smooth_cube: self.smooth_cube.data = self.data self.smooth_cube.smoothing_axis = self.current_axis self.smooth_cube.kernel_type = self.current_kernel_type if self.current_kernel_type == "median": self.smooth_cube.kernel_size = int(self.k_size.text()) else: self.smooth_cube.kernel_size = float(self.k_size.text()) self.smooth_cube.component_id = str(self.component_combo.currentText()) self.smooth_cube.output_as_component = True if self.is_preview_active: self.parent.end_smoothing_preview() self.is_preview_active = False self.smooth_cube.multi_threading_smooth() return def update_preview_button(self): if self.parent is None or "spatial" != self.current_axis: self.previewButton.setDisabled(True) return self.previewButton.setDisabled(False) return def call_preview(self): try: self.preview() except Exception as e: info = QMessageBox.critical(self, "Error", str(e)) self.cancel() raise def preview(self): """Preview current options""" success = self.input_validation() if not success: return if self.smooth_cube.parent is None and self.parent is not self.smooth_cube: self.smooth_cube.parent = self.parent self.smooth_cube.smoothing_axis = self.current_axis self.smooth_cube.kernel_type = self.current_kernel_type if self.current_kernel_type == "median": self.smooth_cube.kernel_size = int(self.k_size.text()) else: self.smooth_cube.kernel_size = float(self.k_size.text()) preview_function = self.smooth_cube.preview_smoothing preview_title = self.smooth_cube.get_preview_title() component_id = self.component_combo.currentData() self.parent.start_smoothing_preview(preview_function, component_id, preview_title) self.is_preview_active = True self.preview_message.show() def cancel(self): self.clean_up() def clean_up(self): self.close() if self.abort_window is not None: self.abort_window.close() if self.is_preview_active: self.parent.end_smoothing_preview() self.is_preview_active = False def closeEvent(self, event): if self.is_preview_active: self.parent.end_smoothing_preview() self.is_preview_active = False def keyPressEvent(self, e): if e.key() == Qt.Key_Escape: self.clean_up()
def __init__(self): super().__init__() self.setWindowTitle("About PyXRF") self.setFixedSize(500, 500) text_name = "PyXRF" text_description = "X-Ray Fluorescence Analysis Tool" text_ver = f"Version: {pyxrf.__version__}" text_latest_ver = "Latest stable version:" text_credit = "Credits:" text_credit_org = ( "Data Acquisition, Management and Analysis Group\n" "National Synchrontron Light Source II\n" "Brookhaven National Laboratory" ) text_copyright = f"\u00A92015\u2014{datetime.now().year} Brookhaven National Laboratory" label_name = QLabel(text_name) label_name.setStyleSheet("QLabel {font-weight: bold; font-size: 32px}") label_description = QLabel(text_description) label_description.setStyleSheet("QLabel {font-style: italic; font-size: 18px}") label_ver = QLabel(text_ver) label_latest_ver = QLabel(text_latest_ver) label_credit = QLabel(text_credit) label_org = QLabel(text_credit_org) label_copyright = QLabel(text_copyright) button_box = QDialogButtonBox(QDialogButtonBox.Close) button_box.accepted.connect(self.accept) button_box.rejected.connect(self.reject) vbox = QVBoxLayout() vbox.addStretch(1) hbox = QHBoxLayout() hbox.addStretch(1) hbox.addWidget(label_name) hbox.addStretch(1) vbox.addLayout(hbox) hbox = QHBoxLayout() hbox.addStretch(1) hbox.addWidget(label_description) hbox.addStretch(1) vbox.addLayout(hbox) vbox.addStretch(1) hbox = QHBoxLayout() hbox.addSpacing(30) hbox.addWidget(label_ver) hbox.addStretch(1) vbox.addLayout(hbox) hbox = QHBoxLayout() hbox.addSpacing(30) hbox.addWidget(label_latest_ver) hbox.addStretch(1) vbox.addLayout(hbox) vbox.addStretch(1) hbox = QHBoxLayout() hbox.addSpacing(30) hbox.addWidget(label_credit, 0, Qt.AlignTop) hbox.addWidget(label_org, 0, Qt.AlignTop) hbox.addStretch(1) vbox.addLayout(hbox) vbox.addSpacing(20) hbox = QHBoxLayout() hbox.addSpacing(30) hbox.addWidget(label_copyright) hbox.addStretch(1) vbox.addLayout(hbox) vbox.addSpacing(20) vbox.addWidget(button_box) self.setLayout(vbox)
def ui(self): """ Define UI's elements """ self.setGeometry(self.left, self.top, self.width, self.height) layout = QVBoxLayout() # Alpha alpha_label = QLabel(self) alpha_label.setObjectName("PopupLabel") alpha_label.setText("Alpha") self.alpha_textbox = QLineEdit(self) self.alpha_textbox.setText(str(1.0)) # Color color_label = QLabel(self) color_label.setObjectName("PopupLabel") color_label.setText("Color") self.color_textbox = QLineEdit(self) self.color_textbox.setText("default") # Create a button in the window self.button = QPushButton("Ok", self) self.button.clicked.connect(self.on_click) self.button.setObjectName("RegionsButton") layout.addWidget(alpha_label) layout.addWidget(self.alpha_textbox) layout.addWidget(color_label) layout.addWidget(self.color_textbox) layout.addWidget(self.button) self.setLayout(layout) self.show()
def get_image_label(name, default="not_found.png"): """Return image inside a QLabel object""" label = QLabel() label.setPixmap(QPixmap(get_image_path(name, default))) return label
def setup(self): """Setup the ShortcutEditor with the provided arguments.""" # Widgets icon_info = HelperToolButton() icon_info.setIcon(get_std_icon('MessageBoxInformation')) layout_icon_info = QVBoxLayout() layout_icon_info.setContentsMargins(0, 0, 0, 0) layout_icon_info.setSpacing(0) layout_icon_info.addWidget(icon_info) layout_icon_info.addStretch(100) self.label_info = QLabel() self.label_info.setText( _("Press the new shortcut and select 'Ok' to confirm, " "click 'Cancel' to revert to the previous state, " "or use 'Clear' to unbind the command from a shortcut.")) self.label_info.setAlignment(Qt.AlignTop | Qt.AlignLeft) self.label_info.setWordWrap(True) layout_info = QHBoxLayout() layout_info.setContentsMargins(0, 0, 0, 0) layout_info.addLayout(layout_icon_info) layout_info.addWidget(self.label_info) layout_info.setStretch(1, 100) self.label_current_sequence = QLabel(_("Current shortcut:")) self.text_current_sequence = QLabel(self.current_sequence) self.label_new_sequence = QLabel(_("New shortcut:")) self.text_new_sequence = ShortcutLineEdit(self) self.text_new_sequence.setPlaceholderText(_("Press shortcut.")) self.helper_button = HelperToolButton() self.helper_button.setIcon(QIcon()) self.label_warning = QLabel() self.label_warning.setWordWrap(True) self.label_warning.setAlignment(Qt.AlignTop | Qt.AlignLeft) self.button_default = QPushButton(_('Default')) self.button_ok = QPushButton(_('Ok')) self.button_ok.setEnabled(False) self.button_clear = QPushButton(_('Clear')) self.button_cancel = QPushButton(_('Cancel')) button_box = QHBoxLayout() button_box.addWidget(self.button_default) button_box.addStretch(100) button_box.addWidget(self.button_ok) button_box.addWidget(self.button_clear) button_box.addWidget(self.button_cancel) # New Sequence button box self.btn_clear_sequence = create_toolbutton( self, icon=ima.icon('editclear'), tip=_("Clear all entered key sequences"), triggered=self.clear_new_sequence) self.button_back_sequence = create_toolbutton( self, icon=ima.icon('ArrowBack'), tip=_("Remove last key sequence entered"), triggered=self.back_new_sequence) newseq_btnbar = QHBoxLayout() newseq_btnbar.setSpacing(0) newseq_btnbar.setContentsMargins(0, 0, 0, 0) newseq_btnbar.addWidget(self.button_back_sequence) newseq_btnbar.addWidget(self.btn_clear_sequence) # Setup widgets self.setWindowTitle(_('Shortcut: {0}').format(self.name)) self.helper_button.setToolTip('') style = """ QToolButton { margin:1px; border: 0px solid grey; padding:0px; border-radius: 0px; }""" self.helper_button.setStyleSheet(style) icon_info.setToolTip('') icon_info.setStyleSheet(style) # Layout layout_sequence = QGridLayout() layout_sequence.setContentsMargins(0, 0, 0, 0) layout_sequence.addLayout(layout_info, 0, 0, 1, 4) layout_sequence.addItem(QSpacerItem(15, 15), 1, 0, 1, 4) layout_sequence.addWidget(self.label_current_sequence, 2, 0) layout_sequence.addWidget(self.text_current_sequence, 2, 2) layout_sequence.addWidget(self.label_new_sequence, 3, 0) layout_sequence.addWidget(self.helper_button, 3, 1) layout_sequence.addWidget(self.text_new_sequence, 3, 2) layout_sequence.addLayout(newseq_btnbar, 3, 3) layout_sequence.addWidget(self.label_warning, 4, 2, 1, 2) layout_sequence.setColumnStretch(2, 100) layout_sequence.setRowStretch(4, 100) layout = QVBoxLayout() layout.addLayout(layout_sequence) layout.addSpacing(5) layout.addLayout(button_box) self.setLayout(layout) # Signals self.button_ok.clicked.connect(self.accept_override) self.button_clear.clicked.connect(self.unbind_shortcut) self.button_cancel.clicked.connect(self.reject) self.button_default.clicked.connect(self.set_sequence_to_default) # Set all widget to no focus so that we can register <Tab> key # press event. widgets = (self.label_warning, self.helper_button, self.text_new_sequence, self.button_clear, self.button_default, self.button_cancel, self.button_ok, self.btn_clear_sequence, self.button_back_sequence) for w in widgets: w.setFocusPolicy(Qt.NoFocus) w.clearFocus()
def __init__(self, parent=None, *, file_path=None): super().__init__(parent) self.__file_path = "" self.__distance_to_sample = 0.0 self.__overwrite_existing = False self.__preview = ("", {}) # str - information, dict - warnings self.setWindowTitle("Save Quantitative Calibration") self.setMinimumHeight(600) self.setMinimumWidth(600) self.resize(600, 600) self.text_edit = QTextEdit() set_tooltip( self.text_edit, "Preview the <b>quantitative calibration data</b> to be saved. The displayed " "warnings will not be saved, but need to be addressed in order to keep " "data integrity. The parameter <b>distance-to-sample</b> is optional, " "but desirable. If <b>distance-to-sample</b> is zero then no scaling will be " "applied to data to compensate for changing distance.", ) self.text_edit.setReadOnly(True) self.le_file_path = LineEditReadOnly() set_tooltip( self.le_file_path, "Full path to the file used to <b>save the calibration data</b>. The path " "can be changed in file selection dialog box.", ) self.pb_file_path = PushButtonMinimumWidth("..") set_tooltip( self.pb_file_path, "Change <b>file path</b> for saving the calibration data.") self.pb_file_path.clicked.connect(self.pb_file_path_clicked) self.pb_file_path.setDefault(False) self.pb_file_path.setAutoDefault(False) self.le_distance_to_sample = LineEditExtended() self.le_distance_to_sample.textChanged.connect( self.le_distance_to_sample_text_changed) self.le_distance_to_sample.editingFinished.connect( self.le_distance_to_sample_editing_finished) self._le_distance_to_sample_validator = DoubleValidatorStrict() self._le_distance_to_sample_validator.setBottom(0.0) set_tooltip( self.le_distance_to_sample, "<b>Distance</b> between the detector and the sample during calibration. If the value " "is 0, then no scaling is applied to data to correct the data if distance-to-sample " "is changed between calibration and measurement.", ) self.cb_overwrite = QCheckBox("Overwrite Existing") self.cb_overwrite.stateChanged.connect(self.cb_overwrite_state_changed) set_tooltip( self.cb_overwrite, "Overwrite the <b>existing</b> file. This is a safety feature implemented to protect " "valuable results from accidental deletion.", ) vbox = QVBoxLayout() hbox = QHBoxLayout() hbox.addWidget( QLabel("The following data will be saved to JSON file:")) hbox.addStretch(1) vbox.addLayout(hbox) vbox.addWidget(self.text_edit) hbox = QHBoxLayout() hbox.addWidget(QLabel("Path: ")) hbox.addWidget(self.pb_file_path) hbox.addWidget(self.le_file_path) vbox.addLayout(hbox) hbox = QHBoxLayout() hbox.addWidget(QLabel("Distance-to-sample:")) hbox.addWidget(self.le_distance_to_sample) hbox.addStretch(1) hbox.addWidget(self.cb_overwrite) vbox.addLayout(hbox) button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) self.pb_ok = button_box.button(QDialogButtonBox.Ok) self.pb_ok.setDefault(False) self.pb_ok.setAutoDefault(False) self.pb_cancel = button_box.button(QDialogButtonBox.Cancel) self.pb_cancel.setDefault(True) self.pb_cancel.setAutoDefault(True) button_box.accepted.connect(self.accept) button_box.rejected.connect(self.reject) vbox.addWidget(button_box) self.setLayout(vbox) self._show_distance_to_sample() self._show_preview() self._show_overwrite_existing() # Set and display file path if file_path is not None: self.file_path = file_path
class BibleExplorer(QWidget): def __init__(self, parent, bcvTextTuple): super().__init__() self.parent = parent self.b, self.c, self.v, self.text = bcvTextTuple self.bcvChain = False self.biblesSqlite = BiblesSqlite() bibleVerseParser = BibleVerseParser(config.parserStandarisation) self.bookNo2Abb = bibleVerseParser.standardAbbreviation self.bookNo2Name = bibleVerseParser.standardFullBookName self.bookLabel = QLabel("") self.chapterLabel = QLabel("") self.verseLabel = QLabel("") # set title self.setWindowTitle(config.thisTranslation["menu_bible"]) # setup interface self.setupUI() # setup ui def setupUI(self): mainLayout = QHBoxLayout() mainLayout.addWidget(self.navigationWidget()) mainLayout.addWidget(self.featuresWidget()) self.setLayout(mainLayout) def navigationWidget(self): navigation = QWidget() navigationLayouts = QVBoxLayout() navigationLayouts.setSpacing(10) navigationLayoutsSub1 = QVBoxLayout() navigationLayoutsSub1.setSpacing(3) navigationLayout1 = self.navigationLayout1() navigationLayoutsSub1.addLayout(navigationLayout1) navigationLayout2 = self.navigationLayout2() navigationLayoutsSub1.addLayout(navigationLayout2) navigationLayouts.addLayout(navigationLayoutsSub1) navigationLayout3 = self.navigationLayout3() navigationLayouts.addLayout(navigationLayout3) navigationLayout3b = self.navigationLayout3b() navigationLayouts.addLayout(navigationLayout3b) navigationLayout4 = self.navigationLayout4() navigationLayouts.addLayout(navigationLayout4) navigationLayout5 = self.navigationLayout5() navigationLayouts.addLayout(navigationLayout5) if len(config.bibleCollections) > 0: navigationLayout6 = self.navigationLayout6() navigationLayouts.addWidget(navigationLayout6) navigationLayout7 = self.navigationLayout7() navigationLayouts.addWidget(navigationLayout7) navigationLayouts.addStretch() navigation.setLayout(navigationLayouts) return navigation def navigationLayout1(self): navigationLayout1 = QBoxLayout(QBoxLayout.LeftToRight) navigationLayout1.setSpacing(5) # Version selection self.versionCombo = QComboBox() #self.textList = self.biblesSqlite.getBibleList() self.textList = self.parent.textList self.versionCombo.addItems(self.textList) for index, fullName in enumerate(self.parent.textFullNameList): self.versionCombo.setItemData(index, fullName, Qt.ToolTipRole) initialIndex = 0 if self.text in self.textList: initialIndex = self.textList.index(self.text) self.versionCombo.setCurrentIndex(initialIndex) navigationLayout1.addWidget(self.versionCombo) # Book / Chapter / Verse selection self.bookCombo = QComboBox() navigationLayout1.addWidget(self.bookCombo) self.chapterCombo = QComboBox() navigationLayout1.addWidget(self.chapterCombo) self.verseCombo = QComboBox() navigationLayout1.addWidget(self.verseCombo) # Initial setup self.updateBookCombo() # Interactive update in response to users selection self.versionCombo.currentIndexChanged.connect(self.updateBookCombo) self.bookCombo.currentIndexChanged.connect( lambda index: self.updateChapterCombo(self.bookList[index], True)) self.chapterCombo.currentIndexChanged.connect( lambda index: self.updateVerseCombo(self.chapterList[index], True)) self.verseCombo.currentIndexChanged.connect(self.updateV) return navigationLayout1 def navigationLayout2(self): buttonElementTuple = ( ("bar1_menu", lambda: self.openInWindow("BIBLE")), ("bar2_menu", lambda: self.openInWindow("STUDY")), ("addToCommand", self.addToCommand), ("presentation", self.present), ) return self.parent.buttonsLayout(buttonElementTuple) def navigationLayout3(self): feature = "html_showParallel" action = lambda: self.versionsAction("PARALLEL") items = self.textList initialItems = list( {config.mainText, config.studyText, config.favouriteBible}) self.parallelCombo = CheckableComboBox( items, initialItems, toolTips=self.parent.textFullNameList) return self.parent.comboFeatureLayout(feature, self.parallelCombo, action) def navigationLayout3b(self): feature = "parallelVerses" action = lambda: self.versionsAction("PARALLELVERSES") items = self.textList initialItems = list( {config.mainText, config.studyText, config.favouriteBible}) self.parallelVersesCombo = CheckableComboBox( items, initialItems, toolTips=self.parent.textFullNameList) return self.parent.comboFeatureLayout(feature, self.parallelVersesCombo, action) def navigationLayout4(self): feature = "html_showCompare" action = lambda: self.versionsAction("COMPARE") items = self.textList initialItems = list( {config.mainText, config.studyText, config.favouriteBible}) self.compareCombo = CheckableComboBox( items, initialItems, toolTips=self.parent.textFullNameList) return self.parent.comboFeatureLayout(feature, self.compareCombo, action) def navigationLayout5(self): feature = "html_showDifference" action = lambda: self.versionsAction("DIFFERENCE") items = self.textList initialItems = list( {config.mainText, config.studyText, config.favouriteBible}) self.differenceCombo = CheckableComboBox( items, initialItems, toolTips=self.parent.textFullNameList) return self.parent.comboFeatureLayout(feature, self.differenceCombo, action) def navigationLayout6(self): rows = [] row = [ ("All", lambda: self.selectCollection("All")), ("None", lambda: self.selectCollection("None")), ] count = len(row) for collection in sorted(config.bibleCollections.keys()): row.append((collection, partial(self.selectCollection, collection))) count += 1 if count % 6 == 0: rows.append(row) row = [] if len(row) > 0: rows.append(row) return self.parent.buttonsWidget(rows, False, False) def navigationLayout7(self): buttonRow1 = ( ("MOB", lambda: self.openInWindow("BIBLE", "MOB")), ("MIB", lambda: self.openInWindow("BIBLE", "MIB")), ("MTB", lambda: self.openInWindow("BIBLE", "MTB")), ("MPB", lambda: self.openInWindow("BIBLE", "MPB")), ("MAB", lambda: self.openInWindow("BIBLE", "MAB")), ("SBLGNTl", lambda: self.openInWindow("BIBLE", "SBLGNTl")), ) buttonRow2 = ( ("LXX1", lambda: self.openInWindow("BIBLE", "LXX1")), ("LXX1i", lambda: self.openInWindow("BIBLE", "LXX1i")), ("LXX2", lambda: self.openInWindow("BIBLE", "LXX2")), ("LXX2i", lambda: self.openInWindow("BIBLE", "LXX2i")), ("OHGB", lambda: self.openInWindow("BIBLE", "OHGB")), ("OHGBi", lambda: self.openInWindow("BIBLE", "OHGBi")), ) buttonElementTupleTuple = (buttonRow1, buttonRow2) return self.parent.buttonsWidget(buttonElementTupleTuple, False, False) def updateBCVText(self, b, c, v, text): if text in self.textList: self.versionCombo.setCurrentIndex(self.textList.index(text)) self.text = text if b in self.bookList: self.bookCombo.setCurrentIndex(self.bookList.index(b)) self.b = b if c in self.chapterList: self.chapterCombo.setCurrentIndex(self.chapterList.index(c)) self.c = c if v in self.verseList: self.verseCombo.setCurrentIndex(self.verseList.index(v)) self.v = v self.updateBcvLabels() def updateBookCombo(self, textIndex=None, reset=False): if textIndex is None or ((textIndex is not None) and textIndex >= 0): self.bcvChain = True if textIndex is not None: self.text = self.textList[textIndex] self.bookCombo.clear() self.bookList = self.biblesSqlite.getBookList(self.text) if self.bookList: # Add only those are recognised by UBA parser for index, b in enumerate(self.bookList): strB = str(b) if strB in self.bookNo2Abb: self.bookCombo.addItem(self.bookNo2Abb[str(b)]) self.bookCombo.setItemData(index, self.bookNo2Name[str(b)], Qt.ToolTipRole) index = 0 if not reset and self.b in self.bookList: index = self.bookList.index(self.b) else: self.b = self.bookList[index] reset = True self.bookCombo.setCurrentIndex(index) # check / update self.updateChapterCombo(self.b, reset, False) def updateChapterCombo(self, b=None, reset=False, head=True): if b is None or ((b is not None) and b >= 0): if (head and not self.bcvChain) or (self.bcvChain and not head): self.bcvChain = True if b is not None: self.b = b self.chapterCombo.clear() self.chapterList = self.biblesSqlite.getChapterList( self.b, self.text) self.chapterCombo.addItems([str(c) for c in self.chapterList]) index = 0 if not reset and self.c in self.chapterList: index = self.chapterList.index(self.c) else: self.c = self.chapterList[index] reset = True self.chapterCombo.setCurrentIndex(index) # check / update self.updateVerseCombo(self.c, reset, False) def updateVerseCombo(self, c=None, reset=False, head=True): if c is None or ((c is not None) and c >= 0): if (head and not self.bcvChain) or (self.bcvChain and not head): self.bcvChain = True if c is not None: self.c = c self.verseCombo.clear() self.verseList = self.biblesSqlite.getVerseList( self.b, self.c, self.text) self.verseCombo.addItems([str(v) for v in self.verseList]) index = 0 if not reset and self.v in self.verseList: index = self.verseList.index(self.v) else: self.v = self.verseList[index] self.verseCombo.setCurrentIndex(index) # Complete update self.updateBcvLabels() self.bcvChain = False def updateV(self, index): if not self.bcvChain and (index >= 0): self.v = self.verseList[index] self.verseLabel.setText(self.getSelectedReference()) self.parent.updateBibleTabText("{0}:::{1}".format( self.text, self.getSelectedReference())) def featuresWidget(self): features = QWidget() featuresLayout = QFormLayout() featuresLayout.setSpacing(5) featuresLayout.addRow(self.bookLabel, self.bookFeatures()) featuresLayout.addRow(self.chapterLabel, self.chapterFeatures()) featuresLayout.addRow(self.verseLabel, self.verseFeatures()) features.setLayout(featuresLayout) return features def updateBcvLabels(self): self.bookLabel.setText(self.getSelectedReferenceBook()) self.chapterLabel.setText(self.getSelectedReferenceChapter()) self.verseLabel.setText(self.getSelectedReference()) self.parent.updateBibleTabText("{0}:::{1}".format( self.text, self.getSelectedReference())) def bookFeatures(self): buttonRow1 = ( ("readNotes", lambda: self.openBibleNotes("book")), ("editNotes", lambda: self.editBibleNotes("book")), ) buttonRow2 = ( ("html_introduction", lambda: self.searchBookChapter("Tidwell_The_Bible_Book_by_Book")), ("html_timelines", lambda: self.searchBookChapter("Timelines")), ("context1_dict", lambda: self.searchBookName(True)), ("context1_encyclopedia", lambda: self.searchBookName(False)), ) buttonElementTupleTuple = (buttonRow1, buttonRow2) return self.parent.buttonsWidget(buttonElementTupleTuple) def chapterFeatures(self): buttonRow1 = ( ("readNotes", lambda: self.openBibleNotes("chapter")), ("editNotes", lambda: self.editBibleNotes("chapter")), ) buttonRow2 = ( ("html_overview", lambda: self.chapterAction("OVERVIEW")), ("html_chapterIndex", lambda: self.chapterAction("CHAPTERINDEX")), ("html_summary", lambda: self.chapterAction("SUMMARY")), ("menu4_commentary", lambda: self.chapterAction("COMMENTARY")), ) buttonElementTupleTuple = (buttonRow1, buttonRow2) return self.parent.buttonsWidget(buttonElementTupleTuple) def verseFeatures(self): buttonRow1 = ( ("readNotes", lambda: self.openBibleNotes("verse")), ("editNotes", lambda: self.editBibleNotes("verse")), ) buttonRow2 = ( ("menu4_compareAll", lambda: self.verseAction("COMPARE")), ("menu4_crossRef", lambda: self.verseAction("CROSSREFERENCE")), ("menu4_tske", lambda: self.verseAction("TSKE")), ) buttonRow3 = ( ("menu4_traslations", lambda: self.verseAction("TRANSLATION")), ("menu4_discourse", lambda: self.verseAction("DISCOURSE")), ("menu4_words", lambda: self.verseAction("WORDS")), ("menu4_tdw", lambda: self.verseAction("COMBO")), ) buttonRow4 = ( ("menu4_indexes", lambda: self.verseAction("INDEX")), ("menu4_commentary", lambda: self.verseAction("COMMENTARY")), ) buttonElementTupleTuple = (buttonRow1, buttonRow2, buttonRow3, buttonRow4) return self.parent.buttonsWidget(buttonElementTupleTuple) # Selected Reference def getSelectedReference(self): return "{0} {1}:{2}".format(self.bookNo2Abb[str(self.b)], self.c, self.v) def getSelectedReferenceBook(self): return self.getSelectedReference().split(" ")[0] def getSelectedReferenceChapter(self): return self.getSelectedReference().split(":")[0] # Button actions def addToCommand(self): self.parent.commandField.setText("{0} {1}".format( self.parent.commandField.text(), self.getSelectedReference())) def openInWindow(self, window, text=""): if window == "STUDY" and config.openBibleInMainViewOnly: self.parent.parent.enableStudyBibleButtonClicked() command = "{0}:::{1}:::{2}".format(window, text if text else self.text, self.getSelectedReference()) self.parent.runTextCommand(command) def present(self): config.mainText = self.text command = "SCREEN:::{0}".format(self.getSelectedReference()) self.parent.runTextCommand(command) def openBibleNotes(self, noteType): keywords = { "book": "_openbooknote", "chapter": "_openchapternote", "verse": "_openversenote", } command = "{0}:::{1}.{2}.{3}".format(keywords[noteType], self.b, self.c, self.v) self.parent.runTextCommand(command) def versionsAction(self, keyword): selectedVersionsMap = { "PARALLEL": self.parallelCombo.checkItems, "PARALLELVERSES": self.parallelVersesCombo.checkItems, "COMPARE": self.compareCombo.checkItems, "DIFFERENCE": self.differenceCombo.checkItems, } selectedVersions = "_".join(selectedVersionsMap[keyword]) command = "{0}:::{1}:::{2}".format(keyword, selectedVersions, self.getSelectedReference()) self.parent.runTextCommand(command) def editBibleNotes(self, noteType): keywords = { "book": "_editbooknote", "chapter": "_editchapternote", "verse": "_editversenote", } command = "{0}:::{1}.{2}.{3}".format(keywords[noteType], self.b, self.c, self.v) self.parent.runTextCommand(command) def searchBookName(self, dictionary): engFullBookName = BibleBooks().eng[str(self.b)][1] matches = re.match("^[0-9]+? (.*?)$", engFullBookName) if matches: engFullBookName = matches.group(1) command = "SEARCHTOOL:::{0}:::{1}".format( config.dictionary if dictionary else config.encyclopedia, engFullBookName) self.parent.runTextCommand(command) def searchBookChapter(self, resource): engFullBookName = BibleBooks().eng[str(self.b)][1] command = "SEARCHBOOKCHAPTER:::{0}:::{1}".format( resource, engFullBookName) self.parent.runTextCommand(command) def chapterAction(self, keyword): command = "{0}:::{1}".format(keyword, self.getSelectedReferenceChapter()) self.parent.runTextCommand(command) def verseAction(self, keyword): command = "{0}:::{1}".format(keyword, self.getSelectedReference()) self.parent.runTextCommand(command) def selectCollection(self, collection): if collection == "All": self.versionCombo.clear() self.versionCombo.addItems(self.textList) for index, fullName in enumerate(self.parent.textFullNameList): self.versionCombo.setItemData(index, fullName, Qt.ToolTipRole) self.parallelCombo.checkAll() self.parallelVersesCombo.checkAll() self.compareCombo.checkAll() self.differenceCombo.checkAll() elif collection == "None": self.parallelCombo.clearAll() self.parallelVersesCombo.clearAll() self.compareCombo.clearAll() self.differenceCombo.clearAll() else: self.versionCombo.clear() self.versionCombo.addItems(config.bibleCollections[collection]) for i in range(self.versionCombo.model().rowCount()): text = self.versionCombo.model().item(i).text() fullName = Bible(text).bibleInfo() self.versionCombo.setItemData(i, fullName, Qt.ToolTipRole) self.parallelCombo.checkFromList( config.bibleCollections[collection]) self.parallelVersesCombo.checkFromList( config.bibleCollections[collection]) self.compareCombo.checkFromList( config.bibleCollections[collection]) self.differenceCombo.checkFromList( config.bibleCollections[collection])
def __init__(self, parent): super(KiteInstallation, self).__init__(parent) # Left side action_layout = QVBoxLayout() progress_layout = QHBoxLayout() self._progress_widget = QWidget(self) self._progress_widget.setFixedHeight(50) self._progress_filter = HoverEventFilter() self._progress_bar = QProgressBar(self) self._progress_bar.setFixedWidth(180) self._progress_widget.installEventFilter(self._progress_filter) self.cancel_button = QPushButton() self.cancel_button.setIcon(ima.icon('DialogCloseButton')) self.cancel_button.hide() progress_layout.addWidget(self._progress_bar, alignment=Qt.AlignLeft) progress_layout.addWidget(self.cancel_button) self._progress_widget.setLayout(progress_layout) self._progress_label = QLabel(_('Downloading')) install_info = QLabel( _("Kite comes with a native app called the Copilot <br>" "which provides you with real time <br>" "documentation as you code.<br><br>" "When Kite is done installing, the Copilot will <br>" "launch automatically and guide you throught the <br>" "rest of the setup process.")) button_layout = QHBoxLayout() self.ok_button = QPushButton(_('OK')) button_layout.addStretch() button_layout.addWidget(self.ok_button) button_layout.addStretch() action_layout.addStretch() action_layout.addWidget(self._progress_label) action_layout.addWidget(self._progress_widget) action_layout.addWidget(install_info) action_layout.addSpacing(10) action_layout.addLayout(button_layout) action_layout.addStretch() # Right side copilot_image_source = get_image_path('kite_copilot') copilot_image = QPixmap(copilot_image_source) copilot_label = QLabel() screen = QApplication.primaryScreen() device_pixel_ratio = screen.devicePixelRatio() if device_pixel_ratio > 1: copilot_image.setDevicePixelRatio(device_pixel_ratio) copilot_label.setPixmap(copilot_image) else: image_height = int(copilot_image.height() * 0.4) image_width = int(copilot_image.width() * 0.4) copilot_label.setPixmap( copilot_image.scaled(image_width, image_height, Qt.KeepAspectRatio, Qt.SmoothTransformation)) # Layout general_layout = QHBoxLayout() general_layout.addLayout(action_layout) general_layout.addWidget(copilot_label) self.setLayout(general_layout) # Signals self._progress_filter.sig_hover_enter.connect( lambda: self.cancel_button.show()) self._progress_filter.sig_hover_leave.connect( lambda: self.cancel_button.hide())
def initialize(self): self.resize(_main_window_geometry["initial_width"], _main_window_geometry["initial_height"]) self.setMinimumWidth(_main_window_geometry["min_width"]) self.setMinimumHeight(_main_window_geometry["min_height"]) self.setWindowTitle(self.gpc.get_window_title()) self.central_widget = TwoPanelWidget(gpc=self.gpc, gui_vars=self.gui_vars) self.setCentralWidget(self.central_widget) # Status bar self.statusLabel = QLabel() self.statusBar().addWidget(self.statusLabel) self.statusProgressBar = QProgressBar() self.statusProgressBar.setFixedWidth(200) self.statusBar().addPermanentWidget(self.statusProgressBar) self.statusLabelDefaultText = "No data is loaded" self.statusLabel.setText(self.statusLabelDefaultText) # 'Scan Data' menu item self.action_read_file = QAction("&Read File...", self) self.action_read_file.setStatusTip("Load data from HDF5 file") self.action_read_file.triggered.connect(self.central_widget.left_panel.load_data_widget.pb_file.clicked) self.action_load_run = QAction("&Load Run...", self) self.action_load_run.setEnabled(self.gui_vars["gui_state"]["databroker_available"]) self.action_load_run.setStatusTip("Load data from database (Databroker)") self.action_load_run.triggered.connect(self.central_widget.left_panel.load_data_widget.pb_dbase.clicked) self.action_view_metadata = QAction("View Metadata...", self) self.action_view_metadata.setEnabled(self.gpc.is_scan_metadata_available()) self.action_view_metadata.setStatusTip("View metadata for loaded run") self.action_view_metadata.triggered.connect( self.central_widget.left_panel.load_data_widget.pb_view_metadata.clicked ) # Main menu menubar = self.menuBar() # Disable native menu bar (it doesn't work on MacOS 10.15 with PyQt<=5.11) # It may work with later versions of PyQt when they become available. menubar.setNativeMenuBar(False) loadData = menubar.addMenu("Scan &Data") loadData.addAction(self.action_read_file) loadData.addAction(self.action_load_run) loadData.addSeparator() loadData.addAction(self.action_view_metadata) # 'Fitting Model' menu item self.action_lines_find_automatically = QAction("Find &Automatically...", self) self.action_lines_find_automatically.setStatusTip("Automatically find emission lines in total spectrum") self.action_lines_find_automatically.triggered.connect( self.central_widget.left_panel.model_widget.pb_find_elines.clicked ) self.action_lines_load_from_file = QAction("Load From &File...", self) self.action_lines_load_from_file.setStatusTip( "Load processing parameters, including selected emission lines, from JSON file" ) self.action_lines_load_from_file.triggered.connect( self.central_widget.left_panel.model_widget.pb_load_elines.clicked ) self.action_lines_load_quant_standard = QAction("Load &Quantitative Standards...", self) self.action_lines_load_quant_standard.setStatusTip( "Load quantitative standard. The emission lines from the standard are automatically selected" ) self.action_lines_load_quant_standard.triggered.connect( self.central_widget.left_panel.model_widget.pb_load_qstandard.clicked ) self.action_add_remove_emission_lines = QAction("&Add/Remove Emission Lines...", self) self.action_add_remove_emission_lines.setStatusTip("Manually add and remove emission lines") self.action_add_remove_emission_lines.triggered.connect( self.central_widget.left_panel.model_widget.pb_manage_emission_lines.clicked ) self.action_save_model_params = QAction("&Save Model Parameters...", self) self.action_save_model_params.setStatusTip("Save model parameters to JSON file") self.action_save_model_params.triggered.connect( self.central_widget.left_panel.model_widget.pb_save_elines.clicked ) self.action_add_remove_emission_lines = QAction("Start Model &Fitting", self) self.action_add_remove_emission_lines.setStatusTip("Run computations: start fitting for total spectrum") self.action_add_remove_emission_lines.triggered.connect( self.central_widget.left_panel.model_widget.pb_start_fitting.clicked ) fittingModel = menubar.addMenu("Fitting &Model") emissionLines = fittingModel.addMenu("&Emission Lines") emissionLines.addAction(self.action_lines_find_automatically) emissionLines.addAction(self.action_lines_load_from_file) emissionLines.addAction(self.action_lines_load_quant_standard) fittingModel.addAction(self.action_add_remove_emission_lines) fittingModel.addSeparator() fittingModel.addAction(self.action_save_model_params) fittingModel.addSeparator() fittingModel.addAction(self.action_add_remove_emission_lines) # "XRF Maps" menu item self.action_start_xrf_map_fitting = QAction("Start XRF Map &Fitting", self) self.action_start_xrf_map_fitting.setStatusTip("Run computations: start fitting for XRF maps") self.action_start_xrf_map_fitting.triggered.connect( self.central_widget.left_panel.fit_maps_widget.pb_start_map_fitting.clicked ) self.action_compute_rois = QAction("Compute &ROIs...", self) self.action_compute_rois.setStatusTip("Compute XRF Maps based on spectral ROIs") self.action_compute_rois.triggered.connect( self.central_widget.left_panel.fit_maps_widget.pb_compute_roi_maps.clicked ) self.action_load_quant_calibration = QAction("&Load Quantitative Calibration...", self) self.action_load_quant_calibration.setStatusTip( "Load quantitative calibration from JSON file. Calibration is used for scaling of XRF Maps" ) self.action_load_quant_calibration.triggered.connect( self.central_widget.left_panel.fit_maps_widget.pb_load_quant_calib.clicked ) self.action_save_quant_calibration = QAction("&Save Quantitative Calibration...", self) self.action_save_quant_calibration.setStatusTip( "Save Quantitative Calibration based on XRF map of the standard sample" ) self.action_save_quant_calibration.triggered.connect( self.central_widget.left_panel.fit_maps_widget.pb_save_q_calibration.clicked ) self.action_export_to_tiff_and_txt = QAction("&Export to TIFF and TXT...", self) self.action_export_to_tiff_and_txt.setStatusTip("Export XRF Maps as TIFF and/or TXT files") self.action_export_to_tiff_and_txt.triggered.connect( self.central_widget.left_panel.fit_maps_widget.pb_export_to_tiff_and_txt.clicked ) xrfMaps = menubar.addMenu("XRF &Maps") xrfMaps.addAction(self.action_start_xrf_map_fitting) xrfMaps.addAction(self.action_compute_rois) xrfMaps.addSeparator() xrfMaps.addAction(self.action_load_quant_calibration) xrfMaps.addSeparator() xrfMaps.addAction(self.action_save_quant_calibration) xrfMaps.addAction(self.action_export_to_tiff_and_txt) # "View" menu item self.action_show_matplotlib_toolbar = QAction("Show &Matplotlib Toolbar", self) self.action_show_matplotlib_toolbar.setCheckable(True) self.action_show_matplotlib_toolbar.setChecked(True) self.action_show_matplotlib_toolbar.setStatusTip("Show Matplotlib Toolbar on the plots") self.action_show_matplotlib_toolbar.toggled.connect(self.action_show_matplotlib_toolbar_toggled) self.action_show_widget_tooltips = QAction("Show &Tooltips", self) self.action_show_widget_tooltips.setCheckable(True) self.action_show_widget_tooltips.setChecked(self.gui_vars["show_tooltip"]) self.action_show_widget_tooltips.setStatusTip("Show widget tooltips") self.action_show_widget_tooltips.toggled.connect(self.action_show_widget_tooltips_toggled) options = menubar.addMenu("&Options") options.addAction(self.action_show_widget_tooltips) options.addAction(self.action_show_matplotlib_toolbar) # "Help" menu item self.action_online_docs = QAction("Online &Documentation", self) self.action_online_docs.setStatusTip("Open online documentation in the default browser") self.action_online_docs.triggered.connect(self.action_online_docs_triggered) self.action_about = QAction("&About PyXRF", self) self.action_about.setStatusTip("Show information about this program") self.action_about.triggered.connect(self.action_about_triggered) help = menubar.addMenu("&Help") help.addAction(self.action_online_docs) help.addSeparator() help.addAction(self.action_about) self.update_widget_state() # Connect signals self.central_widget.left_panel.load_data_widget.update_preview_map_range.connect( self.central_widget.right_panel.tab_preview_plots.preview_plot_count.update_map_range ) # Before loading a new file or run self.central_widget.left_panel.load_data_widget.signal_loading_new_run.connect( self.central_widget.right_panel.slot_activate_tab_preview ) # Open a new file or run self.central_widget.left_panel.load_data_widget.signal_new_run_loaded.connect( self.central_widget.left_panel.slot_activate_load_data_tab ) self.central_widget.left_panel.load_data_widget.signal_new_run_loaded.connect( self.central_widget.right_panel.slot_activate_tab_preview ) self.central_widget.left_panel.load_data_widget.signal_new_run_loaded.connect(self.slot_new_run_loaded) self.central_widget.left_panel.load_data_widget.signal_new_run_loaded.connect( self.wnd_image_wizard.slot_update_table ) self.central_widget.left_panel.load_data_widget.signal_new_run_loaded.connect( self.central_widget.right_panel.tab_plot_xrf_maps.slot_update_dataset_info ) self.central_widget.left_panel.load_data_widget.signal_new_run_loaded.connect( self.central_widget.right_panel.tab_plot_rgb_maps.slot_update_dataset_info ) self.central_widget.left_panel.load_data_widget.signal_new_run_loaded.connect( self.wnd_load_quantitative_calibration.update_all_data ) self.central_widget.left_panel.load_data_widget.signal_new_run_loaded.connect( self.central_widget.left_panel.fit_maps_widget.slot_update_for_new_loaded_run ) # New model is loaded or processing parameters (incident energy) was changed self.central_widget.left_panel.model_widget.signal_incident_energy_or_range_changed.connect( self.central_widget.right_panel.tab_preview_plots.preview_plot_spectrum.redraw_preview_plot ) self.central_widget.left_panel.model_widget.signal_incident_energy_or_range_changed.connect( self.wnd_manage_emission_lines.update_widget_data ) self.central_widget.left_panel.model_widget.signal_incident_energy_or_range_changed.connect( self.central_widget.right_panel.tab_plot_fitting_model.redraw_plot_fit ) self.central_widget.left_panel.model_widget.signal_model_loaded.connect( self.central_widget.right_panel.tab_preview_plots.preview_plot_spectrum.redraw_preview_plot ) self.central_widget.left_panel.model_widget.signal_model_loaded.connect( self.central_widget.right_panel.slot_activate_tab_fitting_model ) self.central_widget.left_panel.model_widget.signal_model_loaded.connect( self.central_widget.right_panel.tab_plot_fitting_model.update_controls ) self.central_widget.left_panel.model_widget.signal_model_loaded.connect( self.wnd_manage_emission_lines.update_widget_data ) # XRF Maps dataset changed self.central_widget.right_panel.tab_plot_xrf_maps.signal_maps_dataset_selection_changed.connect( self.wnd_image_wizard.slot_update_table ) self.central_widget.right_panel.tab_plot_xrf_maps.signal_maps_dataset_selection_changed.connect( self.central_widget.right_panel.tab_plot_rgb_maps.combo_select_dataset_update_current_index ) self.central_widget.right_panel.tab_plot_rgb_maps.signal_rgb_maps_dataset_selection_changed.connect( self.central_widget.right_panel.tab_plot_xrf_maps.combo_select_dataset_update_current_index ) self.central_widget.right_panel.tab_plot_xrf_maps.signal_maps_norm_changed.connect( self.wnd_image_wizard.slot_update_ranges ) # Quantitative calibration changed self.wnd_load_quantitative_calibration.signal_quantitative_calibration_changed.connect( self.central_widget.right_panel.tab_plot_rgb_maps.slot_update_ranges ) self.wnd_load_quantitative_calibration.signal_quantitative_calibration_changed.connect( self.wnd_image_wizard.slot_update_ranges ) # Selected element is changed (tools for emission line selection) self.wnd_manage_emission_lines.signal_selected_element_changed.connect( self.central_widget.right_panel.tab_plot_fitting_model.slot_selection_item_changed ) self.central_widget.right_panel.tab_plot_fitting_model.signal_selected_element_changed.connect( self.wnd_manage_emission_lines.slot_selection_item_changed ) self.central_widget.right_panel.tab_plot_fitting_model.signal_add_line.connect( self.wnd_manage_emission_lines.pb_add_eline_clicked ) self.central_widget.right_panel.tab_plot_fitting_model.signal_remove_line.connect( self.wnd_manage_emission_lines.pb_remove_eline_clicked ) self.wnd_manage_emission_lines.signal_update_element_selection_list.connect( self.central_widget.right_panel.tab_plot_fitting_model.slot_update_eline_selection_list ) self.wnd_manage_emission_lines.signal_update_add_remove_btn_state.connect( self.central_widget.right_panel.tab_plot_fitting_model.slot_update_add_remove_btn_state ) self.wnd_manage_emission_lines.signal_selected_element_changed.connect( self.central_widget.left_panel.model_widget.slot_selection_item_changed ) self.central_widget.right_panel.tab_plot_fitting_model.signal_selected_element_changed.connect( self.central_widget.left_panel.model_widget.slot_selection_item_changed ) # Total spectrum fitting completed self.central_widget.left_panel.model_widget.signal_total_spectrum_fitting_completed.connect( self.wnd_manage_emission_lines.update_eline_table ) # Total spectrum invalidated self.wnd_manage_emission_lines.signal_parameters_changed.connect( self.central_widget.left_panel.model_widget.update_fit_status ) # New dataset loaded or different channel selected. Compute fit parameters. self.central_widget.left_panel.load_data_widget.signal_data_channel_changed.connect( self.central_widget.left_panel.model_widget.clear_fit_status ) self.central_widget.left_panel.load_data_widget.signal_new_run_loaded.connect( self.central_widget.left_panel.model_widget.clear_fit_status ) self.central_widget.left_panel.model_widget.signal_incident_energy_or_range_changed.connect( self.central_widget.left_panel.model_widget.clear_fit_status ) # Update map datasets (Fitted maps) self.central_widget.left_panel.fit_maps_widget.signal_map_fitting_complete.connect( self.central_widget.right_panel.tab_plot_xrf_maps.slot_update_dataset_info ) self.central_widget.left_panel.fit_maps_widget.signal_map_fitting_complete.connect( self.central_widget.right_panel.tab_plot_rgb_maps.slot_update_dataset_info ) self.central_widget.left_panel.fit_maps_widget.signal_activate_tab_xrf_maps.connect( self.central_widget.right_panel.slot_activate_tab_xrf_maps ) # Update map datasets (ROI maps) self.wnd_compute_roi_maps.signal_roi_computation_complete.connect( self.central_widget.right_panel.tab_plot_xrf_maps.slot_update_dataset_info ) self.wnd_compute_roi_maps.signal_roi_computation_complete.connect( self.central_widget.right_panel.tab_plot_rgb_maps.slot_update_dataset_info ) self.wnd_compute_roi_maps.signal_activate_tab_xrf_maps.connect( self.central_widget.right_panel.slot_activate_tab_xrf_maps ) self.signal_fitting_parameters_changed.connect(self.wnd_general_fitting_settings.update_form_data) self.signal_fitting_parameters_changed.connect(self.wnd_fitting_parameters_shared.update_form_data) self.signal_fitting_parameters_changed.connect(self.wnd_fitting_parameters_lines.update_form_data) self.signal_fitting_parameters_changed.connect(self.wnd_manage_emission_lines.update_widget_data)
def _setupUi(self): # status label_status = QLabel('Status: ', self, alignment=Qt.AlignRight | Qt.AlignVCenter) channels2values = { self.device.substitute(propty='ForceComplete-Mon'): 1, self.device.substitute(propty='NegativeDoneMov-Mon'): 1, self.device.substitute(propty='PositiveDoneMov-Mon'): 1 } self.multiled_status = PyDMLedMultiChannel(self, channels2values) self.multiled_status.setStyleSheet('max-width: 1.29em;') self.pb_details = QPushButton(qta.icon('fa5s.ellipsis-h'), '', self) self.pb_details.setToolTip('Open details') self.pb_details.setObjectName('detail') self.pb_details.setStyleSheet( "#detail{min-width:25px; max-width:25px; icon-size:20px;}") util.connect_window(self.pb_details, _DiffCtrlDetails, parent=self, prefix=self.prefix, device=self.device) self.lb_descCtrl1 = QLabel('', self, alignment=Qt.AlignRight | Qt.AlignVCenter) self.sb_Ctrl1 = PyDMSpinbox(self) self.sb_Ctrl1.showStepExponent = False self.lb_Ctrl1 = PyDMLabel(self) self.lb_descCtrl2 = QLabel('', self, alignment=Qt.AlignRight | Qt.AlignVCenter) self.sb_Ctrl2 = PyDMSpinbox(self) self.sb_Ctrl2.showStepExponent = False self.lb_Ctrl2 = PyDMLabel(self) self.pb_open = PyDMPushButton( parent=self, label='Open', pressValue=1, init_channel=self.device.substitute(propty='Home-Cmd')) tmp_file = _substitute_in_file( _os.path.abspath(_os.path.dirname(__file__)) + '/ui_as_ap_dev' + self.orientation.lower() + 'mon.ui', {'PREFIX': self.prefix}) self.dev_widget = loadUi(tmp_file) self.dev_widget.setObjectName('dev') self.dev_widget_scrarea = QScrollArea() self.dev_widget_scrarea.setObjectName('scrarea') self.dev_widget_scrarea.setStyleSheet( '#scrarea{background-color: transparent; max-width: 15em;}' '#dev{background-color:transparent;}') self.dev_widget_scrarea.setWidget(self.dev_widget) lay = QGridLayout(self) lay.setAlignment(Qt.AlignTop) lay.addWidget(label_status, 0, 0) lay.addWidget(self.multiled_status, 0, 1) lay.addWidget(self.pb_details, 0, 2, alignment=Qt.AlignRight) lay.addWidget(self.lb_descCtrl1, 1, 0) lay.addWidget(self.sb_Ctrl1, 1, 1) lay.addWidget(self.lb_Ctrl1, 1, 2) lay.addWidget(self.lb_descCtrl2, 2, 0) lay.addWidget(self.sb_Ctrl2, 2, 1) lay.addWidget(self.lb_Ctrl2, 2, 2) lay.addWidget(self.pb_open, 3, 1, 1, 2) lay.addWidget(self.dev_widget_scrarea, 0, 3, 4, 1)
class MainWindow(QMainWindow): signal_fitting_parameters_changed = Signal() def __init__(self, *, gpc): """ Parameters ---------- gpc: object reference to a class that holds references to processing classes. """ super().__init__() self._cursor_set = False # Indicates if temporary 'wait' cursor is set self.gpc = gpc self.gui_vars = global_gui_variables self.gui_vars["ref_main_window"] = self self.wnd_manage_emission_lines = WndManageEmissionLines(gpc=self.gpc, gui_vars=self.gui_vars) self.wnd_compute_roi_maps = WndComputeRoiMaps(gpc=self.gpc, gui_vars=self.gui_vars) self.wnd_image_wizard = WndImageWizard(gpc=self.gpc, gui_vars=self.gui_vars) self.wnd_load_quantitative_calibration = WndLoadQuantitativeCalibration( gpc=self.gpc, gui_vars=self.gui_vars ) self.wnd_general_fitting_settings = WndGeneralFittingSettings(gpc=self.gpc, gui_vars=self.gui_vars) self.wnd_fitting_parameters_shared = WndDetailedFittingParamsShared(gpc=self.gpc, gui_vars=self.gui_vars) self.wnd_fitting_parameters_lines = WndDetailedFittingParamsLines(gpc=self.gpc, gui_vars=self.gui_vars) # Indicates that the window was closed (used mostly for testing) self._is_closed = False global_gui_variables["gui_state"]["databroker_available"] = self.gpc.is_databroker_available() self.initialize() self.central_widget.left_panel.load_data_widget.update_main_window_title.connect(self.update_window_title) # Set the callback for update forms with fitting parameters def update_fitting_parameter_forms(): self.signal_fitting_parameters_changed.emit() gpc.add_parameters_changed_cb(update_fitting_parameter_forms) gpc.param_model.parameters_changed() def initialize(self): self.resize(_main_window_geometry["initial_width"], _main_window_geometry["initial_height"]) self.setMinimumWidth(_main_window_geometry["min_width"]) self.setMinimumHeight(_main_window_geometry["min_height"]) self.setWindowTitle(self.gpc.get_window_title()) self.central_widget = TwoPanelWidget(gpc=self.gpc, gui_vars=self.gui_vars) self.setCentralWidget(self.central_widget) # Status bar self.statusLabel = QLabel() self.statusBar().addWidget(self.statusLabel) self.statusProgressBar = QProgressBar() self.statusProgressBar.setFixedWidth(200) self.statusBar().addPermanentWidget(self.statusProgressBar) self.statusLabelDefaultText = "No data is loaded" self.statusLabel.setText(self.statusLabelDefaultText) # 'Scan Data' menu item self.action_read_file = QAction("&Read File...", self) self.action_read_file.setStatusTip("Load data from HDF5 file") self.action_read_file.triggered.connect(self.central_widget.left_panel.load_data_widget.pb_file.clicked) self.action_load_run = QAction("&Load Run...", self) self.action_load_run.setEnabled(self.gui_vars["gui_state"]["databroker_available"]) self.action_load_run.setStatusTip("Load data from database (Databroker)") self.action_load_run.triggered.connect(self.central_widget.left_panel.load_data_widget.pb_dbase.clicked) self.action_view_metadata = QAction("View Metadata...", self) self.action_view_metadata.setEnabled(self.gpc.is_scan_metadata_available()) self.action_view_metadata.setStatusTip("View metadata for loaded run") self.action_view_metadata.triggered.connect( self.central_widget.left_panel.load_data_widget.pb_view_metadata.clicked ) # Main menu menubar = self.menuBar() # Disable native menu bar (it doesn't work on MacOS 10.15 with PyQt<=5.11) # It may work with later versions of PyQt when they become available. menubar.setNativeMenuBar(False) loadData = menubar.addMenu("Scan &Data") loadData.addAction(self.action_read_file) loadData.addAction(self.action_load_run) loadData.addSeparator() loadData.addAction(self.action_view_metadata) # 'Fitting Model' menu item self.action_lines_find_automatically = QAction("Find &Automatically...", self) self.action_lines_find_automatically.setStatusTip("Automatically find emission lines in total spectrum") self.action_lines_find_automatically.triggered.connect( self.central_widget.left_panel.model_widget.pb_find_elines.clicked ) self.action_lines_load_from_file = QAction("Load From &File...", self) self.action_lines_load_from_file.setStatusTip( "Load processing parameters, including selected emission lines, from JSON file" ) self.action_lines_load_from_file.triggered.connect( self.central_widget.left_panel.model_widget.pb_load_elines.clicked ) self.action_lines_load_quant_standard = QAction("Load &Quantitative Standards...", self) self.action_lines_load_quant_standard.setStatusTip( "Load quantitative standard. The emission lines from the standard are automatically selected" ) self.action_lines_load_quant_standard.triggered.connect( self.central_widget.left_panel.model_widget.pb_load_qstandard.clicked ) self.action_add_remove_emission_lines = QAction("&Add/Remove Emission Lines...", self) self.action_add_remove_emission_lines.setStatusTip("Manually add and remove emission lines") self.action_add_remove_emission_lines.triggered.connect( self.central_widget.left_panel.model_widget.pb_manage_emission_lines.clicked ) self.action_save_model_params = QAction("&Save Model Parameters...", self) self.action_save_model_params.setStatusTip("Save model parameters to JSON file") self.action_save_model_params.triggered.connect( self.central_widget.left_panel.model_widget.pb_save_elines.clicked ) self.action_add_remove_emission_lines = QAction("Start Model &Fitting", self) self.action_add_remove_emission_lines.setStatusTip("Run computations: start fitting for total spectrum") self.action_add_remove_emission_lines.triggered.connect( self.central_widget.left_panel.model_widget.pb_start_fitting.clicked ) fittingModel = menubar.addMenu("Fitting &Model") emissionLines = fittingModel.addMenu("&Emission Lines") emissionLines.addAction(self.action_lines_find_automatically) emissionLines.addAction(self.action_lines_load_from_file) emissionLines.addAction(self.action_lines_load_quant_standard) fittingModel.addAction(self.action_add_remove_emission_lines) fittingModel.addSeparator() fittingModel.addAction(self.action_save_model_params) fittingModel.addSeparator() fittingModel.addAction(self.action_add_remove_emission_lines) # "XRF Maps" menu item self.action_start_xrf_map_fitting = QAction("Start XRF Map &Fitting", self) self.action_start_xrf_map_fitting.setStatusTip("Run computations: start fitting for XRF maps") self.action_start_xrf_map_fitting.triggered.connect( self.central_widget.left_panel.fit_maps_widget.pb_start_map_fitting.clicked ) self.action_compute_rois = QAction("Compute &ROIs...", self) self.action_compute_rois.setStatusTip("Compute XRF Maps based on spectral ROIs") self.action_compute_rois.triggered.connect( self.central_widget.left_panel.fit_maps_widget.pb_compute_roi_maps.clicked ) self.action_load_quant_calibration = QAction("&Load Quantitative Calibration...", self) self.action_load_quant_calibration.setStatusTip( "Load quantitative calibration from JSON file. Calibration is used for scaling of XRF Maps" ) self.action_load_quant_calibration.triggered.connect( self.central_widget.left_panel.fit_maps_widget.pb_load_quant_calib.clicked ) self.action_save_quant_calibration = QAction("&Save Quantitative Calibration...", self) self.action_save_quant_calibration.setStatusTip( "Save Quantitative Calibration based on XRF map of the standard sample" ) self.action_save_quant_calibration.triggered.connect( self.central_widget.left_panel.fit_maps_widget.pb_save_q_calibration.clicked ) self.action_export_to_tiff_and_txt = QAction("&Export to TIFF and TXT...", self) self.action_export_to_tiff_and_txt.setStatusTip("Export XRF Maps as TIFF and/or TXT files") self.action_export_to_tiff_and_txt.triggered.connect( self.central_widget.left_panel.fit_maps_widget.pb_export_to_tiff_and_txt.clicked ) xrfMaps = menubar.addMenu("XRF &Maps") xrfMaps.addAction(self.action_start_xrf_map_fitting) xrfMaps.addAction(self.action_compute_rois) xrfMaps.addSeparator() xrfMaps.addAction(self.action_load_quant_calibration) xrfMaps.addSeparator() xrfMaps.addAction(self.action_save_quant_calibration) xrfMaps.addAction(self.action_export_to_tiff_and_txt) # "View" menu item self.action_show_matplotlib_toolbar = QAction("Show &Matplotlib Toolbar", self) self.action_show_matplotlib_toolbar.setCheckable(True) self.action_show_matplotlib_toolbar.setChecked(True) self.action_show_matplotlib_toolbar.setStatusTip("Show Matplotlib Toolbar on the plots") self.action_show_matplotlib_toolbar.toggled.connect(self.action_show_matplotlib_toolbar_toggled) self.action_show_widget_tooltips = QAction("Show &Tooltips", self) self.action_show_widget_tooltips.setCheckable(True) self.action_show_widget_tooltips.setChecked(self.gui_vars["show_tooltip"]) self.action_show_widget_tooltips.setStatusTip("Show widget tooltips") self.action_show_widget_tooltips.toggled.connect(self.action_show_widget_tooltips_toggled) options = menubar.addMenu("&Options") options.addAction(self.action_show_widget_tooltips) options.addAction(self.action_show_matplotlib_toolbar) # "Help" menu item self.action_online_docs = QAction("Online &Documentation", self) self.action_online_docs.setStatusTip("Open online documentation in the default browser") self.action_online_docs.triggered.connect(self.action_online_docs_triggered) self.action_about = QAction("&About PyXRF", self) self.action_about.setStatusTip("Show information about this program") self.action_about.triggered.connect(self.action_about_triggered) help = menubar.addMenu("&Help") help.addAction(self.action_online_docs) help.addSeparator() help.addAction(self.action_about) self.update_widget_state() # Connect signals self.central_widget.left_panel.load_data_widget.update_preview_map_range.connect( self.central_widget.right_panel.tab_preview_plots.preview_plot_count.update_map_range ) # Before loading a new file or run self.central_widget.left_panel.load_data_widget.signal_loading_new_run.connect( self.central_widget.right_panel.slot_activate_tab_preview ) # Open a new file or run self.central_widget.left_panel.load_data_widget.signal_new_run_loaded.connect( self.central_widget.left_panel.slot_activate_load_data_tab ) self.central_widget.left_panel.load_data_widget.signal_new_run_loaded.connect( self.central_widget.right_panel.slot_activate_tab_preview ) self.central_widget.left_panel.load_data_widget.signal_new_run_loaded.connect(self.slot_new_run_loaded) self.central_widget.left_panel.load_data_widget.signal_new_run_loaded.connect( self.wnd_image_wizard.slot_update_table ) self.central_widget.left_panel.load_data_widget.signal_new_run_loaded.connect( self.central_widget.right_panel.tab_plot_xrf_maps.slot_update_dataset_info ) self.central_widget.left_panel.load_data_widget.signal_new_run_loaded.connect( self.central_widget.right_panel.tab_plot_rgb_maps.slot_update_dataset_info ) self.central_widget.left_panel.load_data_widget.signal_new_run_loaded.connect( self.wnd_load_quantitative_calibration.update_all_data ) self.central_widget.left_panel.load_data_widget.signal_new_run_loaded.connect( self.central_widget.left_panel.fit_maps_widget.slot_update_for_new_loaded_run ) # New model is loaded or processing parameters (incident energy) was changed self.central_widget.left_panel.model_widget.signal_incident_energy_or_range_changed.connect( self.central_widget.right_panel.tab_preview_plots.preview_plot_spectrum.redraw_preview_plot ) self.central_widget.left_panel.model_widget.signal_incident_energy_or_range_changed.connect( self.wnd_manage_emission_lines.update_widget_data ) self.central_widget.left_panel.model_widget.signal_incident_energy_or_range_changed.connect( self.central_widget.right_panel.tab_plot_fitting_model.redraw_plot_fit ) self.central_widget.left_panel.model_widget.signal_model_loaded.connect( self.central_widget.right_panel.tab_preview_plots.preview_plot_spectrum.redraw_preview_plot ) self.central_widget.left_panel.model_widget.signal_model_loaded.connect( self.central_widget.right_panel.slot_activate_tab_fitting_model ) self.central_widget.left_panel.model_widget.signal_model_loaded.connect( self.central_widget.right_panel.tab_plot_fitting_model.update_controls ) self.central_widget.left_panel.model_widget.signal_model_loaded.connect( self.wnd_manage_emission_lines.update_widget_data ) # XRF Maps dataset changed self.central_widget.right_panel.tab_plot_xrf_maps.signal_maps_dataset_selection_changed.connect( self.wnd_image_wizard.slot_update_table ) self.central_widget.right_panel.tab_plot_xrf_maps.signal_maps_dataset_selection_changed.connect( self.central_widget.right_panel.tab_plot_rgb_maps.combo_select_dataset_update_current_index ) self.central_widget.right_panel.tab_plot_rgb_maps.signal_rgb_maps_dataset_selection_changed.connect( self.central_widget.right_panel.tab_plot_xrf_maps.combo_select_dataset_update_current_index ) self.central_widget.right_panel.tab_plot_xrf_maps.signal_maps_norm_changed.connect( self.wnd_image_wizard.slot_update_ranges ) # Quantitative calibration changed self.wnd_load_quantitative_calibration.signal_quantitative_calibration_changed.connect( self.central_widget.right_panel.tab_plot_rgb_maps.slot_update_ranges ) self.wnd_load_quantitative_calibration.signal_quantitative_calibration_changed.connect( self.wnd_image_wizard.slot_update_ranges ) # Selected element is changed (tools for emission line selection) self.wnd_manage_emission_lines.signal_selected_element_changed.connect( self.central_widget.right_panel.tab_plot_fitting_model.slot_selection_item_changed ) self.central_widget.right_panel.tab_plot_fitting_model.signal_selected_element_changed.connect( self.wnd_manage_emission_lines.slot_selection_item_changed ) self.central_widget.right_panel.tab_plot_fitting_model.signal_add_line.connect( self.wnd_manage_emission_lines.pb_add_eline_clicked ) self.central_widget.right_panel.tab_plot_fitting_model.signal_remove_line.connect( self.wnd_manage_emission_lines.pb_remove_eline_clicked ) self.wnd_manage_emission_lines.signal_update_element_selection_list.connect( self.central_widget.right_panel.tab_plot_fitting_model.slot_update_eline_selection_list ) self.wnd_manage_emission_lines.signal_update_add_remove_btn_state.connect( self.central_widget.right_panel.tab_plot_fitting_model.slot_update_add_remove_btn_state ) self.wnd_manage_emission_lines.signal_selected_element_changed.connect( self.central_widget.left_panel.model_widget.slot_selection_item_changed ) self.central_widget.right_panel.tab_plot_fitting_model.signal_selected_element_changed.connect( self.central_widget.left_panel.model_widget.slot_selection_item_changed ) # Total spectrum fitting completed self.central_widget.left_panel.model_widget.signal_total_spectrum_fitting_completed.connect( self.wnd_manage_emission_lines.update_eline_table ) # Total spectrum invalidated self.wnd_manage_emission_lines.signal_parameters_changed.connect( self.central_widget.left_panel.model_widget.update_fit_status ) # New dataset loaded or different channel selected. Compute fit parameters. self.central_widget.left_panel.load_data_widget.signal_data_channel_changed.connect( self.central_widget.left_panel.model_widget.clear_fit_status ) self.central_widget.left_panel.load_data_widget.signal_new_run_loaded.connect( self.central_widget.left_panel.model_widget.clear_fit_status ) self.central_widget.left_panel.model_widget.signal_incident_energy_or_range_changed.connect( self.central_widget.left_panel.model_widget.clear_fit_status ) # Update map datasets (Fitted maps) self.central_widget.left_panel.fit_maps_widget.signal_map_fitting_complete.connect( self.central_widget.right_panel.tab_plot_xrf_maps.slot_update_dataset_info ) self.central_widget.left_panel.fit_maps_widget.signal_map_fitting_complete.connect( self.central_widget.right_panel.tab_plot_rgb_maps.slot_update_dataset_info ) self.central_widget.left_panel.fit_maps_widget.signal_activate_tab_xrf_maps.connect( self.central_widget.right_panel.slot_activate_tab_xrf_maps ) # Update map datasets (ROI maps) self.wnd_compute_roi_maps.signal_roi_computation_complete.connect( self.central_widget.right_panel.tab_plot_xrf_maps.slot_update_dataset_info ) self.wnd_compute_roi_maps.signal_roi_computation_complete.connect( self.central_widget.right_panel.tab_plot_rgb_maps.slot_update_dataset_info ) self.wnd_compute_roi_maps.signal_activate_tab_xrf_maps.connect( self.central_widget.right_panel.slot_activate_tab_xrf_maps ) self.signal_fitting_parameters_changed.connect(self.wnd_general_fitting_settings.update_form_data) self.signal_fitting_parameters_changed.connect(self.wnd_fitting_parameters_shared.update_form_data) self.signal_fitting_parameters_changed.connect(self.wnd_fitting_parameters_lines.update_form_data) self.signal_fitting_parameters_changed.connect(self.wnd_manage_emission_lines.update_widget_data) @Slot() @Slot(str) def update_widget_state(self, condition=None): # Update the state of the menu bar state = not self.gui_vars["gui_state"]["running_computations"] self.menuBar().setEnabled(state) state_computations = self.gui_vars["gui_state"]["running_computations"] if state_computations: if not self._cursor_set: QGuiApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) self._cursor_set = True else: if self._cursor_set: QGuiApplication.restoreOverrideCursor() self._cursor_set = False # Forward to children self.central_widget.update_widget_state(condition) # Forward the updates to open windows self.wnd_manage_emission_lines.update_widget_state(condition) self.wnd_compute_roi_maps.update_widget_state(condition) self.wnd_image_wizard.update_widget_state(condition) self.wnd_load_quantitative_calibration.update_widget_state(condition) self.wnd_general_fitting_settings.update_widget_state(condition) self.wnd_fitting_parameters_shared.update_widget_state(condition) self.wnd_fitting_parameters_lines.update_widget_state(condition) def closeEvent(self, event): mb_close = QMessageBox( QMessageBox.Question, "Exit", "Are you sure you want to EXIT the program?", QMessageBox.Yes | QMessageBox.No, parent=self, ) mb_close.setDefaultButton(QMessageBox.No) if mb_close.exec() == QMessageBox.Yes: event.accept() self.wnd_manage_emission_lines.close() self.wnd_compute_roi_maps.close() self.wnd_image_wizard.close() self.wnd_load_quantitative_calibration.close() self.wnd_general_fitting_settings.close() self.wnd_fitting_parameters_shared.close() self.wnd_fitting_parameters_lines.close() # This flag is used for CI tests self._is_closed = True else: event.ignore() def action_online_docs_triggered(self): """ Display online documentation: open the URL in the default browser. """ doc_url = "http://nsls-ii.github.io/PyXRF/" try: webbrowser.open(doc_url, autoraise=True) except Exception as ex: logger.error(f"Error occurred while opening URL '{doc_url}' in the default browser") msg = f"Failed to Open Online Documentation. \n Exception: {str(ex)}" msgbox = QMessageBox(QMessageBox.Critical, "Error", msg, QMessageBox.Ok, parent=self) msgbox.exec() def action_about_triggered(self): """ Display 'About' dialog box """ dlg = DialogAbout() dlg.exec() def action_show_matplotlib_toolbar_toggled(self, state): """ Turn tooltips on or off """ self.gui_vars["show_matplotlib_toolbar"] = state self.update_widget_state() def action_show_widget_tooltips_toggled(self, state): """ Turn tooltips on or off """ self.gui_vars["show_tooltip"] = state self.update_widget_state("tooltips") @Slot() def update_window_title(self): self.setWindowTitle(self.gpc.get_window_title()) @Slot(bool) def slot_new_run_loaded(self, success): if success: # Update status bar file_name = self.gpc.get_loaded_file_name() run_id, run_uid = "", "" if self.gpc.is_scan_metadata_available(): try: run_id = self.gpc.get_metadata_scan_id() except Exception: pass try: run_uid = self.gpc.get_metadata_scan_uid() except Exception: pass else: if self.gpc.get_current_run_id() >= 0: run_id = self.gpc.get_current_run_id() s = "" if run_id: s += f"ID: {run_id} " if run_uid: if run_id: s += f"({run_uid}) " else: s += f"UID: {run_uid} " if file_name: s += f"File: '{file_name}'" self._set_status_bar_text(s) # Activate/deactivate "View Metadata ..." menu item if self.gpc.is_scan_metadata_available(): self.action_view_metadata.setEnabled(True) else: self.action_view_metadata.setEnabled(False) else: self._set_status_bar_text() def _set_status_bar_text(self, text=None): if text is None: text = self.statusLabelDefaultText self.statusLabel.setText(text)
class FindInFilesWidget(PluginMainWidget): """ Find in files main widget. """ ENABLE_SPINNER = True REGEX_INVALID = f"background-color:{SpyderPalette.COLOR_ERROR_2};" REGEX_ERROR = _("Regular expression error") # Signals sig_edit_goto_requested = Signal(str, int, str, int, int) """ This signal will request to open a file in a given row and column using a code editor. Parameters ---------- path: str Path to file. row: int Cursor starting row position. word: str Word to select on given row. start_column: int Starting column of found word. end_column: Ending column of found word. """ sig_finished = Signal() """ This signal is emitted to inform the search process has finished. """ sig_max_results_reached = Signal() """ This signal is emitted to inform the search process has finished due to reaching the maximum number of results. """ def __init__(self, name=None, plugin=None, parent=None): super().__init__(name, plugin, parent=parent) self.set_conf('text_color', MAIN_TEXT_COLOR) self.set_conf('hist_limit', MAX_PATH_HISTORY) # Attributes self.text_color = self.get_conf('text_color') self.supported_encodings = self.get_conf('supported_encodings') self.search_thread = None self.running = False self.more_options_action = None self.extras_toolbar = None search_text = self.get_conf('search_text', '') path_history = self.get_conf('path_history', []) exclude = self.get_conf('exclude') if not isinstance(search_text, (list, tuple)): search_text = [search_text] if not isinstance(exclude, (list, tuple)): exclude = [exclude] if not isinstance(path_history, (list, tuple)): path_history = [path_history] # Widgets self.search_text_edit = PatternComboBox( self, search_text, id_=FindInFilesWidgetToolbarItems.SearchPatternCombo) self.search_text_edit.lineEdit().setPlaceholderText( _('Write text to search')) self.search_in_label = QLabel(_('Search in:')) self.search_in_label.ID = FindInFilesWidgetToolbarItems.SearchInLabel self.exclude_label = QLabel(_('Exclude:')) self.exclude_label.ID = FindInFilesWidgetToolbarItems.ExcludeLabel self.path_selection_combo = SearchInComboBox( path_history, self, id_=FindInFilesWidgetToolbarItems.SearchInCombo) self.exclude_pattern_edit = PatternComboBox( self, exclude, _("Exclude pattern"), id_=FindInFilesWidgetToolbarItems.ExcludePatternCombo) self.result_browser = ResultsBrowser( self, text_color=self.text_color, max_results=self.get_conf('max_results'), ) # Setup self.exclude_label.setBuddy(self.exclude_pattern_edit) exclude_idx = self.get_conf('exclude_index', None) if (exclude_idx is not None and exclude_idx >= 0 and exclude_idx < self.exclude_pattern_edit.count()): self.exclude_pattern_edit.setCurrentIndex(exclude_idx) search_in_index = self.get_conf('search_in_index', None) self.path_selection_combo.set_current_searchpath_index(search_in_index) # Layout layout = QHBoxLayout() layout.addWidget(self.result_browser) self.setLayout(layout) # Signals self.path_selection_combo.sig_redirect_stdio_requested.connect( self.sig_redirect_stdio_requested) self.search_text_edit.valid.connect(lambda valid: self.find()) self.exclude_pattern_edit.valid.connect(lambda valid: self.find()) self.result_browser.sig_edit_goto_requested.connect( self.sig_edit_goto_requested) self.result_browser.sig_max_results_reached.connect( self.sig_max_results_reached) self.result_browser.sig_max_results_reached.connect( self._stop_and_reset_thread) self.search_text_edit.sig_resized.connect(self._update_size) # --- PluginMainWidget API # ------------------------------------------------------------------------ def get_title(self): return _("Find") def get_focus_widget(self): return self.search_text_edit def setup(self): self.search_regexp_action = self.create_action( FindInFilesWidgetActions.ToggleSearchRegex, text=_('Regular expression'), tip=_('Use regular expressions'), icon=self.create_icon('regex'), toggled=True, initial=self.get_conf('search_text_regexp'), option='search_text_regexp') self.case_action = self.create_action( FindInFilesWidgetActions.ToggleExcludeCase, text=_("Case sensitive"), tip=_("Case sensitive search"), icon=self.create_icon("format_letter_case"), toggled=True, initial=self.get_conf('case_sensitive'), option='case_sensitive') self.find_action = self.create_action( FindInFilesWidgetActions.Find, text=_("&Find in files"), tip=_("Search text"), icon=self.create_icon('find'), triggered=self.find, register_shortcut=False, ) self.exclude_regexp_action = self.create_action( FindInFilesWidgetActions.ToggleExcludeRegex, text=_('Regular expression'), tip=_('Use regular expressions'), icon=self.create_icon('regex'), toggled=True, initial=self.get_conf('exclude_regexp'), option='exclude_regexp') self.exclude_case_action = self.create_action( FindInFilesWidgetActions.ToggleCase, text=_("Exclude case sensitive"), tip=_("Exclude case sensitive"), icon=self.create_icon("format_letter_case"), toggled=True, initial=self.get_conf('exclude_case_sensitive'), option='exclude_case_sensitive') self.more_options_action = self.create_action( FindInFilesWidgetActions.ToggleMoreOptions, text=_('Show advanced options'), tip=_('Show advanced options'), icon=self.create_icon("options_more"), toggled=True, initial=self.get_conf('more_options'), option='more_options') self.set_max_results_action = self.create_action( FindInFilesWidgetActions.MaxResults, text=_('Set maximum number of results'), tip=_('Set maximum number of results'), triggered=lambda x=None: self.set_max_results(), ) # Toolbar toolbar = self.get_main_toolbar() for item in [ self.search_text_edit, self.find_action, self.search_regexp_action, self.case_action, self.more_options_action ]: self.add_item_to_toolbar( item, toolbar=toolbar, section=FindInFilesWidgetMainToolbarSections.Main, ) # Exclude Toolbar self.extras_toolbar = self.create_toolbar( FindInFilesWidgetToolbars.Exclude) stretcher = self.create_stretcher() stretcher.ID = FindInFilesWidgetToolbarItems.Stretcher1 for item in [ self.exclude_label, self.exclude_pattern_edit, self.exclude_regexp_action, stretcher ]: self.add_item_to_toolbar( item, toolbar=self.extras_toolbar, section=FindInFilesWidgetExcludeToolbarSections.Main, ) # Location toolbar location_toolbar = self.create_toolbar( FindInFilesWidgetToolbars.Location) for item in [self.search_in_label, self.path_selection_combo]: self.add_item_to_toolbar( item, toolbar=location_toolbar, section=FindInFilesWidgetLocationToolbarSections.Main, ) menu = self.get_options_menu() self.add_item_to_menu( self.set_max_results_action, menu=menu, ) def update_actions(self): self.find_action.setIcon( self.create_icon('stop' if self.running else 'find')) if self.extras_toolbar and self.more_options_action: self.extras_toolbar.setVisible( self.more_options_action.isChecked()) @on_conf_change(option='more_options') def on_more_options_update(self, value): self.exclude_pattern_edit.setMinimumWidth( self.search_text_edit.width()) if value: icon = self.create_icon('options_less') tip = _('Hide advanced options') else: icon = self.create_icon('options_more') tip = _('Show advanced options') if self.extras_toolbar: self.extras_toolbar.setVisible(value) if self.more_options_action: self.more_options_action.setIcon(icon) self.more_options_action.setToolTip(tip) @on_conf_change(option='max_results') def on_max_results_update(self, value): self.result_browser.set_max_results(value) # --- Private API # ------------------------------------------------------------------------ def _update_size(self, size, old_size): self.exclude_pattern_edit.setMinimumWidth(size.width()) def _get_options(self): """ Get search options. """ text_re = self.search_regexp_action.isChecked() exclude_re = self.exclude_regexp_action.isChecked() case_sensitive = self.case_action.isChecked() # Clear fields self.search_text_edit.lineEdit().setStyleSheet("") self.exclude_pattern_edit.lineEdit().setStyleSheet("") self.exclude_pattern_edit.setToolTip("") self.search_text_edit.setToolTip("") utext = str(self.search_text_edit.currentText()) if not utext: return try: texts = [(utext.encode('utf-8'), 'utf-8')] except UnicodeEncodeError: texts = [] for enc in self.supported_encodings: try: texts.append((utext.encode(enc), enc)) except UnicodeDecodeError: pass exclude = str(self.exclude_pattern_edit.currentText()) if not case_sensitive: texts = [(text[0].lower(), text[1]) for text in texts] file_search = self.path_selection_combo.is_file_search() path = self.path_selection_combo.get_current_searchpath() if not exclude_re: items = [ fnmatch.translate(item.strip()) for item in exclude.split(",") if item.strip() != '' ] exclude = '|'.join(items) # Validate exclude regular expression if exclude: error_msg = regexp_error_msg(exclude) if error_msg: exclude_edit = self.exclude_pattern_edit.lineEdit() exclude_edit.setStyleSheet(self.REGEX_INVALID) tooltip = self.REGEX_ERROR + ': ' + str(error_msg) self.exclude_pattern_edit.setToolTip(tooltip) return None else: exclude = re.compile(exclude) # Validate text regular expression if text_re: error_msg = regexp_error_msg(texts[0][0]) if error_msg: self.search_text_edit.lineEdit().setStyleSheet( self.REGEX_INVALID) tooltip = self.REGEX_ERROR + ': ' + str(error_msg) self.search_text_edit.setToolTip(tooltip) return None else: texts = [(re.compile(x[0]), x[1]) for x in texts] return (path, file_search, exclude, texts, text_re, case_sensitive) def _update_options(self): """ Extract search options from widgets and set the corresponding option. """ hist_limit = self.get_conf('hist_limit') search_texts = [ str(self.search_text_edit.itemText(index)) for index in range(self.search_text_edit.count()) ] excludes = [ str(self.exclude_pattern_edit.itemText(index)) for index in range(self.exclude_pattern_edit.count()) ] path_history = self.path_selection_combo.get_external_paths() self.set_conf('path_history', path_history) self.set_conf('search_text', search_texts[:hist_limit]) self.set_conf('exclude', excludes[:hist_limit]) self.set_conf('path_history', path_history[-hist_limit:]) self.set_conf('exclude_index', self.exclude_pattern_edit.currentIndex()) self.set_conf('search_in_index', self.path_selection_combo.currentIndex()) def _handle_search_complete(self, completed): """ Current search thread has finished. """ self.result_browser.set_sorting(ON) self.result_browser.set_width() self.result_browser.expandAll() if self.search_thread is None: return self.sig_finished.emit() found = self.search_thread.get_results() self._stop_and_reset_thread() if found is not None: self.result_browser.show() self.stop_spinner() self.update_actions() def _stop_and_reset_thread(self, ignore_results=False): """Stop current search thread and clean-up.""" if self.search_thread is not None: if self.search_thread.isRunning(): if ignore_results: self.search_thread.sig_finished.disconnect( self.search_complete) self.search_thread.stop() self.search_thread.wait() self.search_thread.setParent(None) self.search_thread = None self.running = False self.stop_spinner() self.update_actions() # --- Public API # ------------------------------------------------------------------------ @property def path(self): """Return the current path.""" return self.path_selection_combo.path @property def project_path(self): """Return the current project path.""" return self.path_selection_combo.project_path @property def file_path(self): """Return the current file path.""" return self.path_selection_combo.file_path def set_directory(self, directory): """ Set directory as current path. Parameters ---------- directory: str Directory path string. """ self.path_selection_combo.path = osp.abspath(directory) def set_project_path(self, path): """ Set path as current project path. Parameters ---------- path: str Project path string. """ self.path_selection_combo.set_project_path(path) def disable_project_search(self): """Disable project search path in combobox.""" self.path_selection_combo.set_project_path(None) def set_file_path(self, path): """ Set path as current file path. Parameters ---------- path: str File path string. """ self.path_selection_combo.file_path = path def set_search_text(self, text): """ Set current search text. Parameters ---------- text: str Search string. Notes ----- If `text` is empty, focus will be given to the search lineedit and no search will be performed. """ if text: self.search_text_edit.add_text(text) self.search_text_edit.lineEdit().selectAll() self.search_text_edit.setFocus() def find(self): """ Start/stop find action. Notes ----- If there is no search running, this will start the search. If there is a search running, this will stop it. """ if self.running: self.stop() else: self.start() def stop(self): """Stop find thread.""" self._stop_and_reset_thread() def start(self): """Start find thread.""" options = self._get_options() if options is None: return self._stop_and_reset_thread(ignore_results=True) search_text = self.search_text_edit.currentText() # Update and set options self._update_options() # Setup result_browser self.result_browser.set_path(options[0]) self.result_browser.longest_file_item = '' self.result_browser.longest_line_item = '' # Start self.running = True self.start_spinner() self.search_thread = SearchThread(self, search_text, self.text_color) self.search_thread.sig_finished.connect(self._handle_search_complete) self.search_thread.sig_file_match.connect( self.result_browser.append_file_result) self.search_thread.sig_line_match.connect( self.result_browser.append_result) self.result_browser.clear_title(search_text) self.search_thread.initialize(*self._get_options()) self.search_thread.start() self.update_actions() def add_external_path(self, path): """ Parameters ---------- path: str Path to add to combobox. """ self.path_selection_combo.add_external_path(path) def set_max_results(self, value=None): """ Set maximum amount of results to add to the result browser. Parameters ---------- value: int, optional Number of results. If None an input dialog will be used. Default is None. """ if value is None: # Create dialog dialog = QInputDialog(self) # Set dialog properties dialog.setModal(False) dialog.setWindowTitle(_('Max results')) dialog.setLabelText(_('Set maximum number of results: ')) dialog.setInputMode(QInputDialog.IntInput) dialog.setIntRange(1, 10000) dialog.setIntStep(1) dialog.setIntValue(self.get_conf('max_results')) # Connect slot dialog.intValueSelected.connect( lambda value: self.set_conf('max_results', value)) dialog.show() else: self.set_conf('max_results', value)
def __init__(self, layer): super().__init__(layer) self.layer.events.interpolation.connect(self._on_interpolation_change) self.layer.events.rendering.connect(self._on_rendering_change) self.layer.events.iso_threshold.connect(self._on_iso_threshold_change) self.layer.events.attenuation.connect(self._on_attenuation_change) self.layer._dims.events.ndisplay.connect(self._on_ndisplay_change) self.interpComboBox = QComboBox(self) self.interpComboBox.activated[str].connect(self.changeInterpolation) self.interpLabel = QLabel('interpolation:') renderComboBox = QComboBox(self) renderComboBox.addItems(Rendering.keys()) index = renderComboBox.findText(self.layer.rendering, Qt.MatchFixedString) renderComboBox.setCurrentIndex(index) renderComboBox.activated[str].connect(self.changeRendering) self.renderComboBox = renderComboBox self.renderLabel = QLabel('rendering:') sld = QSlider(Qt.Horizontal, parent=self) sld.setFocusPolicy(Qt.NoFocus) sld.setMinimum(0) sld.setMaximum(100) sld.setSingleStep(1) sld.setValue(int(self.layer.iso_threshold * 100)) sld.valueChanged.connect(self.changeIsoThreshold) self.isoThresholdSlider = sld self.isoThresholdLabel = QLabel('iso threshold:') sld = QSlider(Qt.Horizontal, parent=self) sld.setFocusPolicy(Qt.NoFocus) sld.setMinimum(0) sld.setMaximum(100) sld.setSingleStep(1) sld.setValue(int(self.layer.attenuation * 200)) sld.valueChanged.connect(self.changeAttenuation) self.attenuationSlider = sld self.attenuationLabel = QLabel('attenuation:') self._on_ndisplay_change() colormap_layout = QHBoxLayout() if hasattr(self.layer, 'rgb') and self.layer.rgb: colormap_layout.addWidget(QLabel("RGB")) self.colormapComboBox.setVisible(False) self.colorbarLabel.setVisible(False) else: colormap_layout.addWidget(self.colorbarLabel) colormap_layout.addWidget(self.colormapComboBox) colormap_layout.addStretch(1) # grid_layout created in QtLayerControls # addWidget(widget, row, column, [row_span, column_span]) self.grid_layout.addWidget(QLabel('opacity:'), 0, 0) self.grid_layout.addWidget(self.opacitySlider, 0, 1) self.grid_layout.addWidget(QLabel('contrast limits:'), 1, 0) self.grid_layout.addWidget(self.contrastLimitsSlider, 1, 1) self.grid_layout.addWidget(QLabel('gamma:'), 2, 0) self.grid_layout.addWidget(self.gammaSlider, 2, 1) self.grid_layout.addWidget(QLabel('colormap:'), 3, 0) self.grid_layout.addLayout(colormap_layout, 3, 1) self.grid_layout.addWidget(QLabel('blending:'), 4, 0) self.grid_layout.addWidget(self.blendComboBox, 4, 1) self.grid_layout.addWidget(self.interpLabel, 5, 0) self.grid_layout.addWidget(self.interpComboBox, 5, 1) self.grid_layout.addWidget(self.renderLabel, 6, 0) self.grid_layout.addWidget(self.renderComboBox, 6, 1) self.grid_layout.addWidget(self.isoThresholdLabel, 7, 0) self.grid_layout.addWidget(self.isoThresholdSlider, 7, 1) self.grid_layout.addWidget(self.attenuationLabel, 8, 0) self.grid_layout.addWidget(self.attenuationSlider, 8, 1) self.grid_layout.setRowStretch(9, 1) self.grid_layout.setColumnStretch(1, 1) self.grid_layout.setSpacing(4)
def __init__(self, directory=None): QWidget.__init__(self) vlayout = QVBoxLayout() self.setLayout(vlayout) self.explorer = ProjectExplorerWidget(self, show_all=True) if directory is not None: self.directory = directory else: self.directory = osp.dirname(osp.abspath(__file__)) self.explorer.setup_project(self.directory) vlayout.addWidget(self.explorer) hlayout1 = QHBoxLayout() vlayout.addLayout(hlayout1) label = QLabel("<b>Open file:</b>") label.setAlignment(Qt.AlignRight) hlayout1.addWidget(label) self.label1 = QLabel() hlayout1.addWidget(self.label1) self.explorer.sig_open_file.connect(self.label1.setText) hlayout3 = QHBoxLayout() vlayout.addLayout(hlayout3) label = QLabel("<b>Option changed:</b>") label.setAlignment(Qt.AlignRight) hlayout3.addWidget(label) self.label3 = QLabel() hlayout3.addWidget(self.label3) self.explorer.sig_option_changed.connect( lambda x, y: self.label3.setText('option_changed: %r, %r' % (x, y)))