def resizeEvent(self, event): QTableWidget.resizeEvent(self, event) header = self.horizontalHeader() # resizes the column headers to fit the contents, # currently this overwrites any manual changes to the widths of the columns header.resizeSections(QHeaderView.ResizeToContents) # then allows the users to resize the headers manually header.setSectionResizeMode(QHeaderView.Interactive)
def __init__(self, parent): QTableWidget.__init__(self, parent) self._parent = parent self.setRowCount(2) self.setColumnCount(2) self.reset_headers() # signals self.cellChanged.connect(self.cell_changed)
def __init__(self, parent=None, init_channel=None): QTableWidget.__init__(self, parent) PyDMWritableWidget.__init__(self, init_channel=init_channel) self._columnHeaders = ["Value"] self._rowHeaders = [] self._itemsFlags = (Qt.ItemIsSelectable | Qt.ItemIsEditable | Qt.ItemIsEnabled) self.waveform = None self._valueBeingSet = False self.setColumnCount(1) self.cellChanged.connect(self.send_waveform)
def keyPressEvent(self, event): """ Qt override. """ if event.key() in [Qt.Key_Enter, Qt.Key_Return]: QTableWidget.keyPressEvent(self, event) # To avoid having to enter one final tab self.setDisabled(True) self.setDisabled(False) self._parent.keyPressEvent(event) else: QTableWidget.keyPressEvent(self, event)
def __init__(self, parent): """ :param parent: :return: """ QTableWidget.__init__(self, parent) self._myParent = parent self._myColumnNameList = None self._myColumnTypeList = None self._editableList = list() self._statusColName = 'Status' self._colIndexSelect = None return
def _make_table_widget(self): """ Make a table showing the matplotlib figure number of the plots, the name with close and edit buttons, and a hidden column for sorting with the last actuve order :return: A QTableWidget object which will contain plot widgets """ table_widget = QTableWidget(0, 3, self) table_widget.setHorizontalHeaderLabels(['No.', 'Plot Name', 'Last Active Order (hidden)']) table_widget.verticalHeader().setVisible(False) # Fix the size of 'No.' and let 'Plot Name' fill the space top_header = table_widget.horizontalHeader() table_widget.horizontalHeaderItem(Column.Number).setToolTip('This is the matplotlib figure number.\n\nFrom a ' 'script use plt.figure(N), where N is this figure ' 'number, to get a handle to the plot.') table_widget.horizontalHeaderItem(Column.Name).setToolTip('The plot name, also used as the file name when ' 'saving multiple plots.') table_widget.setSelectionBehavior(QAbstractItemView.SelectRows) table_widget.setSelectionMode(QAbstractItemView.ExtendedSelection) table_widget.setEditTriggers(QAbstractItemView.NoEditTriggers) table_widget.sortItems(Column.Number, Qt.AscendingOrder) table_widget.setSortingEnabled(True) table_widget.horizontalHeader().sectionClicked.connect(self.update_sort_menu_selection) if not DEBUG_MODE: table_widget.setColumnHidden(Column.LastActive, True) top_header.resizeSection(Column.Number, top_header.sectionSizeHint(Column.Number)) top_header.setSectionResizeMode(Column.Name, QHeaderView.Stretch) return table_widget
def _add_tab(self, json=None): """will look at the json and will display the values in conflicts in a new tab to allow the user to fix the conflicts""" number_of_tabs = self.ui.tabWidget.count() _table = QTableWidget() # initialize each table columns_width = self._calculate_columns_width(json=json) for _col in np.arange(len(json[0])): _table.insertColumn(_col) _table.setColumnWidth(_col, columns_width[_col]) for _row in np.arange(len(json)): _table.insertRow(_row) self.list_table.append(_table) _table.setHorizontalHeaderLabels(self.columns_label) for _row in np.arange(len(json)): # run number _col = 0 list_runs = json[_row]["Run Number"] o_parser = ListRunsParser() checkbox = QRadioButton(o_parser.new_runs(list_runs=list_runs)) if _row == 0: checkbox.setChecked(True) # QtCore.QObject.connect(checkbox, QtCore.SIGNAL("clicked(bool)"), # lambda bool, row=_row, table_id=_table: # self._changed_conflict_checkbox(bool, row, table_id)) _table.setCellWidget(_row, _col, checkbox) _col += 1 # chemical formula item = QTableWidgetItem(json[_row]["chemical_formula"]) _table.setItem(_row, _col, item) _col += 1 # geometry item = QTableWidgetItem(json[_row]["geometry"]) _table.setItem(_row, _col, item) _col += 1 # mass_density item = QTableWidgetItem(json[_row]["mass_density"]) _table.setItem(_row, _col, item) _col += 1 # sample_env_device item = QTableWidgetItem(json[_row]["sample_env_device"]) _table.setItem(_row, _col, item) self.ui.tabWidget.insertTab(number_of_tabs, _table, "Conflict #{}".format(number_of_tabs))
def _copy_table_data(table: QTableWidget) -> None: """Copy item text to clipboard.""" text = table.currentItem().text() if text: QApplication.clipboard().setText(text)
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._("Restore 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(trans._("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)
class CameraWindow(PyDialog): def __init__(self, data, win_parent=None): """ +--------+ | Camera | +--------+---------------+ | Camera Name | | +-------------------+ | | | | | | | | | | | | | | | | | | | | | | +-------------------+ | | | | Name xxx Save | | Delete Set | | | | Apply OK Cancel | +--------+---------------+ """ PyDialog.__init__(self, data, win_parent) self.setWindowTitle('Camera Views') #self.setWindowIcon(view_icon) self._default_name = 'Camera' self.out_data['clicked_ok'] = False self.cameras = deepcopy(data['cameras']) self.names = sorted(self.cameras.keys()) self.name = QLabel("Name:") self.name_edit = QLineEdit(str(self._default_name)) self.delete_button = QPushButton("Delete") self.set_button = QPushButton("Set") self.save_button = QPushButton("Save") # closing self.apply_button = QPushButton("Apply") #self.ok_button = QPushButton("OK") self.close_button = QPushButton("Close") self.cancel_button = QPushButton("Cancel") self.table = QTableWidget() names_text = [] for name in self.names: name_text = QTableWidgetItem(str(name)) names_text.append(name_text) self.create_layout(names_text) self.set_connections() def create_layout(self, names_text): nrows = len(self.names) table = self.table table.setRowCount(nrows) table.setColumnCount(1) headers = ['Camera Name'] table.setHorizontalHeaderLabels(headers) header = table.horizontalHeader() header.setStretchLastSection(True) for iname, name_text in enumerate(names_text): # row, col, value table.setItem(iname, 0, name_text) table.resizeRowsToContents() ok_cancel_box = QHBoxLayout() ok_cancel_box.addWidget(self.apply_button) #ok_cancel_box.addWidget(self.ok_button) ok_cancel_box.addWidget(self.close_button) ok_cancel_box.addWidget(self.cancel_button) grid = QGridLayout() irow = 0 grid.addWidget(self.name, irow, 0) grid.addWidget(self.name_edit, irow, 1) grid.addWidget(self.save_button, irow, 2) irow += 1 grid.addWidget(self.delete_button, irow, 0) grid.addWidget(self.set_button, irow, 1) irow += 1 vbox = QVBoxLayout() vbox.addWidget(self.table) vbox.addLayout(grid) vbox.addStretch() vbox.addLayout(ok_cancel_box) self.setLayout(vbox) def set_connections(self): #if qt_version == 4: #self.connect(self.ok_button, QtCore.SIGNAL('clicked()'), self.on_ok) self.set_button.clicked.connect(self.on_set) self.save_button.clicked.connect(self.on_save) self.delete_button.clicked.connect(self.on_delete) self.apply_button.clicked.connect(self.on_apply) self.close_button.clicked.connect(self.on_close) self.cancel_button.clicked.connect(self.on_cancel) def on_set(self): objs = self.table.selectedIndexes() if len(objs) == 1: obj = objs[0] irow = obj.row() name = self.names[irow] self.set_camera(name) return True return False def on_save(self): name = str(self.name_edit.text()).strip() if name in self.cameras: return irow = self.nrows if len(name): self.table.insertRow(irow) name_text = QTableWidgetItem(str(name)) self.table.setItem(irow, 0, name_text) self.name_edit.setText('') self.save_camera(name) def set_camera(self, name): camera_data = self.cameras[name] if self.win_parent is None: return self.win_parent.on_set_camera_data(camera_data) def save_camera(self, name): self.names.append(name) if self.win_parent is None: self.cameras[name] = None return self.cameras[name] = self.win_parent.get_camera_data() #@property #def camera(self): @property def nrows(self): return self.table.rowCount() def on_delete(self): irows = [] for obj in self.table.selectedIndexes(): irow = obj.row() irows.append(irow) irows.sort() for irow in reversed(irows): self.table.removeRow(irow) #print('delete', self.names) name = self.names.pop(irow) del self.cameras[name] #print(' removing irow=%s name=%r' % (irow, name)) def closeEvent(self, event): event.accept() @staticmethod def check_name(cell): text = str(cell.text()).strip() if len(text): cell.setStyleSheet("QLineEdit{background: white;}") return text, True else: cell.setStyleSheet("QLineEdit{background: red;}") return None, False #def on_validate(self): #name_value, flag0 = self.check_name(self.name_edit) #if flag0: #self.out_data['cameras'] = self.cameras #self.out_data['clicked_ok'] = True #return True #return False def on_apply(self): passed = self.on_set() #if passed: # self.win_parent.create_plane(self.out_data) return passed def on_close(self): self.out_data['clicked_ok'] = True self.out_data['cameras'] = self.cameras self.close() def on_ok(self): passed = self.on_apply() if passed: name = str(self.name_edit.text()).strip() self.out_data['name'] = name self.out_data['cameras'] = self.cameras self.out_data['clicked_ok'] = True self.close() #self.destroy() def on_cancel(self): self.close()
def __init__(self, parent=None, packages=None, pip_packages=None, remove_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) if remove_only: self.setWindowTitle("Remove Packages") else: self.setWindowTitle("Install Packages") self.setMinimumWidth(300 if remove_only else 420) # 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)
class PackagesDialog(DialogBase): """Package dependencies dialog.""" sig_setup_ready = Signal() def __init__(self, parent=None, packages=None, pip_packages=None, remove_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) if remove_only: self.setWindowTitle("Remove Packages") else: self.setWindowTitle("Install Packages") self.setMinimumWidth(300 if remove_only else 420) # 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', []) message = exception_name if exception_name else ' ' prefix = worker.prefix navi_deps_error = self.api.check_navigator_dependencies( actions, prefix) description = self.label_description.text() if error: description = 'No packages will be modified.' self.text.setText(error) self.stack.setCurrentIndex(1) self.button_ok.setDisabled(True) 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) actions_link = actions[0].get('LINK', []) actions_unlink = actions[0].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) self.table.setItem(row, 0, item_name) # needed for sorting self.table.setItem(row, 1, item_unlink_v) self.table.setItem(row, 2, item_link_v) self.table.setItem(row, 3, item_link_c) 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) 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)
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 AnnotationsDialog(QDialog): def __init__(self, parent, onset, duration, description): super().__init__(parent) self.setWindowTitle("Edit Annotations") self.table = QTableWidget(len(onset), 3) for row, annotation in enumerate(zip(onset, duration, description)): self.table.setItem(row, 0, IntTableWidgetItem(annotation[0])) self.table.setItem(row, 1, IntTableWidgetItem(annotation[1])) self.table.setItem(row, 2, QTableWidgetItem(annotation[2])) self.table.setHorizontalHeaderLabels(["Onset", "Duration", "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(500, 500) @Slot() def toggle_buttons(self): """Toggle + and - buttons.""" if len(self.table.selectedItems()) == 3: # one row (3 items) selected self.add_button.setEnabled(True) self.remove_button.setEnabled(True) elif len(self.table.selectedItems()) > 3: # 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.setItem(current_row, 2, QTableWidgetItem("New Annotation")) self.table.setSortingEnabled(True) def remove_event(self): rows = {index.row() for index in self.table.selectedIndexes()} for row in sorted(rows, reverse=True): self.table.removeRow(row)
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)
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
class WndComputeRoiMaps(SecondaryWindow): # Signal that is sent (to main window) to update global state of the program update_global_state = Signal() computations_complete = Signal(object) signal_roi_computation_complete = Signal() signal_activate_tab_xrf_maps = Signal() def __init__(self, *, gpc, gui_vars): super().__init__() # Global processing classes self.gpc = gpc # Global GUI variables (used for control of GUI state) self.gui_vars = gui_vars # Reference to the main window. The main window will hold # references to all non-modal windows that could be opened # from multiple places in the program. self.ref_main_window = self.gui_vars["ref_main_window"] self.update_global_state.connect( self.ref_main_window.update_widget_state) self.initialize() def initialize(self): self.setWindowTitle("PyXRF: Compute XRF Maps Based on ROIs") self.setMinimumWidth(600) self.setMinimumHeight(300) self.resize(600, 600) header_vbox = self._setup_header() self._setup_table() footer_hbox = self._setup_footer() vbox = QVBoxLayout() vbox.addLayout(header_vbox) vbox.addWidget(self.table) vbox.addLayout(footer_hbox) self.setLayout(vbox) self._set_tooltips() def _setup_header(self): self.pb_clear = QPushButton("Clear") self.pb_clear.clicked.connect(self.pb_clear_clicked) self.pb_use_lines_for_fitting = QPushButton( "Use Lines Selected For Fitting") self.pb_use_lines_for_fitting.clicked.connect( self.pb_use_lines_for_fitting_clicked) self.le_sel_emission_lines = LineEditExtended() self.le_sel_emission_lines.textChanged.connect( self.le_sel_emission_lines_text_changed) self.le_sel_emission_lines.editingFinished.connect( self.le_sel_emission_lines_editing_finished) sample_elements = "" self.le_sel_emission_lines.setText(sample_elements) vbox = QVBoxLayout() hbox = QHBoxLayout() hbox.addWidget(QLabel("Enter emission lines, e.g. Fe_K, Gd_L ")) hbox.addStretch(1) hbox.addWidget(self.pb_clear) hbox.addWidget(self.pb_use_lines_for_fitting) vbox.addLayout(hbox) vbox.addWidget(self.le_sel_emission_lines) return vbox def _setup_table(self): # Labels for horizontal header self.tbl_labels = ["Line", "E, keV", "ROI, keV", "Show", "Reset"] # The list of columns that stretch with the table self.tbl_cols_stretch = ("E, keV", "ROI, keV") # Table item representation if different from default self.tbl_format = {"E, keV": ".3f"} # Editable items (highlighted with lighter background) self.tbl_cols_editable = {"ROI, keV"} # Columns that contain Range Manager self.tbl_cols_range_manager = ("ROI, keV", ) self.table = QTableWidget() self.table.setColumnCount(len(self.tbl_labels)) self.table.setHorizontalHeaderLabels(self.tbl_labels) self.table.verticalHeader().hide() self.table.setSelectionMode(QTableWidget.NoSelection) self.table.setStyleSheet("QTableWidget::item{color: black;}") header = self.table.horizontalHeader() for n, lbl in enumerate(self.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) self._table_contents = [] self.cb_list = [] self.range_manager_list = [] self.pb_default_list = [] self.fill_table(self._table_contents) def fill_table(self, table_contents): self.table.clearContents() self._table_contents = table_contents # Save new table contents for item in self.range_manager_list: item.selection_changed.disconnect( self.range_manager_selection_changed) self.range_manager_list = [] for cb in self.cb_list: cb.stateChanged.disconnect(self.cb_state_changed) self.cb_list = [] for pb in self.pb_default_list: pb.clicked.connect(self.pb_default_clicked) self.pb_default_list = [] self.table.setRowCount(len(table_contents)) for nr, row in enumerate(table_contents): eline_name = row["eline"] + "a1" energy = row["energy_center"] energy_left = row["energy_left"] energy_right = row["energy_right"] range_displayed = row["range_displayed"] table_row = [eline_name, energy, (energy_left, energy_right)] for nc, entry in enumerate(table_row): label = self.tbl_labels[nc] # Set alternating background colors for the table rows # Make background for editable items a little brighter brightness = 240 if label in self.tbl_cols_editable else 220 if nr % 2: rgb_bckg = (255, brightness, brightness) else: rgb_bckg = (brightness, 255, brightness) if self.tbl_labels[nc] not in self.tbl_cols_range_manager: if self.tbl_labels[nc] in self.tbl_format: fmt = self.tbl_format[self.tbl_labels[nc]] s = ("{:" + fmt + "}").format(entry) else: s = f"{entry}" item = QTableWidgetItem(s) if nc > 0: item.setTextAlignment(Qt.AlignCenter) else: item.setTextAlignment(Qt.AlignRight | Qt.AlignVCenter) # Set all columns not editable (unless needed) item.setFlags(item.flags() & ~Qt.ItemIsEditable) # Note, that there is no way to set style sheet for QTableWidgetItem item.setBackground(QBrush(QColor(*rgb_bckg))) self.table.setItem(nr, nc, item) else: spin_name = f"{nr}" item = RangeManager(name=spin_name, add_sliders=False, selection_to_range_min=0.0001) item.set_range( 0.0, 100.0) # The range is greater than needed (in keV) item.set_selection(value_low=entry[0], value_high=entry[1]) item.setTextColor((0, 0, 0)) # In case of dark theme item.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter) self.range_manager_list.append(item) item.selection_changed.connect( self.range_manager_selection_changed) color = (rgb_bckg[0], rgb_bckg[1], rgb_bckg[2]) item.setBackground(color) self.table.setCellWidget(nr, nc, item) brightness = 220 if nr % 2: rgb_bckg = (255, brightness, brightness) else: rgb_bckg = (brightness, 255, brightness) item = QWidget() cb = CheckBoxNamed(name=f"{nr}") cb.setChecked(Qt.Checked if range_displayed else Qt.Unchecked) self.cb_list.append(cb) cb.stateChanged.connect(self.cb_state_changed) item_hbox = QHBoxLayout(item) item_hbox.addWidget(cb) item_hbox.setAlignment(Qt.AlignCenter) item_hbox.setContentsMargins(0, 0, 0, 0) color_css = f"rgb({rgb_bckg[0]}, {rgb_bckg[1]}, {rgb_bckg[2]})" item.setStyleSheet( f"QWidget {{ background-color: {color_css}; }} " f"QCheckBox {{ color: black; background-color: white }}") self.table.setCellWidget(nr, nc + 1, item) item = PushButtonNamed("Reset", name=f"{nr}") item.clicked.connect(self.pb_default_clicked) self.pb_default_list.append(item) rgb_bckg = [_ - 35 if (_ < 255) else _ for _ in rgb_bckg] color_css = f"rgb({rgb_bckg[0]}, {rgb_bckg[1]}, {rgb_bckg[2]})" item.setStyleSheet( f"QPushButton {{ color: black; background-color: {color_css}; }}" ) self.table.setCellWidget(nr, nc + 2, item) def _setup_footer(self): self.cb_subtract_baseline = QCheckBox("Subtract baseline") self.cb_subtract_baseline.setChecked( Qt.Checked if self.gpc.get_roi_subtract_background( ) else Qt.Unchecked) self.cb_subtract_baseline.toggled.connect( self.cb_subtract_baseline_toggled) self.pb_compute_roi = QPushButton("Compute ROIs") self.pb_compute_roi.clicked.connect(self.pb_compute_roi_clicked) hbox = QHBoxLayout() hbox.addWidget(self.cb_subtract_baseline) hbox.addStretch(1) hbox.addWidget(self.pb_compute_roi) return hbox def _set_tooltips(self): set_tooltip(self.pb_clear, "<b>Clear</b> the list") set_tooltip( self.pb_use_lines_for_fitting, "Copy the contents of <b>the list of emission lines selected for fitting</b> to the list of ROIs", ) set_tooltip( self.le_sel_emission_lines, "The list of <b>emission lines</b> selected for ROI computation.") set_tooltip(self.table, "The list of ROIs") set_tooltip( self.cb_subtract_baseline, "<b>Subtract baseline</b> from the pixel spectra before computing ROIs. " "Subtracting baseline slows down computations and usually have no benefit. " "In most cases it should remain <b>unchecked</b>.", ) set_tooltip( self.pb_compute_roi, "<b>Run</b> computations of the ROIs. The resulting <b>ROI</b> dataset " "may be viewed in <b>XRF Maps</b> tab.", ) def update_widget_state(self, condition=None): # Update the state of the menu bar state = not self.gui_vars["gui_state"]["running_computations"] self.setEnabled(state) # Hide the window if required by the program state state_file_loaded = self.gui_vars["gui_state"]["state_file_loaded"] state_model_exist = self.gui_vars["gui_state"]["state_model_exists"] if not state_file_loaded or not state_model_exist: self.hide() if condition == "tooltips": self._set_tooltips() def pb_clear_clicked(self): self.gpc.clear_roi_element_list() self._update_displayed_element_list() self._validate_element_list() def pb_use_lines_for_fitting_clicked(self): self.gpc.load_roi_element_list_from_selected() self._update_displayed_element_list() self._validate_element_list() def le_sel_emission_lines_text_changed(self, text): self._validate_element_list(text) def le_sel_emission_lines_editing_finished(self): text = self.le_sel_emission_lines.text() if self._validate_element_list(text): self.gpc.set_roi_selected_element_list(text) self._update_table() else: element_list = self.gpc.get_roi_selected_element_list() self.le_sel_emission_lines.setText(element_list) def cb_subtract_baseline_toggled(self, state): self.gpc.set_roi_subtract_background(bool(state)) def cb_state_changed(self, name, state): try: nr = int(name) # Row number checked = state == Qt.Checked eline = self._table_contents[nr]["eline"] self._table_contents[nr]["range_displayed"] = checked self.gpc.show_roi(eline, checked) except Exception as ex: logger.error( f"Failed to process selection change. Exception occurred: {ex}." ) def _find_spin_box(self, name): for item in self.spin_list: if item.getName() == name: return item return None def spin_value_changed(self, name, value): try: nr, side = name.split(",") nr = int(nr) keys = {"left": "energy_left", "right": "energy_right"} side = keys[side] eline = self._table_contents[nr]["eline"] if self._table_contents[nr][side] == value: return if side == "energy_left": # Left boundary if value < self._table_contents[nr]["energy_right"]: self._table_contents[nr][side] = value else: # Right boundary if value > self._table_contents[nr]["energy_left"]: self._table_contents[nr][side] = value # Update plot left, right = self._table_contents[nr][ "energy_left"], self._table_contents[nr]["energy_right"] self.gpc.change_roi(eline, left, right) except Exception as ex: logger.error( f"Failed to change the ROI. Exception occurred: {ex}.") def range_manager_selection_changed(self, left, right, name): try: nr = int(name) eline = self._table_contents[nr]["eline"] self.gpc.change_roi(eline, left, right) except Exception as ex: logger.error( f"Failed to change the ROI. Exception occurred: {ex}.") def pb_default_clicked(self, name): try: nr = int(name) eline = self._table_contents[nr]["eline"] left = self._table_contents[nr]["energy_left_default"] right = self._table_contents[nr]["energy_right_default"] self.range_manager_list[nr].set_selection(value_low=left, value_high=right) self.gpc.change_roi(eline, left, right) except Exception as ex: logger.error( f"Failed to change the ROI. Exception occurred: {ex}.") def pb_compute_roi_clicked(self): def cb(): try: self.gpc.compute_rois() success, msg = True, "" except Exception as ex: success, msg = False, str(ex) return {"success": success, "msg": msg} self._compute_in_background(cb, self.slot_compute_roi_clicked) @Slot(object) def slot_compute_roi_clicked(self, result): self._recover_after_compute(self.slot_compute_roi_clicked) success = result["success"] if success: self.gui_vars["gui_state"]["state_xrf_map_exists"] = True else: msg = result["msg"] msgbox = QMessageBox(QMessageBox.Critical, "Failed to Compute ROIs", msg, QMessageBox.Ok, parent=self) msgbox.exec() self.signal_roi_computation_complete.emit() self.update_global_state.emit() if success: self.signal_activate_tab_xrf_maps.emit() def _update_displayed_element_list(self): element_list = self.gpc.get_roi_selected_element_list() self.le_sel_emission_lines.setText(element_list) self._validate_element_list() self._update_table() def _update_table(self): table_contents = self.gpc.get_roi_settings() self.fill_table(table_contents) def _validate_element_list(self, text=None): if text is None: text = self.le_sel_emission_lines.text() el_list = text.split(",") el_list = [_.strip() for _ in el_list] if el_list == [""]: el_list = [] valid = bool(len(el_list)) for eline in el_list: if self.gpc.get_eline_name_category(eline) != "eline": valid = False self.le_sel_emission_lines.setValid(valid) self.pb_compute_roi.setEnabled(valid) return valid def _compute_in_background(self, func, slot, *args, **kwargs): """ Run function `func` in a background thread. Send the signal `self.computations_complete` once computation is finished. Parameters ---------- func: function Reference to a function that is supposed to be executed at the background. The function return value is passed as a signal parameter once computation is complete. slot: qtpy.QtCore.Slot or None Reference to a slot. If not None, then the signal `self.computation_complete` is connected to this slot. args, kwargs arguments of the function `func`. """ signal_complete = self.computations_complete def func_to_run(func, *args, **kwargs): class LoadFile(QRunnable): def run(self): result_dict = func(*args, **kwargs) signal_complete.emit(result_dict) return LoadFile() if slot is not None: self.computations_complete.connect(slot) self.gui_vars["gui_state"]["running_computations"] = True self.update_global_state.emit() QThreadPool.globalInstance().start(func_to_run(func, *args, **kwargs)) def _recover_after_compute(self, slot): """ The function should be called after the signal `self.computations_complete` is received. The slot should be the same as the one used when calling `self.compute_in_background`. """ if slot is not None: self.computations_complete.disconnect(slot) self.gui_vars["gui_state"]["running_computations"] = False self.update_global_state.emit()
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._("Restore 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(trans._("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.""" response = QMessageBox.question( self, trans._("Restore Shortcuts"), trans._("Are you sure you want to restore default shortcuts?"), QMessageBox.RestoreDefaults | QMessageBox.Cancel, QMessageBox.RestoreDefaults, ) if response == QMessageBox.RestoreDefaults: self._reset_shortcuts() def _reset_shortcuts(self): """Reset shortcuts to default settings.""" get_settings().shortcuts.reset() 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: 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(trans._('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. Parameters ---------- 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 = WarnPopup(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