def _create_table(self) -> QTableWidget: """Create and configure a new table widget.""" table = QTableWidget() table.verticalHeader().setVisible(False) table.horizontalHeader().setVisible(False) table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) table.resizeRowsToContents() table.setShowGrid(False) return table
def add_table(self): """Add table with info about files to be recovered.""" table = QTableWidget(len(self.data), 3, self) self.table = table labels = [_('Original file'), _('Autosave file'), _('Actions')] table.setHorizontalHeaderLabels(labels) table.verticalHeader().hide() table.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded) table.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) table.setSelectionMode(QTableWidget.NoSelection) # Show horizontal grid lines table.setShowGrid(False) table.setStyleSheet('::item { border-bottom: 1px solid gray }') for idx, (original, autosave) in enumerate(self.data): self.add_label_to_table(idx, 0, file_data_to_str(original)) self.add_label_to_table(idx, 1, file_data_to_str(autosave)) widget = QWidget() layout = QHBoxLayout() tooltip = _('Recover the autosave file to its original location, ' 'replacing the original if it exists.') button = QPushButton(_('Restore')) button.setToolTip(tooltip) button.clicked.connect( lambda checked, my_idx=idx: self.restore(my_idx)) layout.addWidget(button) tooltip = _('Delete the autosave file.') button = QPushButton(_('Discard')) button.setToolTip(tooltip) button.clicked.connect( lambda checked, my_idx=idx: self.discard(my_idx)) layout.addWidget(button) tooltip = _('Display the autosave file (and the original, if it ' 'exists) in Spyder\'s Editor. You will have to move ' 'or delete it manually.') button = QPushButton(_('Open')) button.setToolTip(tooltip) button.clicked.connect( lambda checked, my_idx=idx: self.open_files(my_idx)) layout.addWidget(button) widget.setLayout(layout) self.table.setCellWidget(idx, 2, widget) table.resizeRowsToContents() table.resizeColumnsToContents() self.layout.addWidget(table)
class LogViewerDialog(DialogBase): """Logger widget.""" def __init__( self, parent=None, log_folder=LOG_FOLDER, log_filename=LOG_FILENAME, ): """ Logger widget. Parameters ---------- log_folder: str Folder where logs are located log_filename: str Basic name for the rotating log files. """ super(LogViewerDialog, self).__init__(parent=parent) self._data = None self._columns = ['level', 'time', 'module', 'method', 'message'] self._headers = [c.capitalize() for c in self._columns] self._log_filename = log_filename self._log_folder = log_folder # Widgets self.label = QLabel('Select log file:') self.combobox = ComboBoxBase() self.table_logs = QTableWidget(self) self.button_copy = ButtonPrimary('Copy') self.text_search = LineEditSearch() # Widget setup self.table_logs.setAttribute(Qt.WA_LayoutUsesWidgetRect, True) horizontal_header = self.table_logs.horizontalHeader() vertical_header = self.table_logs.verticalHeader() horizontal_header.setStretchLastSection(True) horizontal_header.setSectionResizeMode(QHeaderView.Fixed) vertical_header.setSectionResizeMode(QHeaderView.Fixed) self.table_logs.setSelectionBehavior(QTableWidget.SelectRows) self.table_logs.setEditTriggers(QTableWidget.NoEditTriggers) self.setWindowTitle('Log Viewer') self.setMinimumWidth(800) self.setMinimumHeight(500) self.text_search.setPlaceholderText("Search...") # Layouts top_layout = QHBoxLayout() top_layout.addWidget(self.label) top_layout.addWidget(SpacerHorizontal()) top_layout.addWidget(self.combobox) top_layout.addStretch() top_layout.addWidget(SpacerHorizontal()) top_layout.addWidget(self.text_search) top_layout.addWidget(SpacerHorizontal()) top_layout.addWidget(self.button_copy) layout = QVBoxLayout() layout.addLayout(top_layout) layout.addWidget(SpacerVertical()) layout.addWidget(self.table_logs) self.setLayout(layout) # Signals self.combobox.currentIndexChanged.connect(self.update_text) self.button_copy.clicked.connect(self.copy_item) self.text_search.textChanged.connect(self.filter_text) # Setup() self.setup() self.update_style_sheet() def update_style_sheet(self, style_sheet=None): """Update custom CSS stylesheet.""" if style_sheet is None: style_sheet = load_style_sheet() self.setStyleSheet(style_sheet) def setup(self): """Setup widget content.""" self.combobox.clear() paths = log_files( log_folder=self._log_folder, log_filename=self._log_filename, ) files = [os.path.basename(p) for p in paths] self.combobox.addItems(files) def filter_text(self): """Search for text in the selected log file.""" search = self.text_search.text().lower() for i, data in enumerate(self._data): if any(search in str(d).lower() for d in data.values()): self.table_logs.showRow(i) else: self.table_logs.hideRow(i) def row_data(self, row): """Give the current row data concatenated with spaces.""" data = {} if self._data: length = len(self._data) if 0 >= row < length: data = self._data[row] return data def update_text(self, index): """Update logs based on combobox selection.""" path = os.path.join(self._log_folder, self.combobox.currentText()) self._data = load_log(path) self.table_logs.clear() self.table_logs.setSortingEnabled(False) self.table_logs.setRowCount(len(self._data)) self.table_logs.setColumnCount(len(self._columns)) self.table_logs.setHorizontalHeaderLabels(self._headers) for row, data in enumerate(self._data): for col, col_key in enumerate(self._columns): item = QTableWidgetItem(data.get(col_key, '')) self.table_logs.setItem(row, col, item) for c in [0, 2, 3]: self.table_logs.resizeColumnToContents(c) self.table_logs.resizeRowsToContents() self.table_logs.setSortingEnabled(True) self.table_logs.scrollToBottom() self.table_logs.scrollToTop() self.table_logs.sortByColumn(1, Qt.AscendingOrder) # Make sure there is always a selected row self.table_logs.setCurrentCell(0, 0) def copy_item(self): """Copy selected item to clipboard in markdown format.""" app = QApplication.instance() items = self.table_logs.selectedIndexes() if items: rows = set(sorted(i.row() for i in items)) if self._data: all_data = [self._data[row] for row in rows] dump = json.dumps(all_data, sort_keys=True, indent=4) app.clipboard().setText('```json\n' + dump + '\n```')
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 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