def switch_rows(table: QTableWidget, old_position, new_position): """ Helper function to switch a row in a table. Works for booking and entry table. :param table: :param old_position: :param new_position: :return: """ for col_index in range(table.columnCount()): old_item = table.item(old_position, col_index) new_item = table.item(new_position, col_index) if old_item is not None and new_item is not None: old_text = old_item.text() new_text = new_item.text() table.setItem(old_position, col_index, QTableWidgetItem(new_text)) table.setItem(new_position, col_index, QTableWidgetItem(old_text)) else: old_cell_widget = table.cellWidget(old_position, col_index) new_cell_widget = table.cellWidget(new_position, col_index) if old_cell_widget is not None and new_cell_widget is not None: if isinstance(old_cell_widget, QTimeEdit) and isinstance(new_cell_widget, QTimeEdit): qte = QTimeEdit(table) qte.setTime(new_cell_widget.time()) table.setCellWidget(old_position, col_index, qte) qte = QTimeEdit(table) qte.setTime(old_cell_widget.time()) table.setCellWidget(new_position, col_index, qte) if isinstance(old_cell_widget, QDoubleSpinBox) and isinstance(new_cell_widget, QDoubleSpinBox): qdsb = QDoubleSpinBox(table) qdsb.setValue(new_cell_widget.value()) table.setCellWidget(old_position, col_index, qdsb) qdsb = QDoubleSpinBox(table) qdsb.setValue(old_cell_widget.value()) table.setCellWidget(new_position, col_index, qdsb) if isinstance(old_cell_widget, QCheckBox) and isinstance(new_cell_widget, QCheckBox): qcb = QCheckBox(table) qcb.setChecked(new_cell_widget.isChecked()) table.setCellWidget(old_position, col_index, qcb) qcb = QCheckBox(table) qcb.setChecked(old_cell_widget.isChecked()) table.setCellWidget(new_position, col_index, qcb)
class SimpleMeasurements(QWidget): def __init__(self, settings: StackSettings, parent=None): super().__init__(parent) self.settings = settings self.calculate_btn = QPushButton("Calculate") self.calculate_btn.clicked.connect(self.calculate) self.result_view = QTableWidget() self.channel_select = ChannelComboBox() self.units_select = QEnumComboBox(enum_class=Units) self.units_select.setCurrentEnum( self.settings.get("simple_measurements.units", Units.nm)) self.units_select.currentIndexChanged.connect(self.change_units) self._shift = 2 layout = QHBoxLayout() self.measurement_layout = QVBoxLayout() l1 = QHBoxLayout() l1.addWidget(QLabel("Units")) l1.addWidget(self.units_select) self.measurement_layout.addLayout(l1) l2 = QHBoxLayout() l2.addWidget(QLabel("Channel")) l2.addWidget(self.channel_select) self.measurement_layout.addLayout(l2) layout.addLayout(self.measurement_layout) result_layout = QVBoxLayout() result_layout.addWidget(self.result_view) result_layout.addWidget(self.calculate_btn) layout.addLayout(result_layout) self.setLayout(layout) self.setWindowTitle("Measurement") if self.window() == self: with suppress(KeyError): geometry = self.settings.get_from_profile( "simple_measurement_window_geometry") self.restoreGeometry( QByteArray.fromHex(bytes(geometry, "ascii"))) def closeEvent(self, event: QCloseEvent) -> None: """ Save geometry if widget is used as standalone window. """ if self.window() == self: self.settings.set_in_profile( "simple_measurement_window_geometry", self.saveGeometry().toHex().data().decode("ascii")) super().closeEvent(event) def calculate(self): if self.settings.roi is None: QMessageBox.warning(self, "No segmentation", "need segmentation to work") return to_calculate = [] for i in range(self._shift, self.measurement_layout.count()): # noinspection PyTypeChecker chk: QCheckBox = self.measurement_layout.itemAt(i).widget() if chk.isChecked(): leaf: Leaf = MEASUREMENT_DICT[chk.text()].get_starting_leaf() to_calculate.append( leaf.replace_(per_component=PerComponent.Yes, area=AreaType.ROI)) if not to_calculate: QMessageBox.warning(self, "No measurement", "Select at least one measurement") return profile = MeasurementProfile( "", [MeasurementEntry(x.name, x) for x in to_calculate]) dial = ExecuteFunctionDialog( profile.calculate, kwargs={ "image": self.settings.image, "channel_num": self.channel_select.get_value(), "roi": self.settings.roi_info, "result_units": self.units_select.currentEnum(), }, ) dial.exec_() result: MeasurementResult = dial.get_result() values = result.get_separated() labels = result.get_labels() self.result_view.clear() self.result_view.setColumnCount(len(values) + 1) self.result_view.setRowCount(len(labels)) for i, val in enumerate(labels): self.result_view.setItem(i, 0, QTableWidgetItem(val)) for j, values_list in enumerate(values): for i, val in enumerate(values_list): self.result_view.setItem(i, j + 1, QTableWidgetItem(str(val))) def _clean_measurements(self): selected = set() for _ in range(self.measurement_layout.count() - self._shift): # noinspection PyTypeChecker chk: QCheckBox = self.measurement_layout.takeAt( self._shift).widget() if chk.isChecked(): selected.add(chk.text()) chk.deleteLater() return selected def refresh_measurements(self): selected = self._clean_measurements() for val in MEASUREMENT_DICT.values(): area = val.get_starting_leaf().area pc = val.get_starting_leaf().per_component if (val.get_fields() or (area is not None and area != AreaType.ROI) or (pc is not None and pc != PerComponent.Yes)): continue text = val.get_name() chk = QCheckBox(text) if text in selected: chk.setChecked(True) self.measurement_layout.addWidget(chk) def keyPressEvent(self, e: QKeyEvent): if not e.modifiers() & Qt.ControlModifier: return selected = self.result_view.selectedRanges() if e.key() == Qt.Key_C: # copy s = "" for r in range(selected[0].topRow(), selected[0].bottomRow() + 1): for c in range(selected[0].leftColumn(), selected[0].rightColumn() + 1): try: s += str(self.result_view.item(r, c).text()) + "\t" except AttributeError: s += "\t" s = s[:-1] + "\n" # eliminate last '\t' QApplication.clipboard().setText(s) def event(self, event: QEvent) -> bool: if event.type() == QEvent.WindowActivate: if self.settings.image is not None: self.channel_select.change_channels_num( self.settings.image.channels) self.refresh_measurements() return super().event(event) def change_units(self): self.settings.set("simple_measurements.units", self.units_select.currentEnum())
class EventsDialog(QDialog): def __init__(self, parent, pos, desc): super().__init__(parent) self.setWindowTitle("Edit Events") self.table = QTableWidget(len(pos), 2) for row, (p, d) in enumerate(zip(pos, desc)): self.table.setItem(row, 0, IntTableWidgetItem(p)) self.table.setItem(row, 1, IntTableWidgetItem(d)) self.table.setHorizontalHeaderLabels(["Position", "Type"]) self.table.horizontalHeader().setStretchLastSection(True) self.table.verticalHeader().setVisible(False) self.table.setShowGrid(False) self.table.setSelectionBehavior(QAbstractItemView.SelectRows) self.table.setSortingEnabled(True) self.table.sortByColumn(0, Qt.AscendingOrder) vbox = QVBoxLayout(self) vbox.addWidget(self.table) hbox = QHBoxLayout() self.add_button = QPushButton("+") self.remove_button = QPushButton("-") buttonbox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) hbox.addWidget(self.add_button) hbox.addWidget(self.remove_button) hbox.addStretch() hbox.addWidget(buttonbox) vbox.addLayout(hbox) buttonbox.accepted.connect(self.accept) buttonbox.rejected.connect(self.reject) self.table.itemSelectionChanged.connect(self.toggle_buttons) self.remove_button.clicked.connect(self.remove_event) self.add_button.clicked.connect(self.add_event) self.toggle_buttons() self.resize(300, 500) @Slot() def toggle_buttons(self): """Toggle + and - buttons.""" n_items = len(self.table.selectedItems()) if n_items == 2: # one row (2 items) selected self.add_button.setEnabled(True) self.remove_button.setEnabled(True) elif n_items > 2: # more than one row selected self.add_button.setEnabled(False) self.remove_button.setEnabled(True) else: # no rows selected self.add_button.setEnabled(False) self.remove_button.setEnabled(False) def add_event(self): current_row = self.table.selectedIndexes()[0].row() pos = int(self.table.item(current_row, 0).data(Qt.DisplayRole)) self.table.setSortingEnabled(False) self.table.insertRow(current_row) self.table.setItem(current_row, 0, IntTableWidgetItem(pos)) self.table.setItem(current_row, 1, IntTableWidgetItem(0)) self.table.setSortingEnabled(True) def remove_event(self): rows = {index.row() for index in self.table.selectedIndexes()} self.table.clearSelection() for row in sorted(rows, reverse=True): self.table.removeRow(row)
class ShortcutEditor(QWidget): """Widget to edit keybindings for napari.""" valueChanged = Signal(dict) VIEWER_KEYBINDINGS = trans._('Viewer key bindings') def __init__( self, parent: QWidget = None, description: str = "", value: dict = None, ): super().__init__(parent=parent) # Flag to not run _set_keybinding method after setting special symbols. # When changing line edit to special symbols, the _set_keybinding # method will be called again (and breaks) and is not needed. self._skip = False layers = [ Image, Labels, Points, Shapes, Surface, Vectors, ] self.key_bindings_strs = OrderedDict() # widgets self.layer_combo_box = QComboBox(self) self._label = QLabel(self) self._table = QTableWidget(self) self._table.setSelectionBehavior(QAbstractItemView.SelectItems) self._table.setSelectionMode(QAbstractItemView.SingleSelection) self._table.setShowGrid(False) self._restore_button = QPushButton(trans._("Reset All Keybindings")) # Set up dictionary for layers and associated actions. all_actions = action_manager._actions.copy() self.key_bindings_strs[self.VIEWER_KEYBINDINGS] = [] for layer in layers: if len(layer.class_keymap) == 0: actions = [] else: actions = action_manager._get_layer_actions(layer) for name, action in actions.items(): all_actions.pop(name) self.key_bindings_strs[f"{layer.__name__} layer"] = actions # Left over actions can go here. self.key_bindings_strs[self.VIEWER_KEYBINDINGS] = all_actions # Widget set up self.layer_combo_box.addItems(list(self.key_bindings_strs)) self.layer_combo_box.activated[str].connect(self._set_table) self.layer_combo_box.setCurrentText(self.VIEWER_KEYBINDINGS) self._set_table() self._label.setText("Group") self._restore_button.clicked.connect(self.restore_defaults) # layout hlayout1 = QHBoxLayout() hlayout1.addWidget(self._label) hlayout1.addWidget(self.layer_combo_box) hlayout1.setContentsMargins(0, 0, 0, 0) hlayout1.setSpacing(20) hlayout1.addStretch(0) hlayout2 = QHBoxLayout() hlayout2.addLayout(hlayout1) hlayout2.addWidget(self._restore_button) layout = QVBoxLayout() layout.addLayout(hlayout2) layout.addWidget(self._table) self.setLayout(layout) def restore_defaults(self): """Launches dialog to confirm restore choice.""" self._reset_dialog = ConfirmDialog( parent=self, text=trans._( "Are you sure you want to restore default shortcuts?" ), ) self._reset_dialog.valueChanged.connect(self._reset_shortcuts) self._reset_dialog.exec_() def _reset_shortcuts(self, event=None): """Reset shortcuts to default settings. Parameters ---------- event: Bool Event will indicate whether user confirmed resetting shortcuts. """ # event is True if the user confirmed reset shortcuts if event is True: get_settings().reset(sections=['shortcuts']) for ( action, shortcuts, ) in get_settings().shortcuts.shortcuts.items(): action_manager.unbind_shortcut(action) for shortcut in shortcuts: action_manager.bind_shortcut(action, shortcut) self._set_table(layer_str=self.layer_combo_box.currentText()) def _set_table(self, layer_str=''): """Builds and populates keybindings table. Parameters ---------- layer_str = str If layer_str is not empty, then show the specified layers' keybinding shortcut table. """ # Keep track of what is in each column. self._action_name_col = 0 self._icon_col = 1 self._shortcut_col = 2 self._action_col = 3 # Set header strings for table. header_strs = ['', '', '', ''] header_strs[self._action_name_col] = trans._('Action') header_strs[self._shortcut_col] = trans._('Keybinding') # If no layer_str, then set the page to the viewer keybindings page. if layer_str == '': layer_str = self.VIEWER_KEYBINDINGS # If rebuilding the table, then need to disconnect the connection made # previously as well as clear the table contents. try: self._table.cellChanged.disconnect(self._set_keybinding) except TypeError: # if building the first time, the cells are not yet connected so this would fail. pass except RuntimeError: # Needed to pass some tests. pass self._table.clearContents() # Table styling set up. self._table.horizontalHeader().setStretchLastSection(True) self._table.horizontalHeader().setStyleSheet( 'border-bottom: 2px solid white;' ) # Get all actions for the layer. actions = self.key_bindings_strs[layer_str] if len(actions) > 0: # Set up table based on number of actions and needed columns. self._table.setRowCount(len(actions)) self._table.setColumnCount(4) # Set up delegate in order to capture keybindings. self._table.setItemDelegateForColumn( self._shortcut_col, ShortcutDelegate(self._table) ) self._table.setHorizontalHeaderLabels(header_strs) self._table.verticalHeader().setVisible(False) # Hide the column with action names. These are kept here for reference when needed. self._table.setColumnHidden(self._action_col, True) # Column set up. self._table.setColumnWidth(self._action_name_col, 250) self._table.setColumnWidth(self._shortcut_col, 200) self._table.setColumnWidth(self._icon_col, 50) # Go through all the actions in the layer and add them to the table. for row, (action_name, action) in enumerate(actions.items()): shortcuts = action_manager._shortcuts.get(action_name, []) # Set action description. Make sure its not selectable/editable. item = QTableWidgetItem(action.description) item.setFlags(Qt.NoItemFlags) self._table.setItem(row, self._action_name_col, item) # Create empty item in order to make sure this column is not # selectable/editable. item = QTableWidgetItem("") item.setFlags(Qt.NoItemFlags) self._table.setItem(row, self._icon_col, item) # Set the shortcuts in table. item_shortcut = QTableWidgetItem( Shortcut(list(shortcuts)[0]).platform if shortcuts else "" ) self._table.setItem(row, self._shortcut_col, item_shortcut) # action_name is stored in the table to use later, but is not shown on dialog. item_action = QTableWidgetItem(action_name) self._table.setItem(row, self._action_col, item_action) # If a cell is changed, run .set_keybinding. self._table.cellChanged.connect(self._set_keybinding) else: # Display that there are no actions for this layer. self._table.setRowCount(1) self._table.setColumnCount(4) self._table.setHorizontalHeaderLabels(header_strs) self._table.verticalHeader().setVisible(False) self._table.setColumnHidden(self._action_col, True) item = QTableWidgetItem('No key bindings') item.setFlags(Qt.NoItemFlags) self._table.setItem(0, 0, item) def _set_keybinding(self, row, col): """Checks the new keybinding to determine if it can be set. Parameters ---------- row: int Row in keybindings table that is being edited. col: int Column being edited (shortcut column). """ if self._skip is True: # Do nothing if the text is setting to a symbol. # Its already been handled. self._skip = False return if col == self._shortcut_col: # Get all layer actions and viewer actions in order to determine # the new shortcut is not already set to an action. current_layer_text = self.layer_combo_box.currentText() layer_actions = self.key_bindings_strs[current_layer_text] actions_all = layer_actions.copy() if current_layer_text is not self.VIEWER_KEYBINDINGS: viewer_actions = self.key_bindings_strs[ self.VIEWER_KEYBINDINGS ] actions_all.update(viewer_actions) # get the current item from shortcuts column current_item = self._table.currentItem() new_shortcut = current_item.text() new_shortcut = new_shortcut[0].upper() + new_shortcut[1:] # get the current action name current_action = self._table.item(row, self._action_col).text() # get the original shortcutS current_shortcuts = list( action_manager._shortcuts.get(current_action, {}) ) # Flag to indicate whether to set the new shortcut. replace = True # Go through all layer actions to determine if the new shortcut is already here. for row1, (action_name, action) in enumerate(actions_all.items()): shortcuts = action_manager._shortcuts.get(action_name, []) if new_shortcut in shortcuts: # Shortcut is here (either same action or not), don't replace in settings. replace = False if action_name != current_action: # the shortcut is saved to a different action # show warning symbols self._show_warning_icons([row, row1]) # show warning message message = trans._( "The keybinding <b>{new_shortcut}</b> " + "is already assigned to <b>{action_description}</b>; change or clear " + "that shortcut before assigning <b>{new_shortcut}</b> to this one.", new_shortcut=new_shortcut, action_description=action.description, ) self._show_warning(new_shortcut, action, row, message) if len(current_shortcuts) > 0: # If there was a shortcut set originally, then format it and reset the text. format_shortcut = Shortcut( current_shortcuts[0] ).platform if format_shortcut != current_shortcuts[0]: # only skip the next round if there are special symbols self._skip = True current_item.setText(format_shortcut) else: # There wasn't a shortcut here. current_item.setText("") self._cleanup_warning_icons([row, row1]) break else: # This shortcut was here. Reformat and reset text. format_shortcut = Shortcut(new_shortcut).platform if format_shortcut != new_shortcut: # Only skip the next round if there are special symbols in shortcut. self._skip = True current_item.setText(format_shortcut) if replace is True: # This shortcut is not taken. # Unbind current action from shortcuts in action manager. action_manager.unbind_shortcut(current_action) if new_shortcut != "": # Bind the new shortcut. try: action_manager.bind_shortcut( current_action, new_shortcut ) except TypeError: # Shortcut is not valid. action_manager._shortcuts[current_action] = set() # need to rebind the old shortcut action_manager.unbind_shortcut(current_action) action_manager.bind_shortcut( current_action, current_shortcuts[0] ) # Show warning message to let user know this shortcut is invalid. self._show_warning_icons([row]) message = trans._( "<b>{new_shortcut}</b> is not a valid keybinding.", new_shortcut=new_shortcut, ) self._show_warning(new_shortcut, action, row, message) self._cleanup_warning_icons([row]) format_shortcut = Shortcut( current_shortcuts[0] ).platform if format_shortcut != current_shortcuts[0]: # Skip the next round if there are special symbols. self._skip = True # Update text to formated shortcut. current_item.setText(format_shortcut) return # The new shortcut is valid and can be displayed in widget. # Keep track of what changed. new_value_dict = {current_action: [new_shortcut]} # Format new shortcut. format_shortcut = Shortcut(new_shortcut).platform if format_shortcut != new_shortcut: # Skip the next round because there are special symbols. self._skip = True # Update text to formated shortcut. current_item.setText(format_shortcut) else: # There is not a new shortcut to bind. Keep track of it. if action_manager._shortcuts[current_action] != "": new_value_dict = {current_action: [""]} if new_value_dict: # Emit signal when new value set for shortcut. self.valueChanged.emit(new_value_dict) def _show_warning_icons(self, rows): """Creates and displays the warning icons. Parameters ---------- rows: list[int] List of row numbers that should have the icon. """ for row in rows: self.warning_indicator = QLabel(self) self.warning_indicator.setObjectName("error_label") self._table.setCellWidget( row, self._icon_col, self.warning_indicator ) def _cleanup_warning_icons(self, rows): """Remove the warning icons from the shortcut table. Paramters --------- rows: list[int] List of row numbers to remove warning icon from. """ for row in rows: self._table.setCellWidget(row, self._icon_col, QLabel("")) def _show_warning(self, new_shortcut='', action=None, row=0, message=''): """Creates and displays warning message when shortcut is already assigned. Parameters ---------- new_shortcut: str The new shortcut attempting to be set. action: Action Action that is already assigned with the shortcut. row: int Row in table where the shortcut is attempting to be set. message: str Message to be displayed in warning pop up. """ # Determine placement of warning message. delta_y = 105 delta_x = 10 global_point = self.mapToGlobal( QPoint( self._table.columnViewportPosition(self._shortcut_col) + delta_x, self._table.rowViewportPosition(row) + delta_y, ) ) # Create warning pop up and move it to desired position. self._warn_dialog = KeyBindWarnPopup( text=message, ) self._warn_dialog.move(global_point) # Styling adjustments. self._warn_dialog.resize(250, self._warn_dialog.sizeHint().height()) self._warn_dialog._message.resize( 200, self._warn_dialog._message.sizeHint().height() ) self._warn_dialog.exec_() def value(self): """Return the actions and shortcuts currently assigned in action manager. Returns ------- value: dict Dictionary of action names and shortcuts assigned to them. """ value = {} for row, (action_name, action) in enumerate( action_manager._actions.items() ): shortcuts = action_manager._shortcuts.get(action_name, []) value[action_name] = list(shortcuts) return value
class MeasurementWidget(QWidget): """ :type settings: Settings :type segment: Segment """ def __init__(self, settings: PartSettings, segment=None): super().__init__() self.settings = settings self.segment = segment self.measurements_storage = MeasurementsStorage() self.recalculate_button = QPushButton( "Recalculate and\n replace measurement", self) self.recalculate_button.clicked.connect( self.replace_measurement_result) self.recalculate_append_button = QPushButton( "Recalculate and\n append measurement", self) self.recalculate_append_button.clicked.connect( self.append_measurement_result) self.copy_button = QPushButton("Copy to clipboard", self) self.copy_button.setToolTip( "You cacn copy also with 'Ctrl+C'. To get raw copy copy with 'Ctrl+Shit+C'" ) self.horizontal_measurement_present = QCheckBox( "Horizontal view", self) self.no_header = QCheckBox("No header", self) self.no_units = QCheckBox("No units", self) self.no_units.setChecked(True) self.no_units.clicked.connect(self.refresh_view) self.expand_mode = QCheckBox("Expand", self) self.expand_mode.setToolTip( "Shows results for each component in separate entry") self.file_names = EnumComboBox(FileNamesEnum) self.file_names_label = QLabel("Add file name:") self.file_names.currentIndexChanged.connect(self.refresh_view) self.horizontal_measurement_present.stateChanged.connect( self.refresh_view) self.expand_mode.stateChanged.connect(self.refresh_view) self.copy_button.clicked.connect(self.copy_to_clipboard) self.measurement_type = SearchCombBox(self) # noinspection PyUnresolvedReferences self.measurement_type.currentIndexChanged.connect( self.measurement_profile_selection_changed) self.measurement_type.addItem("<none>") self.measurement_type.addItems( list(sorted(self.settings.measurement_profiles.keys()))) self.measurement_type.setToolTip( 'You can create new measurement profile in advanced window, in tab "Measurement settings"' ) self.channels_chose = ChannelComboBox() self.units_choose = EnumComboBox(Units) self.units_choose.set_value(self.settings.get("units_value", Units.nm)) self.info_field = QTableWidget(self) self.info_field.setColumnCount(3) self.info_field.setHorizontalHeaderLabels(["Name", "Value", "Units"]) self.measurement_add_shift = 0 layout = QVBoxLayout() # layout.addWidget(self.recalculate_button) v_butt_layout = QVBoxLayout() v_butt_layout.setSpacing(1) self.up_butt_layout = QHBoxLayout() self.up_butt_layout.addWidget(self.recalculate_button) self.up_butt_layout.addWidget(self.recalculate_append_button) self.butt_layout = QHBoxLayout() # self.butt_layout.setMargin(0) # self.butt_layout.setSpacing(10) self.butt_layout.addWidget(self.horizontal_measurement_present, 1) self.butt_layout.addWidget(self.no_header, 1) self.butt_layout.addWidget(self.no_units, 1) self.butt_layout.addWidget(self.expand_mode, 1) self.butt_layout.addWidget(self.file_names_label) self.butt_layout.addWidget(self.file_names, 1) self.butt_layout.addWidget(self.copy_button, 2) self.butt_layout2 = QHBoxLayout() self.butt_layout3 = QHBoxLayout() self.butt_layout3.addWidget(QLabel("Channel:")) self.butt_layout3.addWidget(self.channels_chose) self.butt_layout3.addWidget(QLabel("Units:")) self.butt_layout3.addWidget(self.units_choose) # self.butt_layout3.addWidget(QLabel("Noise removal:")) # self.butt_layout3.addWidget(self.noise_removal_method) self.butt_layout3.addWidget(QLabel("Measurement set:")) self.butt_layout3.addWidget(self.measurement_type, 2) v_butt_layout.addLayout(self.up_butt_layout) v_butt_layout.addLayout(self.butt_layout) v_butt_layout.addLayout(self.butt_layout2) v_butt_layout.addLayout(self.butt_layout3) layout.addLayout(v_butt_layout) # layout.addLayout(self.butt_layout) layout.addWidget(self.info_field) self.setLayout(layout) # noinspection PyArgumentList self.clip = QApplication.clipboard() self.settings.image_changed[int].connect(self.image_changed) self.previous_profile = None def check_if_measurement_can_be_calculated(self, name): if name == "<none>": return "<none>" profile: MeasurementProfile = self.settings.measurement_profiles.get( name) if profile.is_any_mask_measurement() and self.settings.mask is None: QMessageBox.information( self, "Need mask", "To use this measurement set please use data with mask loaded", QMessageBox.Ok) self.measurement_type.setCurrentIndex(0) return "<none>" if self.settings.roi is None: QMessageBox.information( self, "Need segmentation", 'Before calculating please create segmentation ("Execute" button)', QMessageBox.Ok, ) self.measurement_type.setCurrentIndex(0) return "<none>" return name def image_changed(self, channels_num): self.channels_chose.change_channels_num(channels_num) def measurement_profile_selection_changed(self, index): text = self.measurement_type.itemText(index) text = self.check_if_measurement_can_be_calculated(text) try: stat = self.settings.measurement_profiles[text] is_mask = stat.is_any_mask_measurement() disable = is_mask and (self.settings.mask is None) except KeyError: disable = True self.recalculate_button.setDisabled(disable) self.recalculate_append_button.setDisabled(disable) if disable: self.recalculate_button.setToolTip( "Measurement profile contains mask measurements when mask is not loaded" ) self.recalculate_append_button.setToolTip( "Measurement profile contains mask measurements when mask is not loaded" ) else: self.recalculate_button.setToolTip("") self.recalculate_append_button.setToolTip("") def copy_to_clipboard(self): s = "" for r in range(self.info_field.rowCount()): for c in range(self.info_field.columnCount()): try: s += str(self.info_field.item(r, c).text()) + "\t" except AttributeError: s += "\t" s = s[:-1] + "\n" # eliminate last '\t' self.clip.setText(s) def replace_measurement_result(self): self.measurements_storage.clear() self.previous_profile = "" self.append_measurement_result() def refresh_view(self): self.measurements_storage.set_expand(self.expand_mode.isChecked()) self.measurements_storage.set_show_units(not self.no_units.isChecked()) self.info_field.clear() save_orientation = self.horizontal_measurement_present.isChecked() columns, rows = self.measurements_storage.get_size(save_orientation) self.info_field.setColumnCount(columns) self.info_field.setRowCount(rows) self.info_field.setHorizontalHeaderLabels( self.measurements_storage.get_header(save_orientation)) self.info_field.setVerticalHeaderLabels( self.measurements_storage.get_rows(save_orientation)) for x in range(rows): for y in range(columns): self.info_field.setItem( x, y, QTableWidgetItem( self.measurements_storage.get_val_as_str( x, y, save_orientation))) if self.file_names.get_value() == FileNamesEnum.No: if save_orientation: self.info_field.removeColumn(0) else: self.info_field.removeRow(0) elif self.file_names.get_value() == FileNamesEnum.Short: if save_orientation: columns = 1 else: rows = 1 for x in range(rows): for y in range(columns): item = self.info_field.item(x, y) item.setText(os.path.basename(item.text())) self.info_field.setEditTriggers(QAbstractItemView.NoEditTriggers) def append_measurement_result(self): try: compute_class = self.settings.measurement_profiles[ self.measurement_type.currentText()] except KeyError: QMessageBox.warning( self, "Measurement profile not found", f"Measurement profile '{self.measurement_type.currentText()}' not found'", ) return if self.settings.roi is None: return units = self.units_choose.get_value() # FIXME find which errors should be displayed as warning # def exception_hook(exception): # QMessageBox.warning(self, "Calculation error", f"Error during calculation: {exception}") for num in compute_class.get_channels_num(): if num >= self.settings.image.channels: QMessageBox.warning( self, "Measurement error", "Cannot calculate this measurement because " f"image do not have channel {num+1}", ) return thread = ExecuteFunctionThread( compute_class.calculate, [ self.settings.image, self.channels_chose.currentIndex(), self.settings.roi_info, units ], ) dial = WaitingDialog( thread, "Measurement calculation") # , exception_hook=exception_hook) dial.exec() stat: MeasurementResult = thread.result if stat is None: return stat.set_filename(self.settings.image_path) self.measurements_storage.add_measurements(stat) self.previous_profile = compute_class.name self.refresh_view() def keyPressEvent(self, e: QKeyEvent): if e.modifiers() & Qt.ControlModifier: selected = self.info_field.selectedRanges() if e.key() == Qt.Key_C: # copy s = "" for r in range(selected[0].topRow(), selected[0].bottomRow() + 1): for c in range(selected[0].leftColumn(), selected[0].rightColumn() + 1): try: s += str(self.info_field.item(r, c).text()) + "\t" except AttributeError: s += "\t" s = s[:-1] + "\n" # eliminate last '\t' self.clip.setText(s) def update_measurement_list(self): self.measurement_type.blockSignals(True) available = list(sorted(self.settings.measurement_profiles.keys())) text = self.measurement_type.currentText() try: index = available.index(text) + 1 except ValueError: index = 0 self.measurement_type.clear() self.measurement_type.addItem("<none>") self.measurement_type.addItems(available) self.measurement_type.setCurrentIndex(index) self.measurement_type.blockSignals(False) def showEvent(self, _): self.update_measurement_list() def event(self, event: QEvent): if event.type() == QEvent.WindowActivate: self.update_measurement_list() return super().event(event) @staticmethod def _move_widgets(widgets_list: List[Tuple[QWidget, int]], layout1: QBoxLayout, layout2: QBoxLayout): for el in widgets_list: layout1.removeWidget(el[0]) layout2.addWidget(el[0], el[1]) def resizeEvent(self, _event: QResizeEvent) -> None: if self.width() < 800 and self.butt_layout2.count() == 0: self._move_widgets( [(self.file_names_label, 1), (self.file_names, 1), (self.copy_button, 2)], self.butt_layout, self.butt_layout2, ) elif self.width() > 800 and self.butt_layout2.count() != 0: self._move_widgets( [(self.file_names_label, 1), (self.file_names, 1), (self.copy_button, 2)], self.butt_layout2, self.butt_layout, )
class ReactionMask(QWidget): """The input mask for a reaction""" def __init__(self, parent: ReactionList): QWidget.__init__(self) self.parent = parent self.reaction = None self.is_valid = True self.changed = False self.setAcceptDrops(False) layout = QVBoxLayout() l = QHBoxLayout() self.delete_button = QPushButton("Delete reaction") self.delete_button.setIcon(QIcon.fromTheme("edit-delete")) policy = QSizePolicy() policy.ShrinkFlag = True self.delete_button.setSizePolicy(policy) l.addWidget(self.delete_button) layout.addItem(l) l = QHBoxLayout() label = QLabel("Id:") self.id = QLineEdit() l.addWidget(label) l.addWidget(self.id) layout.addItem(l) l = QHBoxLayout() label = QLabel("Name:") self.name = QLineEdit() l.addWidget(label) l.addWidget(self.name) layout.addItem(l) l = QHBoxLayout() label = QLabel("Equation:") self.equation = QLineEdit() l.addWidget(label) l.addWidget(self.equation) layout.addItem(l) l = QHBoxLayout() label = QLabel("Rate min:") self.lower_bound = QLineEdit() l.addWidget(label) l.addWidget(self.lower_bound) layout.addItem(l) l = QHBoxLayout() label = QLabel("Rate max:") self.upper_bound = QLineEdit() l.addWidget(label) l.addWidget(self.upper_bound) layout.addItem(l) l = QHBoxLayout() label = QLabel("Coefficient in obj. function:") self.coefficent = QLineEdit() l.addWidget(label) l.addWidget(self.coefficent) layout.addItem(l) l = QHBoxLayout() label = QLabel("Gene reaction rule:") self.gene_reaction_rule = QLineEdit() l.addWidget(label) l.addWidget(self.gene_reaction_rule) layout.addItem(l) l = QVBoxLayout() label = QLabel("Annotations:") l.addWidget(label) l2 = QHBoxLayout() self.annotation = QTableWidget(0, 2) self.annotation.setHorizontalHeaderLabels( ["key", "value"]) self.annotation.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) l2.addWidget(self.annotation) self.add_anno = QPushButton("+") self.add_anno.clicked.connect(self.add_anno_row) l2.addWidget(self.add_anno) l.addItem(l2) layout.addItem(l) l = QVBoxLayout() label = QLabel("Metabolites involved in this reaction:") l.addWidget(label) l2 = QHBoxLayout() self.metabolites = QTreeWidget() self.metabolites.setHeaderLabels(["Id"]) self.metabolites.setSortingEnabled(True) l2.addWidget(self.metabolites) l.addItem(l2) self.metabolites.itemDoubleClicked.connect( self.emit_jump_to_metabolite) layout.addItem(l) self.jump_list = JumpList(self) layout.addWidget(self.jump_list) self.setLayout(layout) self.delete_button.clicked.connect(self.delete_reaction) self.throttler = SignalThrottler(500) self.throttler.triggered.connect(self.reaction_data_changed) self.id.textEdited.connect(self.throttler.throttle) self.name.textEdited.connect(self.throttler.throttle) self.equation.textEdited.connect(self.throttler.throttle) self.lower_bound.textEdited.connect(self.throttler.throttle) self.upper_bound.textEdited.connect(self.throttler.throttle) self.coefficent.textEdited.connect(self.throttler.throttle) self.gene_reaction_rule.textEdited.connect(self.throttler.throttle) self.annotation.itemChanged.connect(self.throttler.throttle) self.validate_mask() def add_anno_row(self): i = self.annotation.rowCount() self.annotation.insertRow(i) self.changed = True def apply(self): try: self.reaction.id = self.id.text() except ValueError: turn_red(self.id) QMessageBox.information( self, 'Invalid id', 'Could not apply changes identifier ' + self.id.text() + ' already used.') else: self.reaction.name = self.name.text() self.reaction.build_reaction_from_string(self.equation.text()) self.reaction.lower_bound = float(self.lower_bound.text()) self.reaction.upper_bound = float(self.upper_bound.text()) self.reaction.objective_coefficient = float(self.coefficent.text()) self.reaction.gene_reaction_rule = self.gene_reaction_rule.text() self.reaction.annotation = {} rows = self.annotation.rowCount() for i in range(0, rows): key = self.annotation.item(i, 0).text() if self.annotation.item(i, 1) is None: value = "" else: value = self.annotation.item(i, 1).text() self.reaction.annotation[key] = value self.changed = False self.reactionChanged.emit(self.reaction) def delete_reaction(self): self.hide() self.reactionDeleted.emit(self.reaction) def validate_id(self): with self.parent.appdata.project.cobra_py_model as model: try: r = cobra.Reaction(id=self.id.text()) model.add_reaction(r) except ValueError: turn_red(self.id) return False else: turn_white(self.id) return True def validate_name(self): with self.parent.appdata.project.cobra_py_model as model: try: r = cobra.Reaction(id="testid", name=self.name.text()) model.add_reaction(r) except ValueError: turn_red(self.name) return False else: turn_white(self.name) return True def validate_equation(self): ok = False test_reaction = cobra.Reaction( "xxxx_cnapy_test_reaction", name="cnapy test reaction") with self.parent.appdata.project.cobra_py_model as model: model.add_reaction(test_reaction) try: eqtxt = self.equation.text().rstrip() if len(eqtxt) > 0 and eqtxt[-1] == '+': turn_red(self.equation) else: test_reaction.build_reaction_from_string(eqtxt) turn_white(self.equation) ok = True except ValueError: turn_red(self.equation) try: test_reaction = self.parent.appdata.project.cobra_py_model.reactions.get_by_id( "xxxx_cnapy_test_reaction") self.parent.appdata.project.cobra_py_model.remove_reactions( [test_reaction], remove_orphans=True) except KeyError: pass return ok def validate_lowerbound(self): try: _x = float(self.lower_bound.text()) except ValueError: turn_red(self.lower_bound) return False else: turn_white(self.lower_bound) return True def validate_upperbound(self): try: _x = float(self.upper_bound.text()) except ValueError: turn_red(self.upper_bound) return False else: turn_white(self.upper_bound) return True def validate_coefficient(self): try: _x = float(self.coefficent.text()) except ValueError: turn_red(self.coefficent) return False else: turn_white(self.coefficent) return True def validate_gene_reaction_rule(self): try: _x = float(self.gene_reaction_rule.text()) except ValueError: turn_red(self.gene_reaction_rule) return False else: turn_white(self.gene_reaction_rule) return True def validate_mask(self): valid_id = self.validate_id() valid_name = self.validate_name() valid_equation = self.validate_equation() valid_lb = self.validate_lowerbound() valid_ub = self.validate_upperbound() valid_coefficient = self.validate_coefficient() if valid_id & valid_name & valid_equation & valid_lb & valid_ub & valid_coefficient: self.is_valid = True else: self.is_valid = False def reaction_data_changed(self): self.changed = True self.validate_mask() if self.is_valid: self.apply() self.update_state() def update_state(self): self.jump_list.clear() for name, m in self.parent.appdata.project.maps.items(): if self.id.text() in m["boxes"]: self.jump_list.add(name) self.metabolites.clear() if self.parent.appdata.project.cobra_py_model.reactions.has_id(self.id.text()): reaction = self.parent.appdata.project.cobra_py_model.reactions.get_by_id( self.id.text()) for m in reaction.metabolites: item = QTreeWidgetItem(self.metabolites) item.setText(0, m.id) item.setText(1, m.name) item.setData(2, 0, m) text = "Id: " + m.id + "\nName: " + m.name item.setToolTip(1, text) def emit_jump_to_map(self, name): self.jumpToMap.emit(name, self.id.text()) def emit_jump_to_metabolite(self, metabolite): self.jumpToMetabolite.emit(str(metabolite.data(2, 0))) jumpToMap = Signal(str, str) jumpToMetabolite = Signal(str) reactionChanged = Signal(cobra.Reaction) reactionDeleted = Signal(cobra.Reaction)
class MetabolitesMask(QWidget): """The input mask for a metabolites""" def __init__(self, appdata): QWidget.__init__(self) self.appdata = appdata self.metabolite = None self.is_valid = True self.changed = False self.setAcceptDrops(False) layout = QVBoxLayout() l = QHBoxLayout() label = QLabel("Id:") self.id = QLineEdit() l.addWidget(label) l.addWidget(self.id) layout.addItem(l) l = QHBoxLayout() label = QLabel("Name:") self.name = QLineEdit() l.addWidget(label) l.addWidget(self.name) layout.addItem(l) l = QHBoxLayout() label = QLabel("Formula:") self.formula = QLineEdit() l.addWidget(label) l.addWidget(self.formula) layout.addItem(l) l = QHBoxLayout() label = QLabel("Charge:") self.charge = QLineEdit() l.addWidget(label) l.addWidget(self.charge) layout.addItem(l) l = QHBoxLayout() label = QLabel("Compartment:") self.compartment = QLineEdit() l.addWidget(label) l.addWidget(self.compartment) layout.addItem(l) l = QVBoxLayout() label = QLabel("Annotations:") l.addWidget(label) l2 = QHBoxLayout() self.annotation = QTableWidget(0, 2) self.annotation.setHorizontalHeaderLabels( ["key", "value"]) self.annotation.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) l2.addWidget(self.annotation) self.add_anno = QPushButton("+") self.add_anno.clicked.connect(self.add_anno_row) l2.addWidget(self.add_anno) l.addItem(l2) layout.addItem(l) l = QVBoxLayout() label = QLabel("Reactions using this metabolite:") l.addWidget(label) l2 = QHBoxLayout() self.reactions = QTreeWidget() self.reactions.setHeaderLabels(["Id"]) self.reactions.setSortingEnabled(True) l2.addWidget(self.reactions) l.addItem(l2) self.reactions.itemDoubleClicked.connect(self.emit_jump_to_reaction) layout.addItem(l) self.setLayout(layout) self.throttler = SignalThrottler(500) self.throttler.triggered.connect(self.metabolites_data_changed) self.id.textEdited.connect(self.throttler.throttle) self.name.textEdited.connect(self.throttler.throttle) self.formula.textEdited.connect(self.throttler.throttle) self.charge.textEdited.connect(self.throttler.throttle) self.compartment.textEdited.connect(self.throttler.throttle) self.annotation.itemChanged.connect(self.throttler.throttle) self.validate_mask() def add_anno_row(self): i = self.annotation.rowCount() self.annotation.insertRow(i) self.changed = True def apply(self): try: self.metabolite.id = self.id.text() except ValueError: turn_red(self.id) QMessageBox.information( self, 'Invalid id', 'Could not apply changes identifier ' + self.id.text()+' already used.') else: self.metabolite.name = self.name.text() self.metabolite.formula = self.formula.text() if self.charge.text() == "": self.metabolite.charge = None else: self.metabolite.charge = int(self.charge.text()) self.metabolite.compartment = self.compartment.text() self.metabolite.annotation = {} rows = self.annotation.rowCount() for i in range(0, rows): key = self.annotation.item(i, 0).text() if self.annotation.item(i, 1) is None: value = "" else: value = self.annotation.item(i, 1).text() self.metabolite.annotation[key] = value self.changed = False self.metaboliteChanged.emit(self.metabolite) def validate_id(self): with self.appdata.project.cobra_py_model as model: text = self.id.text() if text == "": turn_red(self.id) return False if ' ' in text: turn_red(self.id) return False try: m = cobra.Metabolite(id=self.id.text()) model.add_metabolites([m]) except ValueError: turn_red(self.id) return False else: turn_white(self.id) return True def validate_name(self): with self.appdata.project.cobra_py_model as model: try: m = cobra.Metabolite(id="test_id", name=self.name.text()) model.add_metabolites([m]) except ValueError: turn_red(self.name) return False else: turn_white(self.name) return True def validate_formula(self): return True def validate_charge(self): try: if self.charge.text() != "": _x = int(self.charge.text()) except ValueError: turn_red(self.charge) return False else: turn_white(self.charge) return True def validate_compartment(self): try: if ' ' in self.compartment.text(): turn_red(self.compartment) return False if '-' in self.compartment.text(): turn_red(self.compartment) return False _m = cobra.Metabolite(id="test_id", name=self.compartment.text()) except ValueError: turn_red(self.compartment) return False else: turn_white(self.compartment) return True def validate_mask(self): valid_id = self.validate_id() valid_name = self.validate_name() valid_formula = self.validate_formula() valid_charge = self.validate_charge() valid_compartment = self.validate_compartment() if valid_id & valid_name & valid_formula & valid_charge & valid_compartment: self.is_valid = True else: self.is_valid = False def metabolites_data_changed(self): self.changed = True self.validate_mask() if self.is_valid: self.apply() self.update_state() def update_state(self): self.reactions.clear() if self.appdata.project.cobra_py_model.metabolites.has_id(self.id.text()): metabolite = self.appdata.project.cobra_py_model.metabolites.get_by_id( self.id.text()) for r in metabolite.reactions: item = QTreeWidgetItem(self.reactions) item.setText(0, r.id) item.setText(1, r.name) item.setData(2, 0, r) text = "Id: " + r.id + "\nName: " + r.name item.setToolTip(1, text) def emit_jump_to_reaction(self, reaction): self.jumpToReaction.emit(reaction.data(2, 0).id) jumpToReaction = Signal(str) metaboliteChanged = Signal(cobra.Metabolite)
class LevelsPresetDialog(QDialog): # name of the current preset; whether to set this preset as default; dict of Levels levels_changed = Signal(str, bool, dict) def __init__(self, parent, preset_name, levels): super().__init__(parent) self.preset_name = preset_name self.levels = deepcopy(levels) self.setupUi() self.update_output() def setupUi(self): self.resize(480, 340) self.vbox = QVBoxLayout(self) self.presetLabel = QLabel(self) self.table = QTableWidget(0, 4, self) self.setAsDefaultCheckbox = QCheckBox("Set as default preset", self) self.vbox.addWidget(self.presetLabel) self.vbox.addWidget(self.table) self.vbox.addWidget(self.setAsDefaultCheckbox) self.table.setEditTriggers(QTableWidget.NoEditTriggers) self.table.setSelectionBehavior(QTableWidget.SelectRows) self.table.setHorizontalHeaderLabels( ["Show", "Level name", "Preview", "Preview (dark)"]) self.table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) self.table.horizontalHeader().setSectionsClickable(False) self.table.horizontalHeader().setSectionsMovable(False) self.table.horizontalHeader().setSectionResizeMode( 0, QHeaderView.ResizeToContents) self.table.verticalHeader().setVisible(False) self.table.doubleClicked.connect(self.open_level_edit_dialog) self.table.setContextMenuPolicy(Qt.CustomContextMenu) self.table.customContextMenuRequested.connect(self.open_menu) buttons = QDialogButtonBox.Reset | QDialogButtonBox.Save | QDialogButtonBox.Cancel self.buttonBox = QDialogButtonBox(buttons, self) self.vbox.addWidget(self.buttonBox) self.buttonBox.accepted.connect(self.accept) self.buttonBox.rejected.connect(self.reject) self.resetButton = self.buttonBox.button(QDialogButtonBox.Reset) self.resetButton.clicked.connect(self.reset) def update_output(self): self.presetLabel.setText("Preset: {}".format(self.preset_name)) self.setAsDefaultCheckbox.setChecked( CONFIG['default_levels_preset'] == self.preset_name) self.table.clearContents() self.table.setRowCount(len(self.levels)) for i, levelname in enumerate(self.levels): level = self.levels[levelname] checkbox = self.get_level_show_checkbox(level) nameItem = QTableWidgetItem(level.levelname) preview, previewDark = self.get_preview_items(level) self.table.setCellWidget(i, 0, checkbox) self.table.setItem(i, 1, nameItem) self.table.setItem(i, 2, preview) self.table.setItem(i, 3, previewDark) def get_level_show_checkbox(self, level): checkbox_widget = QWidget(self.table) checkbox_widget.setStyleSheet("QWidget { background-color:none;}") checkbox = QCheckBox() checkbox.setStyleSheet( "QCheckBox::indicator { width: 15px; height: 15px;}") checkbox.setChecked(level.enabled) checkbox_layout = QHBoxLayout() checkbox_layout.setAlignment(Qt.AlignCenter) checkbox_layout.setContentsMargins(0, 0, 0, 0) checkbox_layout.addWidget(checkbox) checkbox_widget.setLayout(checkbox_layout) return checkbox_widget def get_preview_items(self, level): previewItem = QTableWidgetItem("Log message") previewItem.setBackground(QBrush(level.bg, Qt.SolidPattern)) previewItem.setForeground(QBrush(level.fg, Qt.SolidPattern)) previewItemDark = QTableWidgetItem("Log message") previewItemDark.setBackground(QBrush(level.bgDark, Qt.SolidPattern)) previewItemDark.setForeground(QBrush(level.fgDark, Qt.SolidPattern)) font = QFont(CONFIG.logger_table_font, CONFIG.logger_table_font_size) fontDark = QFont(font) if 'bold' in level.styles: font.setBold(True) if 'italic' in level.styles: font.setItalic(True) if 'underline' in level.styles: font.setUnderline(True) if 'bold' in level.stylesDark: fontDark.setBold(True) if 'italic' in level.stylesDark: fontDark.setItalic(True) if 'underline' in level.stylesDark: fontDark.setUnderline(True) previewItem.setFont(font) previewItemDark.setFont(fontDark) return previewItem, previewItemDark def open_level_edit_dialog(self, index): levelname = self.table.item(index.row(), 1).data(Qt.DisplayRole) level = self.levels[levelname] d = LevelEditDialog(self, level) d.setWindowModality(Qt.NonModal) d.setWindowTitle('Level editor') d.level_changed.connect(self.update_output) d.open() def open_menu(self, position): menu = QMenu(self) preset_menu = menu.addMenu('Presets') preset_menu.addAction('New preset', self.new_preset_dialog) preset_menu.addSeparator() preset_names = CONFIG.get_levels_presets() if len(preset_names) == 0: action = preset_menu.addAction('No presets') action.setEnabled(False) else: delete_menu = menu.addMenu('Delete preset') for name in preset_names: preset_menu.addAction(name, partial(self.load_preset, name)) delete_menu.addAction(name, partial(self.delete_preset, name)) menu.addSeparator() menu.addAction('New level...', self.create_new_level_dialog) if len(self.table.selectedIndexes()) > 0: menu.addAction('Delete selected', self.delete_selected) menu.popup(self.table.viewport().mapToGlobal(position)) def load_preset(self, name): new_levels = CONFIG.load_levels_preset(name) if not new_levels: return self.levels = new_levels self.preset_name = name self.update_output() def delete_preset(self, name): CONFIG.delete_levels_preset(name) if name == self.preset_name: self.reset() def delete_selected(self): selected = self.table.selectionModel().selectedRows() for index in selected: item = self.table.item(index.row(), 1) del self.levels[item.text()] self.update_output() def new_preset_dialog(self): d = QInputDialog(self) d.setLabelText('Enter the new name for the new preset:') d.setWindowTitle('Create new preset') d.textValueSelected.connect(self.create_new_preset) d.open() def create_new_preset(self, name): if name in CONFIG.get_levels_presets(): show_warning_dialog( self, "Preset creation error", 'Preset named "{}" already exists.'.format(name)) return if len(name.strip()) == 0: show_warning_dialog( self, "Preset creation error", 'This preset name is not allowed.'.format(name)) return self.preset_name = name self.update_output() CONFIG.save_levels_preset(name, self.levels) def create_new_level_dialog(self): d = LevelEditDialog(self, creating_new_level=True, level_names=self.levels.keys()) d.setWindowModality(Qt.NonModal) d.setWindowTitle('Level editor') d.level_changed.connect(self.level_changed) d.open() def level_changed(self, level): if level.levelname in self.levels: self.levels.copy_from(level) else: self.levels[level.levelname] = level self.update_output() def accept(self): for i, _ in enumerate(self.levels): checkbox = self.table.cellWidget(i, 0).children()[1] levelname = self.table.item(i, 1).text() self.levels[levelname].enabled = checkbox.isChecked() self.levels_changed.emit(self.preset_name, self.setAsDefaultCheckbox.isChecked(), self.levels) self.done(0) def reject(self): self.done(0) def reset(self): for levelname, level in self.levels.items(): level.copy_from(get_default_level(levelname)) self.update_output()
class OpticsAdjustSettings(SiriusDialog): """Auxiliar window to optics adjust settings.""" updateSettings = Signal(str, str) def __init__(self, tuneconfig_currname, chromconfig_currname, parent=None): """Initialize object.""" super().__init__(parent) self.setWindowTitle('Optics Adjust Settings') self.setObjectName('BOApp') self.tuneconfig_currname = tuneconfig_currname self.chromconfig_currname = chromconfig_currname self.conn_tuneparams = _ConfigDBClient( config_type='bo_tunecorr_params') self.conn_chromparams = _ConfigDBClient( config_type='bo_chromcorr_params') self._setupUi() def _setupUi(self): self.tune_settings = QWidget(self) self.tune_settings.setLayout(self._setupTuneSettings()) self.le_tuneconfig.setText(self.tuneconfig_currname) self.chrom_settings = QWidget(self) self.chrom_settings.setLayout(self._setupChromSettings()) self.le_chromconfig.setText(self.chromconfig_currname) self.bt_apply = QPushButton('Apply Settings', self) self.bt_apply.setStyleSheet("""min-width:8em; max-width:8em;""") self.bt_apply.clicked.connect(self._emitSettings) self.bt_apply.setAutoDefault(False) self.bt_apply.setDefault(False) hlay_apply = QHBoxLayout() hlay_apply.addItem( QSpacerItem(20, 60, QSzPlcy.Expanding, QSzPlcy.Ignored)) hlay_apply.addWidget(self.bt_apply) tabs = QTabWidget(self) tabs.addTab(self.tune_settings, 'Tune') tabs.addTab(self.chrom_settings, 'Chromaticity') lay = QVBoxLayout() lay.addWidget(tabs) lay.addLayout(hlay_apply) self.setLayout(lay) def _setupTuneSettings(self): l_tuneconfig = QLabel('<h3>Tune Variation Config</h3>', self) l_tuneconfig.setAlignment(Qt.AlignCenter) self.le_tuneconfig = _ConfigLineEdit(parent=self, config_type='bo_tunecorr_params') self.le_tuneconfig.textChanged.connect(self._showTuneConfigData) label_tunemat = QLabel('<h4>Matrix</h4>', self) label_tunemat.setAlignment(Qt.AlignCenter) self.table_tunemat = QTableWidget(self) self.table_tunemat.setObjectName('tunemat') self.table_tunemat.setStyleSheet(""" #tunemat{ background-color: #efebe7; min-width: 22.14em; min-height: 6em; max-height: 6em;}""") self.table_tunemat.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.table_tunemat.setEditTriggers(QAbstractItemView.NoEditTriggers) self.table_tunemat.setRowCount(2) self.table_tunemat.setColumnCount(2) self.table_tunemat.setVerticalHeaderLabels([' X', ' Y']) self.table_tunemat.setHorizontalHeaderLabels(['QF', 'QD']) self.table_tunemat.horizontalHeader().setStyleSheet(""" min-height:1.55em; max-height:1.55em;""") self.table_tunemat.verticalHeader().setStyleSheet(""" min-width:1.55em; max-width:1.55em;""") self.table_tunemat.horizontalHeader().setSectionResizeMode( QHeaderView.Stretch) self.table_tunemat.verticalHeader().setSectionResizeMode( QHeaderView.Stretch) self.table_tunemat.setSizePolicy(QSzPlcy.MinimumExpanding, QSzPlcy.Preferred) label_nomKL = QLabel('<h4>Nominal KL</h4>') label_nomKL.setAlignment(Qt.AlignCenter) self.table_nomKL = QTableWidget(self) self.table_nomKL.setObjectName('nomKL') self.table_nomKL.setStyleSheet(""" #nomKL{ background-color: #efebe7; min-width: 22.14em; min-height: 4em; max-height: 4em;}""") self.table_nomKL.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.table_nomKL.setEditTriggers(QAbstractItemView.NoEditTriggers) self.table_nomKL.setRowCount(1) self.table_nomKL.setColumnCount(2) self.table_nomKL.setVerticalHeaderLabels(['KL']) self.table_nomKL.setHorizontalHeaderLabels(['QF', 'QD']) self.table_nomKL.horizontalHeader().setStyleSheet(""" min-height:1.55em; max-height:1.55em;""") self.table_nomKL.verticalHeader().setStyleSheet(""" min-width:1.55em; max-width:1.55em;""") self.table_nomKL.horizontalHeader().setSectionResizeMode( QHeaderView.Stretch) self.table_nomKL.verticalHeader().setSectionResizeMode( QHeaderView.Stretch) self.table_nomKL.setSizePolicy(QSzPlcy.MinimumExpanding, QSzPlcy.Preferred) lay = QVBoxLayout() lay.addWidget(l_tuneconfig) lay.addWidget(self.le_tuneconfig) lay.addItem(QSpacerItem(20, 10, QSzPlcy.Ignored, QSzPlcy.Expanding)) lay.addWidget(label_tunemat) lay.addWidget(self.table_tunemat) lay.addItem(QSpacerItem(20, 10, QSzPlcy.Ignored, QSzPlcy.Expanding)) lay.addWidget(label_nomKL) lay.addWidget(self.table_nomKL) lay.addItem(QSpacerItem(20, 10, QSzPlcy.Ignored, QSzPlcy.Expanding)) return lay def _setupChromSettings(self): l_chromconfig = QLabel('<h3>Chromaticity Variation Config</h3>', self) l_chromconfig.setAlignment(Qt.AlignCenter) self.le_chromconfig = _ConfigLineEdit( parent=self, config_type='bo_chromcorr_params') self.le_chromconfig.textChanged.connect(self._showChromConfigData) l_chrommat = QLabel('<h4>Matrix</h4>', self) l_chrommat.setAlignment(Qt.AlignCenter) self.table_chrommat = QTableWidget(self) self.table_chrommat.setObjectName('chrommat') self.table_chrommat.setStyleSheet(""" #chrommat{ background-color: #efebe7; min-width: 22.14em; min-height: 6em; max-height: 6em;}""") self.table_chrommat.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.table_chrommat.setEditTriggers(QAbstractItemView.NoEditTriggers) self.table_chrommat.setRowCount(2) self.table_chrommat.setColumnCount(2) self.table_chrommat.setVerticalHeaderLabels([' X', ' Y']) self.table_chrommat.setHorizontalHeaderLabels(['SF', 'SD']) self.table_chrommat.horizontalHeader().setStyleSheet(""" min-height:1.55em; max-height:1.55em;""") self.table_chrommat.verticalHeader().setStyleSheet(""" min-width:1.55em; max-width:1.55em;""") self.table_chrommat.horizontalHeader().setSectionResizeMode( QHeaderView.Stretch) self.table_chrommat.verticalHeader().setSectionResizeMode( QHeaderView.Stretch) self.table_chrommat.setSizePolicy(QSzPlcy.MinimumExpanding, QSzPlcy.Preferred) l_nomSL = QLabel('<h4>Nominal SL</h4>') l_nomSL.setAlignment(Qt.AlignCenter) self.table_nomSL = QTableWidget(self) self.table_nomSL.setObjectName('nomSL') self.table_nomSL.setStyleSheet(""" #nomSL{ background-color: #efebe7; min-width: 22.14em; min-height: 4em; max-height: 4em;}""") self.table_nomSL.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.table_nomSL.setEditTriggers(QAbstractItemView.NoEditTriggers) self.table_nomSL.setRowCount(1) self.table_nomSL.setColumnCount(2) self.table_nomSL.setVerticalHeaderLabels(['SL']) self.table_nomSL.setHorizontalHeaderLabels(['SF', 'SD']) self.table_nomSL.horizontalHeader().setStyleSheet(""" min-height:1.55em; max-height:1.55em;""") self.table_nomSL.verticalHeader().setStyleSheet(""" min-width:1.55em; max-width:1.55em;""") self.table_nomSL.horizontalHeader().setSectionResizeMode( QHeaderView.Stretch) self.table_nomSL.verticalHeader().setSectionResizeMode( QHeaderView.Stretch) self.table_nomSL.setSizePolicy(QSzPlcy.MinimumExpanding, QSzPlcy.Preferred) l_nomchrom = QLabel('<h4>Nominal Chrom</h4>') l_nomchrom.setAlignment(Qt.AlignCenter) self.label_nomchrom = QLabel() self.label_nomchrom.setAlignment(Qt.AlignCenter) lay = QVBoxLayout() lay.addWidget(l_chromconfig) lay.addWidget(self.le_chromconfig) lay.addItem(QSpacerItem(20, 10, QSzPlcy.Expanding, QSzPlcy.Expanding)) lay.addWidget(l_chrommat) lay.addWidget(self.table_chrommat) lay.addItem(QSpacerItem(20, 10, QSzPlcy.Expanding, QSzPlcy.Expanding)) lay.addWidget(l_nomSL) lay.addWidget(self.table_nomSL) lay.addItem(QSpacerItem(20, 10, QSzPlcy.Expanding, QSzPlcy.Expanding)) lay.addWidget(l_nomchrom) lay.addWidget(self.label_nomchrom) return lay def _showTuneConfigData(self): try: name = self.le_tuneconfig.text() config = self.conn_tuneparams.get_config_value(name=name) mat = config['matrix'] nomKL = config['nominal KLs'] except _ConfigDBException as err: QMessageBox.critical(self, 'Error', str(err), QMessageBox.Ok) else: self.tuneconfig_currname = name self.table_tunemat.setItem(0, 0, QTableWidgetItem(str(mat[0][0]))) self.table_tunemat.setItem(0, 1, QTableWidgetItem(str(mat[0][1]))) self.table_tunemat.setItem(1, 0, QTableWidgetItem(str(mat[1][0]))) self.table_tunemat.setItem(1, 1, QTableWidgetItem(str(mat[1][1]))) self.table_tunemat.item(0, 0).setFlags(Qt.ItemIsEnabled) self.table_tunemat.item(0, 1).setFlags(Qt.ItemIsEnabled) self.table_tunemat.item(1, 0).setFlags(Qt.ItemIsEnabled) self.table_tunemat.item(1, 1).setFlags(Qt.ItemIsEnabled) self.table_nomKL.setItem(0, 0, QTableWidgetItem(str(nomKL[0]))) self.table_nomKL.setItem(0, 1, QTableWidgetItem(str(nomKL[1]))) self.table_nomKL.item(0, 0).setFlags(Qt.ItemIsEnabled) self.table_nomKL.item(0, 1).setFlags(Qt.ItemIsEnabled) def _showChromConfigData(self): try: name = self.le_chromconfig.text() config = self.conn_chromparams.get_config_value(name=name) mat = config['matrix'] nomSL = config['nominal SLs'] nomChrom = config['nominal chrom'] except _ConfigDBException as err: QMessageBox.critical(self, 'Error', str(err), QMessageBox.Ok) else: self.chromconfig_currname = name self.table_chrommat.setItem(0, 0, QTableWidgetItem(str(mat[0][0]))) self.table_chrommat.setItem(0, 1, QTableWidgetItem(str(mat[0][1]))) self.table_chrommat.setItem(1, 0, QTableWidgetItem(str(mat[1][0]))) self.table_chrommat.setItem(1, 1, QTableWidgetItem(str(mat[1][1]))) self.table_chrommat.item(0, 0).setFlags(Qt.ItemIsEnabled) self.table_chrommat.item(0, 1).setFlags(Qt.ItemIsEnabled) self.table_chrommat.item(1, 0).setFlags(Qt.ItemIsEnabled) self.table_chrommat.item(1, 1).setFlags(Qt.ItemIsEnabled) self.table_nomSL.setItem(0, 0, QTableWidgetItem(str(nomSL[0]))) self.table_nomSL.setItem(0, 1, QTableWidgetItem(str(nomSL[1]))) self.table_nomSL.item(0, 0).setFlags(Qt.ItemIsEnabled) self.table_nomSL.item(0, 1).setFlags(Qt.ItemIsEnabled) self.label_nomchrom.setText(str(nomChrom)) def _emitSettings(self): tuneconfig_name = self.le_tuneconfig.text() chromconfig_name = self.le_chromconfig.text() self.updateSettings.emit(tuneconfig_name, chromconfig_name) self.close()
class PMGRuleCtrl(BaseExtendedWidget): """ rules: {'name':'regex', 'text':'匹配正则表达式', 'init':False } """ def __init__(self, layout_dir='v', title='', rules: List[Dict[str, Union[bool, int, float, str]]] = None): super().__init__(layout_dir) self.table_h_headers = [] self.table_keys = [] self.initial_values = [] for rule in rules: self.table_h_headers.append(rule['text']) self.table_keys.append(rule['name']) self.initial_values.append(rule['init']) self.regulations_table = QTableWidget(0, len(self.table_h_headers)) self.regulations_table.horizontalHeader().setSectionResizeMode( QHeaderView.Stretch) self.regulations_table.setHorizontalHeaderLabels(self.table_h_headers) self.layout().addWidget(self.regulations_table) self.set_layout = QHBoxLayout() self.layout().addLayout(self.set_layout) self.button_add = QPushButton('Add') self.button_remove = QPushButton('Remove') self.set_layout.addWidget(self.button_add) self.set_layout.addWidget(self.button_remove) self.button_add.clicked.connect(self.add_regulation) self.button_remove.clicked.connect(self.remove_regulation) def load_regulations(self, regulations: List[Dict[str, Union[int, str, float, bool]]]): row_count = len(regulations) self.regulations_table.setRowCount(row_count) for i, regulation in enumerate(regulations): l = [regulation[k] for k in self.table_keys] for j, obj in enumerate(l): item = QTableWidgetItem() item.setData(0, obj) self.regulations_table.setItem(i, j, item) def add_regulation(self): rc = self.regulations_table.rowCount() self.regulations_table.setRowCount(rc + 1) for i, obj in enumerate(self.initial_values): item = QTableWidgetItem() item.setData(0, obj) self.regulations_table.setItem(rc, i, item) def remove_regulation(self): self.regulations_table.removeRow(self.regulations_table.currentRow()) def get_value(self) -> List[Dict]: l = [] for i in range(self.regulations_table.rowCount()): dic = {} for j in range(self.regulations_table.columnCount()): item = self.regulations_table.item(i, j) dic[self.table_keys[j]] = item.data(0) l.append(dic) return l def set_value(self, value): self.load_regulations(value)
class AddDataTableMixin(object): """ Mixin providing validation and type casting for a table for adding cycle data. Should be used as part of a widget that has a `table` attribute. If it also has a `okButton`, this will be enabled/diabled when validation is performed. """ invalid = Signal(int, int) """ **signal** invalid(int `row`, int `col`) Emitted if the data in cell `row`,`col` is invalid. """ def __init__(self, *args, emptyDateValid=True, **kwargs): super().__init__(*args, **kwargs) self.headerLabels = ['Date', 'Time', 'Distance (km)', 'Calories', 'Gear'] self.headerLabelColumnOffset = 0 self.table = QTableWidget(0, len(self.headerLabels)) self.table.setHorizontalHeaderLabels(self.headerLabels) self.table.verticalHeader().setVisible(False) # dict of methods to validate and cast types for input data in each column isDatePartial = partial(isDate, allowEmpty=emptyDateValid) validateMethods = [isDatePartial, isDuration, isFloat, isFloat, isInt] parseDatePartial = partial(parseDate, pd_timestamp=True) castMethods = [parseDatePartial, parseDuration, float, float, int] self.mthds = {name:{'validate':validateMethods[i], 'cast':castMethods[i]} for i, name in enumerate(self.headerLabels)} self.validateTimer = QTimer() self.validateTimer.setInterval(100) self.validateTimer.setSingleShot(True) self.validateTimer.timeout.connect(self._validate) self.table.cellChanged.connect(self.validateTimer.start) self.invalid.connect(self._invalid) @property def defaultBrush(self): # made this a property rather than setting in constructor as the mixin # doesn't have a `table` attribute when __init__ is called return self.table.item(0,0).background() @property def invalidBrush(self): return QBrush(QColor("#910404")) @Slot(int, int) def _invalid(self, row, col): """ Set the background of cell `row`,`col` to the `invalidBrush` and disable the 'Ok' button. """ self.table.item(row, col).setBackground(self.invalidBrush) if hasattr(self, "okButton"): self.okButton.setEnabled(False) @Slot() def _validate(self): """ Validate all data in the table. If data is valid and the widget has an `okButton`, it will be enabled. """ # it would be more efficient to only validate a single cell, after its # text has been changed, but this table is unlikely to ever be more # than a few rows long, so this isn't too inefficient allValid = True for row in range(self.table.rowCount()): for col, name in enumerate(self.headerLabels): col += self.headerLabelColumnOffset item = self.table.item(row, col) value = item.text() mthd = self.mthds[name]['validate'] valid = mthd(value) if not valid: if hasattr(self, "_clicked"): if (row, col) not in self._clicked: continue self.invalid.emit(row, col) allValid = False elif valid and self.table.item(row, col).background() == self.invalidBrush: self.table.item(row, col).setBackground(self.defaultBrush) if allValid and hasattr(self, "okButton"): self.okButton.setEnabled(True) return allValid def _getValues(self): values = {name:[] for name in self.headerLabels} self.table.sortItems(0, Qt.AscendingOrder) for row in range(self.table.rowCount()): for col, name in enumerate(self.headerLabels): item = self.table.item(row, col) value = item.text() mthd = self.mthds[name]['cast'] value = mthd(value) values[name].append(value) return values
class D0(QGroupBox): def __init__(self, parent=None): self._parent = parent super().__init__(parent) self.setTitle("Define d₀") layout = QVBoxLayout() self.d0_grid_switch = QComboBox() self.d0_grid_switch.addItems(["Constant", "Field"]) self.d0_grid_switch.currentTextChanged.connect(self.set_case) layout.addWidget(self.d0_grid_switch) self.d0_box = QWidget() d0_box_layout = QHBoxLayout() d0_box_layout.addWidget(QLabel("d₀")) validator = QDoubleValidator() validator.setBottom(0) self.d0 = QLineEdit() self.d0.setValidator(validator) self.d0.editingFinished.connect(self.update_d0) d0_box_layout.addWidget(self.d0) d0_box_layout.addWidget(QLabel("Δd₀")) self.d0e = QLineEdit() self.d0e.setValidator(validator) self.d0e.editingFinished.connect(self.update_d0) d0_box_layout.addWidget(self.d0e) self.d0_box.setLayout(d0_box_layout) layout.addWidget(self.d0_box) load_save = QWidget() load_save_layout = QHBoxLayout() self.load_grid = QPushButton("Load d₀ Grid") self.load_grid.clicked.connect(self.load_d0_field) load_save_layout.addWidget(self.load_grid) self.save_grid = QPushButton("Save d₀ Grid") self.save_grid.clicked.connect(self.save_d0_field) load_save_layout.addWidget(self.save_grid) load_save.setLayout(load_save_layout) layout.addWidget(load_save) self.d0_grid = QTableWidget() self.d0_grid.setColumnCount(5) self.d0_grid.setColumnWidth(0, 60) self.d0_grid.setColumnWidth(1, 60) self.d0_grid.setColumnWidth(2, 60) self.d0_grid.setColumnWidth(3, 60) self.d0_grid.verticalHeader().setVisible(False) self.d0_grid.horizontalHeader().setStretchLastSection(True) self.d0_grid.setHorizontalHeaderLabels(['vx', 'vy', 'vz', "d₀", "Δd₀"]) spinBoxDelegate = SpinBoxDelegate() self.d0_grid.setItemDelegateForColumn(3, spinBoxDelegate) self.d0_grid.setItemDelegateForColumn(4, spinBoxDelegate) layout.addWidget(self.d0_grid) self.setLayout(layout) self.set_case('Constant') def set_case(self, case): if case == "Constant": self.d0_box.setEnabled(True) self.load_grid.setEnabled(False) self.save_grid.setEnabled(False) self.d0_grid.setEnabled(False) else: self.d0_box.setEnabled(False) self.load_grid.setEnabled(True) self.save_grid.setEnabled(True) self.d0_grid.setEnabled(True) def update_d0(self): self._parent.update_plot() def set_d0(self, d0, d0e): if d0 is None: self.d0.clear() self.d0e.clear() else: self.d0.setText(str(d0)) self.d0e.setText(str(d0e)) def set_d0_field(self, x, y, z, d0, d0e): if x is None: self.d0_grid.clearContents() else: self.d0_grid.setRowCount(len(x)) for n in range(len(x)): x_item = QTableWidgetItem(f'{x[n]: 7.2f}') x_item.setFlags(x_item.flags() ^ Qt.ItemIsEditable) y_item = QTableWidgetItem(f'{y[n]: 7.2f}') y_item.setFlags(y_item.flags() ^ Qt.ItemIsEditable) z_item = QTableWidgetItem(f'{z[n]: 7.2f}') z_item.setFlags(z_item.flags() ^ Qt.ItemIsEditable) d0_item = QTableWidgetItem() d0_item.setData(Qt.EditRole, float(d0[n])) d0e_item = QTableWidgetItem() d0e_item.setData(Qt.EditRole, float(d0e[n])) self.d0_grid.setItem(n, 0, QTableWidgetItem(x_item)) self.d0_grid.setItem(n, 1, QTableWidgetItem(y_item)) self.d0_grid.setItem(n, 2, QTableWidgetItem(z_item)) self.d0_grid.setItem(n, 3, QTableWidgetItem(d0_item)) self.d0_grid.setItem(n, 4, QTableWidgetItem(d0e_item)) def get_d0_field(self): if self.d0_grid.rowCount() == 0: return None else: x = [ float(self.d0_grid.item(row, 0).text()) for row in range(self.d0_grid.rowCount()) ] y = [ float(self.d0_grid.item(row, 1).text()) for row in range(self.d0_grid.rowCount()) ] z = [ float(self.d0_grid.item(row, 2).text()) for row in range(self.d0_grid.rowCount()) ] d0 = [ float(self.d0_grid.item(row, 3).text()) for row in range(self.d0_grid.rowCount()) ] d0e = [ float(self.d0_grid.item(row, 4).text()) for row in range(self.d0_grid.rowCount()) ] return (d0, d0e, x, y, z) def save_d0_field(self): filename, _ = QFileDialog.getSaveFileName( self, "Save d0 Grid", "", "CSV (*.csv);;All Files (*)") if filename: d0, d0e, x, y, z = self.get_d0_field() np.savetxt(filename, np.array([x, y, z, d0, d0e]).T, fmt=['%.4g', '%.4g', '%.4g', '%.9g', '%.9g'], header="vx, vy, vz, d0, d0_error", delimiter=',') def load_d0_field(self): filename, _ = QFileDialog.getOpenFileName( self, "Load d0 Grid", "", "CSV (*.csv);;All Files (*)") if filename: x, y, z, d0, d0e = np.loadtxt(filename, delimiter=',', unpack=True) self.set_d0_field(x, y, z, d0, d0e) def get_d0(self): if self.d0_grid_switch.currentText() == "Constant": try: return (float(self.d0.text()), float(self.d0e.text())) except ValueError: return None else: return self.get_d0_field()
class Extension2ReaderTable(QWidget): """Table showing extension to reader mappings with removal button. Widget presented in preferences-plugin dialog.""" valueChanged = Signal(int) def __init__(self, parent=None, npe2_readers=None, npe1_readers=None): super().__init__(parent=parent) npe2, npe1 = get_all_readers() if npe2_readers is None: npe2_readers = npe2 if npe1_readers is None: npe1_readers = npe1 self._npe2_readers = npe2_readers self._npe1_readers = npe1_readers self._table = QTableWidget() self._table.setShowGrid(False) self._set_up_table() self._edit_row = self._make_new_preference_row() self._populate_table() instructions = QLabel( trans. _('Enter a filename pattern to associate with a reader e.g. "*.tif" for all TIFF files.' ) + trans. _('Available readers will be filtered to those compatible with your pattern. Hover over a reader to see what patterns it accepts.' ) + trans. _('\n\nPreference saving for folder readers is not supported, so these readers are not shown.' ) + trans. _('\n\nFor documentation on valid filename patterns, see https://docs.python.org/3/library/fnmatch.html' )) instructions.setWordWrap(True) instructions.setOpenExternalLinks(True) layout = QVBoxLayout() instructions.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Expanding) layout.addWidget(instructions) layout.addWidget(self._edit_row) layout.addWidget(self._table) self.setLayout(layout) def _set_up_table(self): """Add table columns and headers, define styling""" self._fn_pattern_col = 0 self._reader_col = 1 header_strs = [trans._('Filename Pattern'), trans._('Reader Plugin')] self._table.setColumnCount(2) self._table.setColumnWidth(self._fn_pattern_col, 200) self._table.setColumnWidth(self._reader_col, 200) self._table.verticalHeader().setVisible(False) self._table.setMinimumHeight(120) self._table.horizontalHeader().setStyleSheet( 'border-bottom: 2px solid white;') self._table.setHorizontalHeaderLabels(header_strs) self._table.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) def _populate_table(self): """Add row for each extension to reader mapping in settings""" fnpattern2reader = get_settings().plugins.extension2reader if len(fnpattern2reader) > 0: for fn_pattern, plugin_name in fnpattern2reader.items(): self._add_new_row(fn_pattern, plugin_name) else: # Display that there are no filename patterns with reader associations self._display_no_preferences_found() def _make_new_preference_row(self): """Make row for user to add a new filename pattern assignment""" edit_row_widget = QWidget() edit_row_widget.setLayout(QGridLayout()) edit_row_widget.layout().setContentsMargins(0, 0, 0, 0) self._fn_pattern_edit = QLineEdit() self._fn_pattern_edit.setPlaceholderText( "Start typing filename pattern...") self._fn_pattern_edit.textChanged.connect( self._filter_compatible_readers) add_reader_widg = QWidget() add_reader_widg.setLayout(QHBoxLayout()) add_reader_widg.layout().setContentsMargins(0, 0, 0, 0) self._new_reader_dropdown = QComboBox() for i, (plugin_name, display_name) in enumerate( sorted(dict(self._npe2_readers, **self._npe1_readers).items())): self._add_reader_choice(i, plugin_name, display_name) add_btn = QPushButton('Add') add_btn.setToolTip(trans._('Save reader preference for pattern')) add_btn.clicked.connect(self._save_new_preference) add_reader_widg.layout().addWidget(self._new_reader_dropdown) add_reader_widg.layout().addWidget(add_btn) edit_row_widget.layout().addWidget( self._fn_pattern_edit, 0, 0, ) edit_row_widget.layout().addWidget(add_reader_widg, 0, 1) return edit_row_widget def _display_no_preferences_found(self): self._table.setRowCount(1) item = QTableWidgetItem(trans._('No filename preferences found.')) item.setFlags(Qt.NoItemFlags) self._table.setItem(self._fn_pattern_col, 0, item) def _add_reader_choice(self, i, plugin_name, display_name): """Add dropdown item for plugin_name with reader pattern tooltip""" reader_patterns = get_filename_patterns_for_reader(plugin_name) # TODO: no reader_patterns means directory reader, # we don't support preference association yet if not reader_patterns: return self._new_reader_dropdown.addItem(display_name, plugin_name) if '*' in reader_patterns: tooltip_text = 'Accepts all' else: reader_patterns_formatted = ', '.join(sorted( list(reader_patterns))) tooltip_text = f'Accepts: {reader_patterns_formatted}' self._new_reader_dropdown.setItemData(i, tooltip_text, role=Qt.ToolTipRole) def _filter_compatible_readers(self, new_pattern): """Filter reader dropwdown items to those that accept `new_extension`""" self._new_reader_dropdown.clear() readers = self._npe2_readers.copy() to_delete = [] compatible_readers = get_potential_readers(new_pattern) for plugin_name, display_name in readers.items(): if plugin_name not in compatible_readers: to_delete.append(plugin_name) for reader in to_delete: del readers[reader] readers.update(self._npe1_readers) if not readers: self._new_reader_dropdown.addItem("None available") else: for i, (plugin_name, display_name) in enumerate(sorted(readers.items())): self._add_reader_choice(i, plugin_name, display_name) def _save_new_preference(self, event): """Save current preference to settings and show in table""" fn_pattern = self._fn_pattern_edit.text() reader = self._new_reader_dropdown.currentData() if not fn_pattern or not reader: return # if user types pattern that starts with a . it's probably a file extension so prepend the * if fn_pattern.startswith('.'): fn_pattern = f'*{fn_pattern}' if fn_pattern in get_settings().plugins.extension2reader: self._edit_existing_preference(fn_pattern, reader) else: self._add_new_row(fn_pattern, reader) get_settings().plugins.extension2reader = { **get_settings().plugins.extension2reader, fn_pattern: reader, } def _edit_existing_preference(self, fn_pattern, reader): """Edit existing extension preference""" current_reader_label = self.findChild(QLabel, fn_pattern) if reader in self._npe2_readers: reader = self._npe2_readers[reader] current_reader_label.setText(reader) def _add_new_row(self, fn_pattern, reader): """Add new reader preference to table""" last_row = self._table.rowCount() if (last_row == 1 and 'No filename preferences found' in self._table.item(0, 0).text()): self._table.removeRow(0) last_row = 0 self._table.insertRow(last_row) item = QTableWidgetItem(fn_pattern) item.setFlags(Qt.NoItemFlags) self._table.setItem(last_row, self._fn_pattern_col, item) plugin_widg = QWidget() # need object name to easily find row plugin_widg.setObjectName(f'{fn_pattern}') plugin_widg.setLayout(QHBoxLayout()) plugin_widg.layout().setContentsMargins(0, 0, 0, 0) if reader in self._npe2_readers: reader = self._npe2_readers[reader] plugin_label = QLabel(reader, objectName=fn_pattern) # need object name to easily work out which button was clicked remove_btn = QPushButton('X', objectName=fn_pattern) remove_btn.setFixedWidth(30) remove_btn.setStyleSheet('margin: 4px;') remove_btn.setToolTip( trans._('Remove this filename pattern to reader association')) remove_btn.clicked.connect(self.remove_existing_preference) plugin_widg.layout().addWidget(plugin_label) plugin_widg.layout().addWidget(remove_btn) self._table.setCellWidget(last_row, self._reader_col, plugin_widg) def remove_existing_preference(self, event): """Delete extension to reader mapping setting and remove table row""" pattern_to_remove = self.sender().objectName() current_settings = get_settings().plugins.extension2reader # need explicit assignment to new object here for persistence get_settings().plugins.extension2reader = { k: v for k, v in current_settings.items() if k != pattern_to_remove } for i in range(self._table.rowCount()): row_widg_name = self._table.cellWidget( i, self._reader_col).objectName() if row_widg_name == pattern_to_remove: self._table.removeRow(i) break if self._table.rowCount() == 0: self._display_no_preferences_found() def value(self): """Return extension:reader mapping from settings. Returns ------- Dict[str, str] mapping of extension to reader plugin display name """ return get_settings().plugins.extension2reader
class DialogSelectQuantStandard(QDialog): def __init__(self, parent=None): super().__init__(parent) self._qe_param_built_in = [] self._qe_param_custom = [] self._qe_standard_selected = None self._qe_param = [] # The list of all standards self._custom_label = [] # The list of booleans: True - the standard is custom, False -built-in self.setWindowTitle("Load Quantitative Standard") self.setMinimumHeight(500) self.setMinimumWidth(600) self.resize(600, 500) self.selected_standard_index = -1 labels = ("C", "Serial #", "Name", "Description") col_stretch = ( QHeaderView.ResizeToContents, QHeaderView.ResizeToContents, QHeaderView.ResizeToContents, QHeaderView.Stretch, ) self.table = QTableWidget() set_tooltip( self.table, "To <b> load the sta" "ndard</b>, double-click on the table row or " "select the table row and then click <b>Ok</b> button.", ) self.table.setMinimumHeight(200) self.table.setColumnCount(len(labels)) self.table.verticalHeader().hide() self.table.setHorizontalHeaderLabels(labels) self.table.setSelectionBehavior(QTableWidget.SelectRows) self.table.setSelectionMode(QTableWidget.SingleSelection) self.table.itemSelectionChanged.connect(self.item_selection_changed) self.table.itemDoubleClicked.connect(self.item_double_clicked) self.table.setStyleSheet( "QTableWidget::item{color: black;}" "QTableWidget::item:selected{background-color: red;}" "QTableWidget::item:selected{color: white;}" ) header = self.table.horizontalHeader() for n, col_stretch in enumerate(col_stretch): # Set stretching for the columns header.setSectionResizeMode(n, col_stretch) self.lb_info = QLabel() self.lb_info.setText("Column 'C': * means that the standard is user-defined.") button_box = QDialogButtonBox(QDialogButtonBox.Open | QDialogButtonBox.Cancel) button_box.button(QDialogButtonBox.Cancel).setDefault(True) button_box.accepted.connect(self.accept) button_box.rejected.connect(self.reject) self.pb_open = button_box.button(QDialogButtonBox.Open) self.pb_open.setEnabled(False) vbox = QVBoxLayout() vbox.addWidget(self.table) vbox.addWidget(self.lb_info) vbox.addWidget(button_box) self.setLayout(vbox) def _fill_table(self, table_contents): self.table.setRowCount(len(table_contents)) for nr, row in enumerate(table_contents): for nc, entry in enumerate(row): s = textwrap.fill(entry, width=40) item = QTableWidgetItem(s) item.setFlags(item.flags() & ~Qt.ItemIsEditable) if not nc: item.setTextAlignment(Qt.AlignHCenter | Qt.AlignVCenter) self.table.setItem(nr, nc, item) self.table.resizeRowsToContents() brightness = 220 for nr in range(self.table.rowCount()): for nc in range(self.table.columnCount()): self.table.item(nr, nc) if nr % 2: color = QColor(255, brightness, brightness) else: color = QColor(brightness, 255, brightness) self.table.item(nr, nc).setBackground(QBrush(color)) try: index = self._qe_param.index(self._qe_standard_selected) self.selected_standard_index = index n_columns = self.table.columnCount() self.table.setRangeSelected(QTableWidgetSelectionRange(index, 0, index, n_columns - 1), True) except ValueError: pass def item_selection_changed(self): sel_ranges = self.table.selectedRanges() # The table is configured to have one or no selected ranges # 'Open' button should be enabled only if a range (row) is selected if sel_ranges: self.selected_standard_index = sel_ranges[0].topRow() self.pb_open.setEnabled(True) else: self.selected_standard_index = -1 self.pb_open.setEnabled(False) def item_double_clicked(self): self.accept() def set_standards(self, qe_param_built_in, qe_param_custom, qe_standard_selected): self._qe_standard_selected = qe_standard_selected self._qe_param = qe_param_custom + qe_param_built_in custom_label = [True] * len(qe_param_custom) + [False] * len(qe_param_built_in) table_contents = [] for n, param in enumerate(self._qe_param): custom = "*" if custom_label[n] else "" serial = param["serial"] name = param["name"] description = param["description"] table_contents.append([custom, serial, name, description]) self._fill_table(table_contents) def get_selected_standard(self): if self.selected_standard_index >= 0: return self._qe_param[self.selected_standard_index] else: return None