class PMGListCtrl(BaseExtendedWidget): def __init__(self, layout_dir: str, title: str, initial_value: List[List[str]], new_id_func: Callable = None): super().__init__(layout_dir) self.choices = [] self.text_list = [] lab_title = QLabel(text=title) layout = QHBoxLayout() self.central_layout.addWidget(lab_title) self.on_check_callback = None self.list_widget = QListWidget() self.list_widget.mouseDoubleClickEvent = self.on_listwidget_double_cicked self.set_value(initial_value) layout_tools = QVBoxLayout() self.button_add_item = QPushButton('+') self.button_delete_item = QPushButton('-') self.button_delete_item.clicked.connect(self.delete_row) self.button_add_item.clicked.connect(self.add_row) self.button_add_item.setMaximumWidth(20) self.button_delete_item.setMaximumWidth(20) layout_tools.addWidget(self.button_add_item) layout_tools.addWidget(self.button_delete_item) layout.addLayout(layout_tools) layout.addWidget(self.list_widget) self.central_layout.addLayout(layout) self.data = initial_value self.new_id_func = new_id_func self.text_edit = QLineEdit(parent=self.list_widget) self.text_edit.setWindowFlags(self.text_edit.windowFlags() | Qt.Dialog | Qt.FramelessWindowHint) self.text_edit.hide() self.completer = QCompleter() self.text_edit.setCompleter(self.completer) self.completer.setCompletionMode(QCompleter.UnfilteredPopupCompletion) def set_completions(self, completions: List[str]): """ 设置补全内容 Args: completions: Returns: """ self.completer.setModel(QStringListModel(completions)) def new_id(self): if callable(self.new_id_func): return self.new_id_func() else: return None def add_row(self): self.data = self.get_value() self.data[0].append(self.new_id()) self.data[1].append('Unnamed') self.list_widget.addItem(QListWidgetItem('Unnamed')) def delete_row(self): index = self.list_widget.currentIndex().row() self.data[0].pop(index) self.data[1].pop(index) self.list_widget.takeItem(index) def on_listwidget_double_cicked(self, evt: QMouseEvent): print('edit', evt) pos = evt.globalPos() current_item: QListWidgetItem = self.list_widget.currentItem() def set_value(): current_item.setText(self.text_edit.text()) self.text_edit.hide() self.text_edit.returnPressed.disconnect(set_value) item: QListWidgetItem = self.list_widget.currentItem() self.text_edit.setGeometry(pos.x(), pos.y(), 200, 20) self.text_edit.returnPressed.connect(set_value) self.text_edit.show() # self.list_widget.editItem(item) def get_value(self): text = [] for i in range(self.list_widget.count()): text.append(self.list_widget.item(i).text()) self.data[1] = text assert len(self.data[1]) == len(self.data[0]), repr(self.data) return self.data def set_value(self, data: List[List[str]]): self.list_widget.clear() self.list_widget.addItems(data[1]) self.data = data for index in range(self.list_widget.count()): item: QListWidgetItem = self.list_widget.item(index)
class PreferencesDialog(QDialog): """Preferences Dialog for Napari user settings.""" resized = Signal(QSize) def __init__(self, parent=None): super().__init__(parent) self._list = QListWidget(self) self._stack = QStackedWidget(self) self._list.setObjectName("Preferences") # Set up buttons self._button_cancel = QPushButton(trans._("Cancel")) self._button_ok = QPushButton(trans._("OK")) self._default_restore = QPushButton(trans._("Restore defaults")) # Setup self.setWindowTitle(trans._("Preferences")) # Layout left_layout = QVBoxLayout() left_layout.addWidget(self._list) left_layout.addStretch() left_layout.addWidget(self._default_restore) left_layout.addWidget(self._button_cancel) left_layout.addWidget(self._button_ok) main_layout = QHBoxLayout() main_layout.addLayout(left_layout, 1) main_layout.addWidget(self._stack, 3) self.setLayout(main_layout) # Signals self._list.currentRowChanged.connect( lambda index: self._stack.setCurrentIndex(index)) self._button_cancel.clicked.connect(self.on_click_cancel) self._button_ok.clicked.connect(self.on_click_ok) self._default_restore.clicked.connect(self.restore_defaults) # Make widget self.make_dialog() self._list.setCurrentRow(0) def resizeEvent(self, event): """Override to emit signal.""" self.resized.emit(event.size()) super().resizeEvent(event) def make_dialog(self): """Removes settings not to be exposed to user and creates dialog pages.""" # Because there are multiple pages, need to keep a list of values sets. self._values_orig_set_list = [] self._values_set_list = [] for _key, setting in SETTINGS.schemas().items(): schema = json.loads(setting['json_schema']) # Need to remove certain properties that will not be displayed on the GUI properties = schema.pop('properties') model = setting['model'] values = model.dict() napari_config = getattr(model, "NapariConfig", None) if napari_config is not None: for val in napari_config.preferences_exclude: properties.pop(val) values.pop(val) schema['properties'] = properties self._values_orig_set_list.append(set(values.items())) self._values_set_list.append(set(values.items())) # Only add pages if there are any properties to add. if properties: self.add_page(schema, values) def restore_defaults(self): """Launches dialog to confirm restore settings choice.""" widget = ConfirmDialog( parent=self, text=trans._("Are you sure you want to restore default settings?"), ) widget.valueChanged.connect(self._reset_widgets) widget.exec_() def _reset_widgets(self): """Deletes the widgets and rebuilds with defaults.""" self.close() self._list.clear() for n in range(self._stack.count()): widget = self._stack.removeWidget(self._stack.currentWidget()) del widget self.make_dialog() self._list.setCurrentRow(0) self.show() def on_click_ok(self): """Keeps the selected preferences saved to SETTINGS.""" self.close() def on_click_cancel(self): """Restores the settings in place when dialog was launched.""" # Need to check differences for each page. for n in range(self._stack.count()): # Must set the current row so that the proper set list is updated # in check differences. self._list.setCurrentRow(n) self.check_differences( self._values_orig_set_list[n], self._values_set_list[n], ) self._list.setCurrentRow(0) self.close() def add_page(self, schema, values): """Creates a new page for each section in dialog. Parameters ---------- schema : dict Json schema including all information to build each page in the preferences dialog. values : dict Dictionary of current values set in preferences. """ widget = self.build_page_dialog(schema, values) self._list.addItem(schema["title"]) self._stack.addWidget(widget) def build_page_dialog(self, schema, values): """Builds the preferences widget using the json schema builder. Parameters ---------- schema : dict Json schema including all information to build each page in the preferences dialog. values : dict Dictionary of current values set in preferences. """ builder = WidgetBuilder() form = builder.create_form(schema, {}) # set state values for widget form.widget.state = values form.widget.on_changed.connect(lambda d: self.check_differences( set(d.items()), self._values_set_list[self._list.currentIndex().row()], )) return form def check_differences(self, new_set, values_set): """Changes settings in settings manager with changes from dialog. Parameters ---------- new_set : set The set of new values, with tuples of key value pairs for each setting. values_set : set The old set of values. """ page = self._list.currentItem().text().split(" ")[0].lower() different_values = list(new_set - values_set) if len(different_values) > 0: # change the values in SETTINGS for val in different_values: try: setattr(SETTINGS._settings[page], val[0], val[1]) self._values_set_list[ self._list.currentIndex().row()] = new_set except: # noqa: E722 continue