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)
def _setup_table_widget(self): """ Make a table showing :return: A QTableWidget object which will contain plot widgets """ table_widget = QTableWidget(3, 7, self) table_widget.setVerticalHeaderLabels(['u1', 'u2', 'u3']) col_headers = [ 'a*', 'b*', 'c*' ] if self.frame == SpecialCoordinateSystem.HKL else ['Qx', 'Qy', 'Qz'] col_headers.extend(['start', 'stop', 'nbins', 'step']) table_widget.setHorizontalHeaderLabels(col_headers) table_widget.setFixedHeight( table_widget.verticalHeader().defaultSectionSize() * (table_widget.rowCount() + 1)) # +1 to include headers for icol in range(table_widget.columnCount()): table_widget.setColumnWidth(icol, 50) table_widget.horizontalHeader().setSectionResizeMode( QHeaderView.Stretch) table_widget.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.table = table_widget self.layout.addWidget(self.table)
class PMGTableShow(BaseExtendedWidget): default_bg = QTableWidgetItem().background() default_fg = QTableWidgetItem().foreground() def __init__(self, layout_dir: str, title: List[str], initial_value: List[List[Union[int, float, str]]], size_restricted=False, header_adaption_h=False, header_adaption_v=False, background_color: List[List[Union[str]]] = None, foreground_color: List[List[Union[str]]] = None): super().__init__(layout_dir=layout_dir) self.maximum_rows = 100 self.size_restricted = size_restricted self.header_adaption_h = header_adaption_h self.header_adaption_v = header_adaption_v self.background_color = background_color if background_color is not None else '' self.foreground_color = foreground_color if foreground_color is not None else '' self.char_width = 15 self.on_check_callback = None self.title_list = title entryLayout = QHBoxLayout() entryLayout.setContentsMargins(0, 0, 0, 0) self.ctrl = QTableWidget() self.ctrl.verticalHeader().setVisible(False) self.set_params(size_restricted, header_adaption_h, header_adaption_v) self.ctrl.setColumnCount(len(title)) self.ctrl.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded) for i, text in enumerate(title): self.ctrl.setColumnWidth(i, len(text) * self.char_width + 10) self.ctrl.setHorizontalHeaderItem(i, QTableWidgetItem(text)) self.central_layout.addLayout(entryLayout) entryLayout.addWidget(self.ctrl) if initial_value is not None: for sublist in initial_value: assert len(sublist) == len(title), \ 'title is not as long as sublist,%s,%s' % (repr(title), sublist) self.ctrl.setRowCount(len(initial_value)) self.set_value(initial_value) def set_params(self, size_restricted=False, header_adaption_h=False, header_adaption_v=False): self.size_restricted = size_restricted self.header_adaption_h = header_adaption_h self.header_adaption_v = header_adaption_v if header_adaption_h: self.ctrl.horizontalHeader().setSectionResizeMode( QHeaderView.Stretch) if header_adaption_v: self.ctrl.verticalHeader().setSectionResizeMode( QHeaderView.Stretch) def check_data(self, value: List[List[Union[int, float, str]]]): for sublist in value: assert len(sublist) == len(self.title_list),\ '%s,%s' % (repr(sublist), repr(self.title_list)) def set_value(self, value: List[List[Union[int, float, str]]]): self.check_data(value) self.ctrl.setRowCount(len(value)) cols = len(value[0]) if isinstance(self.foreground_color, str): fg = [[self.foreground_color for i in range(cols)] for j in range(len(value))] else: fg = self.foreground_color if isinstance(self.background_color, str): bg = [[self.background_color for i in range(cols)] for j in range(len(value))] else: bg = self.background_color for row, row_list in enumerate(value): for col, content in enumerate(row_list): if len(str(content)) * self.char_width > self.ctrl.columnWidth( col): self.ctrl.setColumnWidth( col, len(str(content)) * self.char_width + 10) table_item = QTableWidgetItem(str(content)) table_item.setTextAlignment(Qt.AlignCenter) # 字体颜色(红色) if fg[row][col] == '': table_item.setForeground(self.default_fg) else: table_item.setForeground( QBrush(QColor(*color_str2tup(fg[row][col])))) # 背景颜色(红色) if bg[row][col] == '': table_item.setBackground(self.default_bg) else: table_item.setBackground( QBrush(QColor(*color_str2tup(bg[row][col])))) self.ctrl.setItem(row, col, table_item) if self.size_restricted: if self.header_adaption_h: self.ctrl.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) scrollbar_area_width = 0 else: self.ctrl.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded) scrollbar_area_width = 10 self.ctrl.setMaximumHeight((self.ctrl.rowCount() + 1) * 30 + scrollbar_area_width) self.setMaximumHeight((self.ctrl.rowCount() + 1) * 30 + scrollbar_area_width) def alert(self, alert_level: int): self.ctrl.alert(alert_level) def add_row(self, row: List): assert len(row) == self.ctrl.columnCount() rc = self.ctrl.rowCount() self.ctrl.setRowCount(rc + 1) for i, val in enumerate(row): self.ctrl.setItem(rc, i, QTableWidgetItem(str(val))) if self.ctrl.rowCount() > self.maximum_rows: self.ctrl.removeRow(0)
class PackagesDialog(DialogBase): """Package dependencies dialog.""" sig_setup_ready = Signal() def __init__( self, parent=None, packages=None, pip_packages=None, remove_only=False, update_only=False, ): """About dialog.""" super(PackagesDialog, self).__init__(parent=parent) # Variables self.api = AnacondaAPI() self.actions = None self.packages = packages or [] self.pip_packages = pip_packages or [] # Widgets self.stack = QStackedWidget() self.table = QTableWidget() self.text = QTextEdit() self.label_description = LabelBase() self.label_status = LabelBase() self.progress_bar = QProgressBar() self.button_ok = ButtonPrimary('Apply') self.button_cancel = ButtonNormal('Cancel') # Widget setup self.text.setReadOnly(True) self.stack.addWidget(self.table) self.stack.addWidget(self.text) if remove_only: text = 'The following packages will be removed:<br>' else: text = 'The following packages will be modified:<br>' self.label_description.setText(text) self.label_description.setWordWrap(True) self.label_description.setWordWrap(True) self.label_status.setWordWrap(True) self.table.horizontalScrollBar().setVisible(False) self.table.setSelectionBehavior(QAbstractItemView.SelectRows) self.table.setAlternatingRowColors(True) self.table.setSelectionMode(QAbstractItemView.NoSelection) self.table.setSortingEnabled(True) self._hheader = self.table.horizontalHeader() self._vheader = self.table.verticalHeader() self._hheader.setStretchLastSection(True) self._hheader.setDefaultAlignment(Qt.AlignLeft) self._hheader.setSectionResizeMode(self._hheader.Fixed) self._vheader.setSectionResizeMode(self._vheader.Fixed) self.button_ok.setMinimumWidth(70) self.button_ok.setDefault(True) self.base_minimum_width = 300 if remove_only else 420 if remove_only: self.setWindowTitle("Remove Packages") elif update_only: self.setWindowTitle("Update Packages") else: self.setWindowTitle("Install Packages") self.setMinimumWidth(self.base_minimum_width) # Layouts layout_progress = QHBoxLayout() layout_progress.addWidget(self.label_status) layout_progress.addWidget(SpacerHorizontal()) layout_progress.addWidget(self.progress_bar) layout_buttons = QHBoxLayout() layout_buttons.addStretch() layout_buttons.addWidget(self.button_cancel) layout_buttons.addWidget(SpacerHorizontal()) layout_buttons.addWidget(self.button_ok) layout = QVBoxLayout() layout.addWidget(self.label_description) layout.addWidget(SpacerVertical()) layout.addWidget(self.stack) layout.addWidget(SpacerVertical()) layout.addLayout(layout_progress) layout.addWidget(SpacerVertical()) layout.addWidget(SpacerVertical()) layout.addLayout(layout_buttons) self.setLayout(layout) # Signals self.button_ok.clicked.connect(self.accept) self.button_cancel.clicked.connect(self.reject) self.button_ok.setDisabled(True) # Setup self.table.setDisabled(True) self.update_status('Solving package specifications', value=0, max_value=0) def setup(self, worker, output, error): """Setup the widget to include the list of dependencies.""" if not isinstance(output, dict): output = {} packages = sorted(pkg.split('==')[0] for pkg in self.packages) success = output.get('success') error = output.get('error', '') exception_name = output.get('exception_name', '') actions = output.get('actions', []) prefix = worker.prefix if exception_name: message = exception_name else: # All requested packages already installed message = output.get('message', ' ') navi_deps_error = self.api.check_navigator_dependencies( actions, prefix) description = self.label_description.text() if error: description = 'No packages will be modified.' self.stack.setCurrentIndex(1) self.button_ok.setDisabled(True) if self.api.is_offline(): error = ("Some of the functionality of Anaconda Navigator " "will be limited in <b>offline mode</b>. <br><br>" "Installation and upgrade actions will be subject to " "the packages currently available on your package " "cache.") self.text.setText(error) elif navi_deps_error: description = 'No packages will be modified.' error = ('Downgrading/removing these packages will modify ' 'Anaconda Navigator dependencies.') self.text.setText(error) self.stack.setCurrentIndex(1) message = 'NavigatorDependenciesError' self.button_ok.setDisabled(True) elif success and actions: self.stack.setCurrentIndex(0) # Conda 4.3.x if isinstance(actions, list): actions_link = actions[0].get('LINK', []) actions_unlink = actions[0].get('UNLINK', []) # Conda 4.4.x else: actions_link = actions.get('LINK', []) actions_unlink = actions.get('UNLINK', []) deps = set() deps = deps.union({p['name'] for p in actions_link}) deps = deps.union({p['name'] for p in actions_unlink}) deps = deps - set(packages) deps = sorted(list(deps)) count_total_packages = len(packages) + len(deps) plural_total = 's' if count_total_packages != 1 else '' plural_selected = 's' if len(packages) != 1 else '' self.table.setRowCount(count_total_packages) self.table.setColumnCount(4) if actions_link: description = '{0} package{1} will be installed'.format( count_total_packages, plural_total) self.table.showColumn(2) self.table.showColumn(3) elif actions_unlink and not actions_link: self.table.hideColumn(2) self.table.hideColumn(3) self.table.setHorizontalHeaderLabels( ['Name', 'Unlink', 'Link', 'Channel']) description = '{0} package{1} will be removed'.format( count_total_packages, plural_total) for row, pkg in enumerate(packages + deps): link_item = [p for p in actions_link if p['name'] == pkg] if not link_item: link_item = { 'version': '-'.center(len('link')), 'channel': '-'.center(len('channel')), } else: link_item = link_item[0] unlink_item = [p for p in actions_unlink if p['name'] == pkg] if not unlink_item: unlink_item = { 'version': '-'.center(len('link')), } else: unlink_item = unlink_item[0] unlink_version = str(unlink_item['version']) link_version = str(link_item['version']) item_unlink_v = QTableWidgetItem(unlink_version) item_link_v = QTableWidgetItem(link_version) item_link_c = QTableWidgetItem(link_item['channel']) if pkg in packages: item_name = QTableWidgetItem(pkg) else: item_name = QTableWidgetItem('*' + pkg) items = [item_name, item_unlink_v, item_link_v, item_link_c] for column, item in enumerate(items): item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable) self.table.setItem(row, column, item) if deps: message = ( '<b>*</b> indicates the package is a dependency of a ' 'selected package{0}<br>').format(plural_selected) self.button_ok.setEnabled(True) self.table.resizeColumnsToContents() unlink_width = self.table.columnWidth(1) if unlink_width < 60: self.table.setColumnWidth(1, 60) self.table.setHorizontalHeaderLabels( ['Name ', 'Unlink ', 'Link ', 'Channel ']) self.table.setEnabled(True) self.update_status(message=message) self.label_description.setText(description) # Adjust size after data has populated the table self.table.resizeColumnsToContents() width = sum( self.table.columnWidth(i) for i in range(self.table.columnCount())) delta = (self.width() - self.table.width() + self.table.verticalHeader().width() + 10) new_width = width + delta if new_width < self.base_minimum_width: new_width = self.base_minimum_width self.setMinimumWidth(new_width) self.setMaximumWidth(new_width) self.sig_setup_ready.emit() def update_status(self, message='', value=None, max_value=None): """Update status of packages dialog.""" self.label_status.setText(message) if max_value is None and value is None: self.progress_bar.setVisible(False) else: self.progress_bar.setVisible(True) self.progress_bar.setMaximum(max_value) self.progress_bar.setValue(value)
class 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 RgbSelectionWidget(QWidget): signal_update_map_selections = Signal() def __init__(self): super().__init__() self._range_table = [] self._limit_table = [] self._rgb_keys = ["red", "green", "blue"] self._rgb_dict = {_: None for _ in self._rgb_keys} widget_layout = self._setup_rgb_widget() self.setLayout(widget_layout) sp = QSizePolicy() sp.setControlType(QSizePolicy.PushButton) sp.setHorizontalPolicy(QSizePolicy.Expanding) sp.setVerticalPolicy(QSizePolicy.Fixed) self.setSizePolicy(sp) def _setup_rgb_element(self, n_row, *, rb_check=0): """ Parameters ---------- rb_check: int The number of QRadioButton to check. Typically this would be the row number. """ combo_elements = ComboBoxNamed(name=f"{n_row}") # combo_elements.setSizeAdjustPolicy(QComboBox.AdjustToContents) # Set text color for QComboBox widget (necessary if the program is used with Dark theme) pal = combo_elements.palette() pal.setColor(QPalette.ButtonText, Qt.black) combo_elements.setPalette(pal) # Set text color for drop-down view (necessary if the program is used with Dark theme) pal = combo_elements.view().palette() pal.setColor(QPalette.Text, Qt.black) combo_elements.view().setPalette(pal) btns = [QRadioButton(), QRadioButton(), QRadioButton()] if 0 <= rb_check < len(btns): btns[rb_check].setChecked(True) # Color is set for operation with Dark theme for btn in btns: pal = btn.palette() pal.setColor(QPalette.Text, Qt.black) btn.setPalette(pal) btn_group = QButtonGroup() for btn in btns: btn_group.addButton(btn) rng = RangeManager(name=f"{n_row}", add_sliders=True) rng.setTextColor([0, 0, 0]) # Set text color to 'black' # Set some text in edit boxes (just to demonstrate how the controls will look like) rng.le_min_value.setText("0.0") rng.le_max_value.setText("1.0") rng.setAlignment(Qt.AlignCenter) return combo_elements, btns, rng, btn_group def _enable_selection_events(self, enable): if enable: if not self.elements_btn_groups_events_enabled: for btn_group in self.elements_btn_groups: btn_group.buttonToggled.connect(self.rb_toggled) for el_combo in self.elements_combo: el_combo.currentIndexChanged.connect( self.combo_element_current_index_changed) for el_range in self.elements_range: el_range.selection_changed.connect( self.range_selection_changed) self.elements_btn_groups_events_enabled = True else: if self.elements_btn_groups_events_enabled: for btn_group in self.elements_btn_groups: btn_group.buttonToggled.disconnect(self.rb_toggled) for el_combo in self.elements_combo: el_combo.currentIndexChanged.disconnect( self.combo_element_current_index_changed) # Disconnecting the Range Manager signals is not necessary, but let's do it for consistency for el_range in self.elements_range: el_range.selection_changed.disconnect( self.range_selection_changed) self.elements_btn_groups_events_enabled = False def _setup_rgb_widget(self): self.elements_combo = [] self.elements_rb_color = [] self.elements_range = [] self.elements_btn_groups = [] self.elements_btn_groups_events_enabled = False self.row_colors = [] self.table = QTableWidget() # Horizontal header entries tbl_labels = ["Element", "Red", "Green", "Blue", "Range"] # The list of columns that stretch with the table self.tbl_cols_stretch = ("Range", ) self.table.setColumnCount(len(tbl_labels)) self.table.setRowCount(3) self.table.setHorizontalHeaderLabels(tbl_labels) self.table.verticalHeader().hide() self.table.setSelectionMode(QTableWidget.NoSelection) header = self.table.horizontalHeader() for n, lbl in enumerate(tbl_labels): # Set stretching for the columns if lbl in self.tbl_cols_stretch: header.setSectionResizeMode(n, QHeaderView.Stretch) else: header.setSectionResizeMode(n, QHeaderView.ResizeToContents) vheader = self.table.verticalHeader() vheader.setSectionResizeMode(QHeaderView.Stretch) # ResizeToContents) for n_row in range(3): combo_elements, btns, rng, btn_group = self._setup_rgb_element( n_row, rb_check=n_row) combo_elements.setMinimumWidth(180) self.table.setCellWidget(n_row, 0, combo_elements) for i, btn in enumerate(btns): item = QWidget() item_hbox = QHBoxLayout(item) item_hbox.addWidget(btn) item_hbox.setAlignment(Qt.AlignCenter) item_hbox.setContentsMargins(0, 0, 0, 0) item.setMinimumWidth(70) self.table.setCellWidget(n_row, i + 1, item) rng.setMinimumWidth(200) rng.setMaximumWidth(400) self.table.setCellWidget(n_row, 4, rng) self.elements_combo.append(combo_elements) self.elements_rb_color.append(btns) self.elements_range.append(rng) self.elements_btn_groups.append(btn_group) self.row_colors.append(self._rgb_keys[n_row]) # Colors that are used to paint rows of the table in RGB colors br = 150 self._rgb_row_colors = { "red": (255, br, br), "green": (br, 255, br), "blue": (br, br, 255) } self._rgb_color_keys = ["red", "green", "blue"] # Set initial colors for n_row in range(self.table.rowCount()): self._set_row_color(n_row) self._enable_selection_events(True) self.table.resizeRowsToContents() # Table height is computed based on content. It doesn't seem # to account for the height of custom widgets, but the table # looks good enough table_height = 0 for n_row in range(self.table.rowCount()): table_height += self.table.rowHeight(n_row) self.table.setMaximumHeight(table_height) table_width = 650 self.table.setMinimumWidth(table_width) self.table.setMaximumWidth(800) hbox = QHBoxLayout() hbox.addWidget(self.table) return hbox def combo_element_current_index_changed(self, name, index): if index < 0 or index >= len(self._range_table): return n_row = int(name) sel_eline = self._range_table[index][0] row_color = self.row_colors[n_row] self._rgb_dict[row_color] = sel_eline self.elements_range[n_row].set_range(self._range_table[index][1], self._range_table[index][2]) self.elements_range[n_row].set_selection( value_low=self._limit_table[index][1], value_high=self._limit_table[index][2]) self._update_map_selections() def range_selection_changed(self, v_low, v_high, name): n_row = int(name) row_color = self.row_colors[n_row] sel_eline = self._rgb_dict[row_color] ind = None try: ind = [_[0] for _ in self._limit_table].index(sel_eline) except ValueError: pass if ind is not None: self._limit_table[ind][1] = v_low self._limit_table[ind][2] = v_high self._update_map_selections() # We are not preventing users to select the same emission line in to rows. # Update the selected limits in other rows where the same element is selected. for nr, el_range in enumerate(self.elements_range): if (nr != n_row) and (self._rgb_dict[self.row_colors[nr]] == sel_eline): el_range.set_selection(value_low=v_low, value_high=v_high) def _get_selected_row_color(self, n_row): color_key = None btns = self.elements_rb_color[n_row] for n, btn in enumerate(btns): if btn.isChecked(): color_key = self._rgb_color_keys[n] break return color_key def _set_row_color(self, n_row, *, color_key=None): """ Parameters ---------- n_row: int The row number that needs background color change (0..2 if table has 3 rows) color_key: int Color key: "red", "green" or "blue" """ if color_key is None: color_key = self._get_selected_row_color(n_row) if color_key is None: return self.row_colors[n_row] = color_key rgb = self._rgb_row_colors[color_key] # The following code is based on the arrangement of the widgets in the table # Modify the code if widgets are arranged differently or the table structure # is changed for n_col in range(self.table.columnCount()): wd = self.table.cellWidget(n_row, n_col) if n_col == 0: # Combo box: update both QComboBox and QWidget backgrounds # QWidget - background of the drop-down selection list css1 = get_background_css(rgb, widget="QComboBox", editable=False) css2 = get_background_css(rgb, widget="QWidget", editable=True) wd.setStyleSheet(css2 + css1) elif n_col <= 3: # 3 QRadioButton's. The buttons are inserted into QWidget objects, # and we need to change backgrounds of QWidgets, not only buttons. wd.setStyleSheet( get_background_css(rgb, widget="QWidget", editable=False)) elif n_col == 4: # Custom RangeManager widget, color is updated using custom method wd.setBackground(rgb) n_col = self._rgb_color_keys.index(color_key) for n, n_btn in enumerate(self.elements_rb_color[n_row]): check_status = True if n == n_col else False n_btn.setChecked(check_status) def _fill_table(self): self._enable_selection_events(False) eline_list = [_[0] for _ in self._range_table] for n_row in range(self.table.rowCount()): self.elements_combo[n_row].clear() self.elements_combo[n_row].addItems(eline_list) for n_row, color in enumerate(self._rgb_color_keys): # Initially set colors in order self._set_row_color(n_row, color_key=color) eline_key = self._rgb_dict[color] if eline_key is not None: try: ind = eline_list.index(eline_key) self.elements_combo[n_row].setCurrentIndex(ind) range_low, range_high = self._range_table[ind][1:] self.elements_range[n_row].set_range(range_low, range_high) sel_low, sel_high = self._limit_table[ind][1:] self.elements_range[n_row].set_selection( value_low=sel_low, value_high=sel_high) except ValueError: pass else: self.elements_combo[n_row].setCurrentIndex(-1) # Deselect all self.elements_range[n_row].set_range(0, 1) self.elements_range[n_row].set_selection(value_low=0, value_high=1) self._enable_selection_events(True) def _find_rbutton(self, button): for nr, btns in enumerate(self.elements_rb_color): for nc, btn in enumerate(btns): if btn == button: # Return tuple (nr, nc) return nr, nc # Return None if the button is not found (this shouldn't happen) return None def rb_toggled(self, button, state): if state: # Ignore signals from unchecked buttons nr, nc = self._find_rbutton(button) color_current = self.row_colors[nr] color_to_set = self._rgb_color_keys[nc] nr_switch = self.row_colors.index(color_to_set) self._enable_selection_events(False) self._set_row_color(nr, color_key=color_to_set) self._set_row_color(nr_switch, color_key=color_current) # Swap selected maps tmp = self._rgb_dict[color_to_set] self._rgb_dict[color_to_set] = self._rgb_dict[color_current] self._rgb_dict[color_current] = tmp self._enable_selection_events(True) self._update_map_selections() def set_ranges_and_limits(self, *, range_table=None, limit_table=None, rgb_dict=None): if range_table is not None: self._range_table = copy.deepcopy(range_table) if limit_table is not None: self._limit_table = copy.deepcopy(limit_table) if rgb_dict is not None: self._rgb_dict = rgb_dict.copy() self._fill_table() def _update_map_selections(self): """Upload the selections (limit table) and update plot""" self.signal_update_map_selections.emit()
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 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