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 _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 Extension2ReaderTable(QWidget): """Table showing extension to reader mappings with removal button. Widget presented in preferences-plugin dialog.""" valueChanged = Signal(int) def __init__(self, parent=None): super().__init__(parent=parent) self._table = QTableWidget() self._table.setShowGrid(False) self._populate_table() layout = QVBoxLayout() layout.addWidget(self._table) self.setLayout(layout) def _populate_table(self): """Add row for each extension to reader mapping in settings""" self._extension_col = 0 self._reader_col = 1 header_strs = [trans._('Extension'), trans._('Reader Plugin')] self._table.setColumnCount(2) self._table.setColumnWidth(self._extension_col, 100) self._table.setColumnWidth(self._reader_col, 150) self._table.verticalHeader().setVisible(False) self._table.setMinimumHeight(120) extension2reader = get_settings().plugins.extension2reader if len(extension2reader) > 0: self._table.setRowCount(len(extension2reader)) self._table.horizontalHeader().setStretchLastSection(True) self._table.horizontalHeader().setStyleSheet( 'border-bottom: 2px solid white;') self._table.setHorizontalHeaderLabels(header_strs) for row, (extension, plugin_name) in enumerate(extension2reader.items()): item = QTableWidgetItem(extension) item.setFlags(Qt.NoItemFlags) self._table.setItem(row, self._extension_col, item) plugin_widg = QWidget() # need object name to easily find row plugin_widg.setObjectName(f'{extension}') plugin_widg.setLayout(QHBoxLayout()) plugin_widg.layout().setContentsMargins(0, 0, 0, 0) plugin_label = QLabel(plugin_name) # need object name to easily work out which button was clicked remove_btn = QPushButton('x', objectName=f'{extension}') remove_btn.setFixedWidth(30) remove_btn.setStyleSheet('margin: 4px;') remove_btn.setToolTip( trans._('Remove this extension to reader association')) remove_btn.clicked.connect(self._remove_extension_assignment) plugin_widg.layout().addWidget(plugin_label) plugin_widg.layout().addWidget(remove_btn) self._table.setCellWidget(row, self._reader_col, plugin_widg) else: # Display that there are no extensions with reader associations self._table.setRowCount(1) self._table.setHorizontalHeaderLabels(header_strs) self._table.setColumnHidden(self._reader_col, True) self._table.setColumnWidth(self._extension_col, 200) item = QTableWidgetItem(trans._('No extensions found.')) item.setFlags(Qt.NoItemFlags) self._table.setItem(0, 0, item) def _remove_extension_assignment(self, event): """Delete extension to reader mapping setting and remove table row""" extension_to_remove = self.sender().objectName() current_settings = get_settings().plugins.extension2reader # need explicit assignment to new object here for persistence get_settings().plugins.extension2reader = { k: v for k, v in current_settings.items() if k != extension_to_remove } for i in range(self._table.rowCount()): row_widg_name = self._table.cellWidget( i, self._reader_col).objectName() if row_widg_name == extension_to_remove: self._table.removeRow(i) return
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 ShortcutEditor(QWidget): """Widget to edit keybindings for napari.""" valueChanged = Signal(dict) VIEWER_KEYBINDINGS = trans._('Viewer key bindings') def __init__( self, parent: QWidget = None, description: str = "", value: dict = None, ): super().__init__(parent=parent) # Flag to not run _set_keybinding method after setting special symbols. # When changing line edit to special symbols, the _set_keybinding # method will be called again (and breaks) and is not needed. self._skip = False layers = [ Image, Labels, Points, Shapes, Surface, Vectors, ] self.key_bindings_strs = OrderedDict() # widgets self.layer_combo_box = QComboBox(self) self._label = QLabel(self) self._table = QTableWidget(self) self._table.setSelectionBehavior(QAbstractItemView.SelectItems) self._table.setSelectionMode(QAbstractItemView.SingleSelection) self._table.setShowGrid(False) self._restore_button = QPushButton(trans._("Reset All Keybindings")) # Set up dictionary for layers and associated actions. all_actions = action_manager._actions.copy() self.key_bindings_strs[self.VIEWER_KEYBINDINGS] = [] for layer in layers: if len(layer.class_keymap) == 0: actions = [] else: actions = action_manager._get_layer_actions(layer) for name, action in actions.items(): all_actions.pop(name) self.key_bindings_strs[f"{layer.__name__} layer"] = actions # Left over actions can go here. self.key_bindings_strs[self.VIEWER_KEYBINDINGS] = all_actions # Widget set up self.layer_combo_box.addItems(list(self.key_bindings_strs)) self.layer_combo_box.activated[str].connect(self._set_table) self.layer_combo_box.setCurrentText(self.VIEWER_KEYBINDINGS) self._set_table() self._label.setText("Group") self._restore_button.clicked.connect(self.restore_defaults) # layout hlayout1 = QHBoxLayout() hlayout1.addWidget(self._label) hlayout1.addWidget(self.layer_combo_box) hlayout1.setContentsMargins(0, 0, 0, 0) hlayout1.setSpacing(20) hlayout1.addStretch(0) hlayout2 = QHBoxLayout() hlayout2.addLayout(hlayout1) hlayout2.addWidget(self._restore_button) layout = QVBoxLayout() layout.addLayout(hlayout2) layout.addWidget(self._table) self.setLayout(layout) def restore_defaults(self): """Launches dialog to confirm restore choice.""" self._reset_dialog = ConfirmDialog( parent=self, text=trans._( "Are you sure you want to restore default shortcuts?" ), ) self._reset_dialog.valueChanged.connect(self._reset_shortcuts) self._reset_dialog.exec_() def _reset_shortcuts(self, event=None): """Reset shortcuts to default settings. Parameters ---------- event: Bool Event will indicate whether user confirmed resetting shortcuts. """ # event is True if the user confirmed reset shortcuts if event is True: get_settings().reset(sections=['shortcuts']) for ( action, shortcuts, ) in get_settings().shortcuts.shortcuts.items(): action_manager.unbind_shortcut(action) for shortcut in shortcuts: action_manager.bind_shortcut(action, shortcut) self._set_table(layer_str=self.layer_combo_box.currentText()) def _set_table(self, layer_str=''): """Builds and populates keybindings table. Parameters ---------- layer_str = str If layer_str is not empty, then show the specified layers' keybinding shortcut table. """ # Keep track of what is in each column. self._action_name_col = 0 self._icon_col = 1 self._shortcut_col = 2 self._action_col = 3 # Set header strings for table. header_strs = ['', '', '', ''] header_strs[self._action_name_col] = trans._('Action') header_strs[self._shortcut_col] = trans._('Keybinding') # If no layer_str, then set the page to the viewer keybindings page. if layer_str == '': layer_str = self.VIEWER_KEYBINDINGS # If rebuilding the table, then need to disconnect the connection made # previously as well as clear the table contents. try: self._table.cellChanged.disconnect(self._set_keybinding) except TypeError: # if building the first time, the cells are not yet connected so this would fail. pass except RuntimeError: # Needed to pass some tests. pass self._table.clearContents() # Table styling set up. self._table.horizontalHeader().setStretchLastSection(True) self._table.horizontalHeader().setStyleSheet( 'border-bottom: 2px solid white;' ) # Get all actions for the layer. actions = self.key_bindings_strs[layer_str] if len(actions) > 0: # Set up table based on number of actions and needed columns. self._table.setRowCount(len(actions)) self._table.setColumnCount(4) # Set up delegate in order to capture keybindings. self._table.setItemDelegateForColumn( self._shortcut_col, ShortcutDelegate(self._table) ) self._table.setHorizontalHeaderLabels(header_strs) self._table.verticalHeader().setVisible(False) # Hide the column with action names. These are kept here for reference when needed. self._table.setColumnHidden(self._action_col, True) # Column set up. self._table.setColumnWidth(self._action_name_col, 250) self._table.setColumnWidth(self._shortcut_col, 200) self._table.setColumnWidth(self._icon_col, 50) # Go through all the actions in the layer and add them to the table. for row, (action_name, action) in enumerate(actions.items()): shortcuts = action_manager._shortcuts.get(action_name, []) # Set action description. Make sure its not selectable/editable. item = QTableWidgetItem(action.description) item.setFlags(Qt.NoItemFlags) self._table.setItem(row, self._action_name_col, item) # Create empty item in order to make sure this column is not # selectable/editable. item = QTableWidgetItem("") item.setFlags(Qt.NoItemFlags) self._table.setItem(row, self._icon_col, item) # Set the shortcuts in table. item_shortcut = QTableWidgetItem( Shortcut(list(shortcuts)[0]).platform if shortcuts else "" ) self._table.setItem(row, self._shortcut_col, item_shortcut) # action_name is stored in the table to use later, but is not shown on dialog. item_action = QTableWidgetItem(action_name) self._table.setItem(row, self._action_col, item_action) # If a cell is changed, run .set_keybinding. self._table.cellChanged.connect(self._set_keybinding) else: # Display that there are no actions for this layer. self._table.setRowCount(1) self._table.setColumnCount(4) self._table.setHorizontalHeaderLabels(header_strs) self._table.verticalHeader().setVisible(False) self._table.setColumnHidden(self._action_col, True) item = QTableWidgetItem('No key bindings') item.setFlags(Qt.NoItemFlags) self._table.setItem(0, 0, item) def _set_keybinding(self, row, col): """Checks the new keybinding to determine if it can be set. Parameters ---------- row: int Row in keybindings table that is being edited. col: int Column being edited (shortcut column). """ if self._skip is True: # Do nothing if the text is setting to a symbol. # Its already been handled. self._skip = False return if col == self._shortcut_col: # Get all layer actions and viewer actions in order to determine # the new shortcut is not already set to an action. current_layer_text = self.layer_combo_box.currentText() layer_actions = self.key_bindings_strs[current_layer_text] actions_all = layer_actions.copy() if current_layer_text is not self.VIEWER_KEYBINDINGS: viewer_actions = self.key_bindings_strs[ self.VIEWER_KEYBINDINGS ] actions_all.update(viewer_actions) # get the current item from shortcuts column current_item = self._table.currentItem() new_shortcut = current_item.text() new_shortcut = new_shortcut[0].upper() + new_shortcut[1:] # get the current action name current_action = self._table.item(row, self._action_col).text() # get the original shortcutS current_shortcuts = list( action_manager._shortcuts.get(current_action, {}) ) # Flag to indicate whether to set the new shortcut. replace = True # Go through all layer actions to determine if the new shortcut is already here. for row1, (action_name, action) in enumerate(actions_all.items()): shortcuts = action_manager._shortcuts.get(action_name, []) if new_shortcut in shortcuts: # Shortcut is here (either same action or not), don't replace in settings. replace = False if action_name != current_action: # the shortcut is saved to a different action # show warning symbols self._show_warning_icons([row, row1]) # show warning message message = trans._( "The keybinding <b>{new_shortcut}</b> " + "is already assigned to <b>{action_description}</b>; change or clear " + "that shortcut before assigning <b>{new_shortcut}</b> to this one.", new_shortcut=new_shortcut, action_description=action.description, ) self._show_warning(new_shortcut, action, row, message) if len(current_shortcuts) > 0: # If there was a shortcut set originally, then format it and reset the text. format_shortcut = Shortcut( current_shortcuts[0] ).platform if format_shortcut != current_shortcuts[0]: # only skip the next round if there are special symbols self._skip = True current_item.setText(format_shortcut) else: # There wasn't a shortcut here. current_item.setText("") self._cleanup_warning_icons([row, row1]) break else: # This shortcut was here. Reformat and reset text. format_shortcut = Shortcut(new_shortcut).platform if format_shortcut != new_shortcut: # Only skip the next round if there are special symbols in shortcut. self._skip = True current_item.setText(format_shortcut) if replace is True: # This shortcut is not taken. # Unbind current action from shortcuts in action manager. action_manager.unbind_shortcut(current_action) if new_shortcut != "": # Bind the new shortcut. try: action_manager.bind_shortcut( current_action, new_shortcut ) except TypeError: # Shortcut is not valid. action_manager._shortcuts[current_action] = set() # need to rebind the old shortcut action_manager.unbind_shortcut(current_action) action_manager.bind_shortcut( current_action, current_shortcuts[0] ) # Show warning message to let user know this shortcut is invalid. self._show_warning_icons([row]) message = trans._( "<b>{new_shortcut}</b> is not a valid keybinding.", new_shortcut=new_shortcut, ) self._show_warning(new_shortcut, action, row, message) self._cleanup_warning_icons([row]) format_shortcut = Shortcut( current_shortcuts[0] ).platform if format_shortcut != current_shortcuts[0]: # Skip the next round if there are special symbols. self._skip = True # Update text to formated shortcut. current_item.setText(format_shortcut) return # The new shortcut is valid and can be displayed in widget. # Keep track of what changed. new_value_dict = {current_action: [new_shortcut]} # Format new shortcut. format_shortcut = Shortcut(new_shortcut).platform if format_shortcut != new_shortcut: # Skip the next round because there are special symbols. self._skip = True # Update text to formated shortcut. current_item.setText(format_shortcut) else: # There is not a new shortcut to bind. Keep track of it. if action_manager._shortcuts[current_action] != "": new_value_dict = {current_action: [""]} if new_value_dict: # Emit signal when new value set for shortcut. self.valueChanged.emit(new_value_dict) def _show_warning_icons(self, rows): """Creates and displays the warning icons. Parameters ---------- rows: list[int] List of row numbers that should have the icon. """ for row in rows: self.warning_indicator = QLabel(self) self.warning_indicator.setObjectName("error_label") self._table.setCellWidget( row, self._icon_col, self.warning_indicator ) def _cleanup_warning_icons(self, rows): """Remove the warning icons from the shortcut table. Paramters --------- rows: list[int] List of row numbers to remove warning icon from. """ for row in rows: self._table.setCellWidget(row, self._icon_col, QLabel("")) def _show_warning(self, new_shortcut='', action=None, row=0, message=''): """Creates and displays warning message when shortcut is already assigned. Parameters ---------- new_shortcut: str The new shortcut attempting to be set. action: Action Action that is already assigned with the shortcut. row: int Row in table where the shortcut is attempting to be set. message: str Message to be displayed in warning pop up. """ # Determine placement of warning message. delta_y = 105 delta_x = 10 global_point = self.mapToGlobal( QPoint( self._table.columnViewportPosition(self._shortcut_col) + delta_x, self._table.rowViewportPosition(row) + delta_y, ) ) # Create warning pop up and move it to desired position. self._warn_dialog = KeyBindWarnPopup( text=message, ) self._warn_dialog.move(global_point) # Styling adjustments. self._warn_dialog.resize(250, self._warn_dialog.sizeHint().height()) self._warn_dialog._message.resize( 200, self._warn_dialog._message.sizeHint().height() ) self._warn_dialog.exec_() def value(self): """Return the actions and shortcuts currently assigned in action manager. Returns ------- value: dict Dictionary of action names and shortcuts assigned to them. """ value = {} for row, (action_name, action) in enumerate( action_manager._actions.items() ): shortcuts = action_manager._shortcuts.get(action_name, []) value[action_name] = list(shortcuts) return value
class D0(QGroupBox): def __init__(self, parent=None): self._parent = parent super().__init__(parent) self.setTitle("Define d₀") layout = QVBoxLayout() self.d0_grid_switch = QComboBox() self.d0_grid_switch.addItems(["Constant", "Field"]) self.d0_grid_switch.currentTextChanged.connect(self.set_case) layout.addWidget(self.d0_grid_switch) self.d0_box = QWidget() d0_box_layout = QHBoxLayout() d0_box_layout.addWidget(QLabel("d₀")) validator = QDoubleValidator() validator.setBottom(0) self.d0 = QLineEdit() self.d0.setValidator(validator) self.d0.editingFinished.connect(self.update_d0) d0_box_layout.addWidget(self.d0) d0_box_layout.addWidget(QLabel("Δd₀")) self.d0e = QLineEdit() self.d0e.setValidator(validator) self.d0e.editingFinished.connect(self.update_d0) d0_box_layout.addWidget(self.d0e) self.d0_box.setLayout(d0_box_layout) layout.addWidget(self.d0_box) load_save = QWidget() load_save_layout = QHBoxLayout() self.load_grid = QPushButton("Load d₀ Grid") self.load_grid.clicked.connect(self.load_d0_field) load_save_layout.addWidget(self.load_grid) self.save_grid = QPushButton("Save d₀ Grid") self.save_grid.clicked.connect(self.save_d0_field) load_save_layout.addWidget(self.save_grid) load_save.setLayout(load_save_layout) layout.addWidget(load_save) self.d0_grid = QTableWidget() self.d0_grid.setColumnCount(5) self.d0_grid.setColumnWidth(0, 60) self.d0_grid.setColumnWidth(1, 60) self.d0_grid.setColumnWidth(2, 60) self.d0_grid.setColumnWidth(3, 60) self.d0_grid.verticalHeader().setVisible(False) self.d0_grid.horizontalHeader().setStretchLastSection(True) self.d0_grid.setHorizontalHeaderLabels(['vx', 'vy', 'vz', "d₀", "Δd₀"]) spinBoxDelegate = SpinBoxDelegate() self.d0_grid.setItemDelegateForColumn(3, spinBoxDelegate) self.d0_grid.setItemDelegateForColumn(4, spinBoxDelegate) layout.addWidget(self.d0_grid) self.setLayout(layout) self.set_case('Constant') def set_case(self, case): if case == "Constant": self.d0_box.setEnabled(True) self.load_grid.setEnabled(False) self.save_grid.setEnabled(False) self.d0_grid.setEnabled(False) else: self.d0_box.setEnabled(False) self.load_grid.setEnabled(True) self.save_grid.setEnabled(True) self.d0_grid.setEnabled(True) def update_d0(self): self._parent.update_plot() def set_d0(self, d0, d0e): if d0 is None: self.d0.clear() self.d0e.clear() else: self.d0.setText(str(d0)) self.d0e.setText(str(d0e)) def set_d0_field(self, x, y, z, d0, d0e): if x is None: self.d0_grid.clearContents() else: self.d0_grid.setRowCount(len(x)) for n in range(len(x)): x_item = QTableWidgetItem(f'{x[n]: 7.2f}') x_item.setFlags(x_item.flags() ^ Qt.ItemIsEditable) y_item = QTableWidgetItem(f'{y[n]: 7.2f}') y_item.setFlags(y_item.flags() ^ Qt.ItemIsEditable) z_item = QTableWidgetItem(f'{z[n]: 7.2f}') z_item.setFlags(z_item.flags() ^ Qt.ItemIsEditable) d0_item = QTableWidgetItem() d0_item.setData(Qt.EditRole, float(d0[n])) d0e_item = QTableWidgetItem() d0e_item.setData(Qt.EditRole, float(d0e[n])) self.d0_grid.setItem(n, 0, QTableWidgetItem(x_item)) self.d0_grid.setItem(n, 1, QTableWidgetItem(y_item)) self.d0_grid.setItem(n, 2, QTableWidgetItem(z_item)) self.d0_grid.setItem(n, 3, QTableWidgetItem(d0_item)) self.d0_grid.setItem(n, 4, QTableWidgetItem(d0e_item)) def get_d0_field(self): if self.d0_grid.rowCount() == 0: return None else: x = [ float(self.d0_grid.item(row, 0).text()) for row in range(self.d0_grid.rowCount()) ] y = [ float(self.d0_grid.item(row, 1).text()) for row in range(self.d0_grid.rowCount()) ] z = [ float(self.d0_grid.item(row, 2).text()) for row in range(self.d0_grid.rowCount()) ] d0 = [ float(self.d0_grid.item(row, 3).text()) for row in range(self.d0_grid.rowCount()) ] d0e = [ float(self.d0_grid.item(row, 4).text()) for row in range(self.d0_grid.rowCount()) ] return (d0, d0e, x, y, z) def save_d0_field(self): filename, _ = QFileDialog.getSaveFileName( self, "Save d0 Grid", "", "CSV (*.csv);;All Files (*)") if filename: d0, d0e, x, y, z = self.get_d0_field() np.savetxt(filename, np.array([x, y, z, d0, d0e]).T, fmt=['%.4g', '%.4g', '%.4g', '%.9g', '%.9g'], header="vx, vy, vz, d0, d0_error", delimiter=',') def load_d0_field(self): filename, _ = QFileDialog.getOpenFileName( self, "Load d0 Grid", "", "CSV (*.csv);;All Files (*)") if filename: x, y, z, d0, d0e = np.loadtxt(filename, delimiter=',', unpack=True) self.set_d0_field(x, y, z, d0, d0e) def get_d0(self): if self.d0_grid_switch.currentText() == "Constant": try: return (float(self.d0.text()), float(self.d0e.text())) except ValueError: return None else: return self.get_d0_field()
class Extension2ReaderTable(QWidget): """Table showing extension to reader mappings with removal button. Widget presented in preferences-plugin dialog.""" valueChanged = Signal(int) def __init__(self, parent=None, npe2_readers=None, npe1_readers=None): super().__init__(parent=parent) npe2, npe1 = get_all_readers() if npe2_readers is None: npe2_readers = npe2 if npe1_readers is None: npe1_readers = npe1 self._npe2_readers = npe2_readers self._npe1_readers = npe1_readers self._table = QTableWidget() self._table.setShowGrid(False) self._set_up_table() self._edit_row = self._make_new_preference_row() self._populate_table() instructions = QLabel( trans. _('Enter a filename pattern to associate with a reader e.g. "*.tif" for all TIFF files.' ) + trans. _('Available readers will be filtered to those compatible with your pattern. Hover over a reader to see what patterns it accepts.' ) + trans. _('\n\nPreference saving for folder readers is not supported, so these readers are not shown.' ) + trans. _('\n\nFor documentation on valid filename patterns, see https://docs.python.org/3/library/fnmatch.html' )) instructions.setWordWrap(True) instructions.setOpenExternalLinks(True) layout = QVBoxLayout() instructions.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Expanding) layout.addWidget(instructions) layout.addWidget(self._edit_row) layout.addWidget(self._table) self.setLayout(layout) def _set_up_table(self): """Add table columns and headers, define styling""" self._fn_pattern_col = 0 self._reader_col = 1 header_strs = [trans._('Filename Pattern'), trans._('Reader Plugin')] self._table.setColumnCount(2) self._table.setColumnWidth(self._fn_pattern_col, 200) self._table.setColumnWidth(self._reader_col, 200) self._table.verticalHeader().setVisible(False) self._table.setMinimumHeight(120) self._table.horizontalHeader().setStyleSheet( 'border-bottom: 2px solid white;') self._table.setHorizontalHeaderLabels(header_strs) self._table.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) def _populate_table(self): """Add row for each extension to reader mapping in settings""" fnpattern2reader = get_settings().plugins.extension2reader if len(fnpattern2reader) > 0: for fn_pattern, plugin_name in fnpattern2reader.items(): self._add_new_row(fn_pattern, plugin_name) else: # Display that there are no filename patterns with reader associations self._display_no_preferences_found() def _make_new_preference_row(self): """Make row for user to add a new filename pattern assignment""" edit_row_widget = QWidget() edit_row_widget.setLayout(QGridLayout()) edit_row_widget.layout().setContentsMargins(0, 0, 0, 0) self._fn_pattern_edit = QLineEdit() self._fn_pattern_edit.setPlaceholderText( "Start typing filename pattern...") self._fn_pattern_edit.textChanged.connect( self._filter_compatible_readers) add_reader_widg = QWidget() add_reader_widg.setLayout(QHBoxLayout()) add_reader_widg.layout().setContentsMargins(0, 0, 0, 0) self._new_reader_dropdown = QComboBox() for i, (plugin_name, display_name) in enumerate( sorted(dict(self._npe2_readers, **self._npe1_readers).items())): self._add_reader_choice(i, plugin_name, display_name) add_btn = QPushButton('Add') add_btn.setToolTip(trans._('Save reader preference for pattern')) add_btn.clicked.connect(self._save_new_preference) add_reader_widg.layout().addWidget(self._new_reader_dropdown) add_reader_widg.layout().addWidget(add_btn) edit_row_widget.layout().addWidget( self._fn_pattern_edit, 0, 0, ) edit_row_widget.layout().addWidget(add_reader_widg, 0, 1) return edit_row_widget def _display_no_preferences_found(self): self._table.setRowCount(1) item = QTableWidgetItem(trans._('No filename preferences found.')) item.setFlags(Qt.NoItemFlags) self._table.setItem(self._fn_pattern_col, 0, item) def _add_reader_choice(self, i, plugin_name, display_name): """Add dropdown item for plugin_name with reader pattern tooltip""" reader_patterns = get_filename_patterns_for_reader(plugin_name) # TODO: no reader_patterns means directory reader, # we don't support preference association yet if not reader_patterns: return self._new_reader_dropdown.addItem(display_name, plugin_name) if '*' in reader_patterns: tooltip_text = 'Accepts all' else: reader_patterns_formatted = ', '.join(sorted( list(reader_patterns))) tooltip_text = f'Accepts: {reader_patterns_formatted}' self._new_reader_dropdown.setItemData(i, tooltip_text, role=Qt.ToolTipRole) def _filter_compatible_readers(self, new_pattern): """Filter reader dropwdown items to those that accept `new_extension`""" self._new_reader_dropdown.clear() readers = self._npe2_readers.copy() to_delete = [] compatible_readers = get_potential_readers(new_pattern) for plugin_name, display_name in readers.items(): if plugin_name not in compatible_readers: to_delete.append(plugin_name) for reader in to_delete: del readers[reader] readers.update(self._npe1_readers) if not readers: self._new_reader_dropdown.addItem("None available") else: for i, (plugin_name, display_name) in enumerate(sorted(readers.items())): self._add_reader_choice(i, plugin_name, display_name) def _save_new_preference(self, event): """Save current preference to settings and show in table""" fn_pattern = self._fn_pattern_edit.text() reader = self._new_reader_dropdown.currentData() if not fn_pattern or not reader: return # if user types pattern that starts with a . it's probably a file extension so prepend the * if fn_pattern.startswith('.'): fn_pattern = f'*{fn_pattern}' if fn_pattern in get_settings().plugins.extension2reader: self._edit_existing_preference(fn_pattern, reader) else: self._add_new_row(fn_pattern, reader) get_settings().plugins.extension2reader = { **get_settings().plugins.extension2reader, fn_pattern: reader, } def _edit_existing_preference(self, fn_pattern, reader): """Edit existing extension preference""" current_reader_label = self.findChild(QLabel, fn_pattern) if reader in self._npe2_readers: reader = self._npe2_readers[reader] current_reader_label.setText(reader) def _add_new_row(self, fn_pattern, reader): """Add new reader preference to table""" last_row = self._table.rowCount() if (last_row == 1 and 'No filename preferences found' in self._table.item(0, 0).text()): self._table.removeRow(0) last_row = 0 self._table.insertRow(last_row) item = QTableWidgetItem(fn_pattern) item.setFlags(Qt.NoItemFlags) self._table.setItem(last_row, self._fn_pattern_col, item) plugin_widg = QWidget() # need object name to easily find row plugin_widg.setObjectName(f'{fn_pattern}') plugin_widg.setLayout(QHBoxLayout()) plugin_widg.layout().setContentsMargins(0, 0, 0, 0) if reader in self._npe2_readers: reader = self._npe2_readers[reader] plugin_label = QLabel(reader, objectName=fn_pattern) # need object name to easily work out which button was clicked remove_btn = QPushButton('X', objectName=fn_pattern) remove_btn.setFixedWidth(30) remove_btn.setStyleSheet('margin: 4px;') remove_btn.setToolTip( trans._('Remove this filename pattern to reader association')) remove_btn.clicked.connect(self.remove_existing_preference) plugin_widg.layout().addWidget(plugin_label) plugin_widg.layout().addWidget(remove_btn) self._table.setCellWidget(last_row, self._reader_col, plugin_widg) def remove_existing_preference(self, event): """Delete extension to reader mapping setting and remove table row""" pattern_to_remove = self.sender().objectName() current_settings = get_settings().plugins.extension2reader # need explicit assignment to new object here for persistence get_settings().plugins.extension2reader = { k: v for k, v in current_settings.items() if k != pattern_to_remove } for i in range(self._table.rowCount()): row_widg_name = self._table.cellWidget( i, self._reader_col).objectName() if row_widg_name == pattern_to_remove: self._table.removeRow(i) break if self._table.rowCount() == 0: self._display_no_preferences_found() def value(self): """Return extension:reader mapping from settings. Returns ------- Dict[str, str] mapping of extension to reader plugin display name """ return get_settings().plugins.extension2reader