From 9058d6327a9b2243731efc9c1713a5303fd23aef Mon Sep 17 00:00:00 2001 From: Talley Lambert Date: Mon, 14 Oct 2024 10:43:26 -0400 Subject: [PATCH] Refactor config group widget to config groups editor --- examples/config_group_widget.py | 15 ------- examples/config_groups_editor.py | 21 ++++++++++ src/pymmcore_widgets/__init__.py | 4 +- .../config_presets/__init__.py | 4 +- ...oup_widget.py => _config_groups_editor.py} | 18 ++++---- .../config_presets/_unique_name_list.py | 42 +++++++++---------- 6 files changed, 56 insertions(+), 48 deletions(-) delete mode 100644 examples/config_group_widget.py create mode 100644 examples/config_groups_editor.py rename src/pymmcore_widgets/config_presets/{_config_group_widget.py => _config_groups_editor.py} (96%) diff --git a/examples/config_group_widget.py b/examples/config_group_widget.py deleted file mode 100644 index 1eae1630c..000000000 --- a/examples/config_group_widget.py +++ /dev/null @@ -1,15 +0,0 @@ -from pymmcore_plus import CMMCorePlus -from qtpy.QtWidgets import QApplication - -from pymmcore_widgets import ConfigGroupWidget - -core = CMMCorePlus().instance() -core.loadSystemConfiguration() - -app = QApplication([]) -ocd = ConfigGroupWidget.create_from_core(core) -ocd.resize(1080, 860) -ocd.setCurrentGroup("Channel") -ocd.show() - -app.exec() diff --git a/examples/config_groups_editor.py b/examples/config_groups_editor.py new file mode 100644 index 000000000..4b5a3de62 --- /dev/null +++ b/examples/config_groups_editor.py @@ -0,0 +1,21 @@ +from pymmcore_plus import CMMCorePlus +from qtpy.QtWidgets import QApplication, QPushButton +from rich import print + +from pymmcore_widgets import ConfigGroupsEditor + +core = CMMCorePlus().instance() +core.loadSystemConfiguration() + +app = QApplication([]) + +ocd = ConfigGroupsEditor.create_from_core(core) +ocd.resize(1080, 860) +ocd.setCurrentGroup("Channel") +ocd.show() + +btn = QPushButton("Print Current Data") +btn.clicked.connect(lambda: print(ocd.data())) +ocd.layout().children()[0].addWidget(btn) # Add button to the layout + +app.exec() diff --git a/src/pymmcore_widgets/__init__.py b/src/pymmcore_widgets/__init__.py index a5bcca5f1..eb336e570 100644 --- a/src/pymmcore_widgets/__init__.py +++ b/src/pymmcore_widgets/__init__.py @@ -29,7 +29,7 @@ "MDAWidget", "ObjectivesPixelConfigurationWidget", "ObjectivesWidget", - "ConfigGroupWidget", + "ConfigGroupsEditor", "PixelConfigurationWidget", "PositionTable", "PresetsWidget", @@ -46,7 +46,7 @@ from ._install_widget import InstallWidget from .config_presets import ( - ConfigGroupWidget, + ConfigGroupsEditor, GroupPresetTableWidget, ObjectivesPixelConfigurationWidget, PixelConfigurationWidget, diff --git a/src/pymmcore_widgets/config_presets/__init__.py b/src/pymmcore_widgets/config_presets/__init__.py index 5ccf5df77..b2173cb90 100644 --- a/src/pymmcore_widgets/config_presets/__init__.py +++ b/src/pymmcore_widgets/config_presets/__init__.py @@ -1,6 +1,6 @@ """Widgets related to configuration groups and presets.""" -from ._config_group_widget import ConfigGroupWidget +from ._config_groups_editor import ConfigGroupsEditor from ._group_preset_widget._group_preset_table_widget import GroupPresetTableWidget from ._objectives_pixel_configuration_widget import ObjectivesPixelConfigurationWidget from ._pixel_configuration_widget import PixelConfigurationWidget @@ -9,5 +9,5 @@ "GroupPresetTableWidget", "ObjectivesPixelConfigurationWidget", "PixelConfigurationWidget", - "ConfigGroupWidget", + "ConfigGroupsEditor", ] diff --git a/src/pymmcore_widgets/config_presets/_config_group_widget.py b/src/pymmcore_widgets/config_presets/_config_groups_editor.py similarity index 96% rename from src/pymmcore_widgets/config_presets/_config_group_widget.py rename to src/pymmcore_widgets/config_presets/_config_groups_editor.py index 680222641..e3cf97dfd 100644 --- a/src/pymmcore_widgets/config_presets/_config_group_widget.py +++ b/src/pymmcore_widgets/config_presets/_config_groups_editor.py @@ -12,7 +12,6 @@ QGroupBox, QHBoxLayout, QLabel, - QPushButton, QSpacerItem, QSplitter, QVBoxLayout, @@ -43,7 +42,7 @@ def _copy_named_obj(obj: T, new_name: str) -> T: return obj -class ConfigGroupWidget(QWidget): +class ConfigGroupsEditor(QWidget): """Widget for managing configuration groups and presets. This is a high level widget that allows the user to manage all of the configuration @@ -71,9 +70,6 @@ def __init__( ConfigPreset, clone_function=_copy_named_obj, parent=self, base_key="Config" ) - self.btn_activate = QPushButton("Set Active") - self.presets.btn_layout.insertWidget(3, self.btn_activate) - # Groups ----------------------------------------------------- self._light_path_group = _LightPathGroupBox(self) self._cam_group = _CameraGroupBox(self) @@ -116,13 +112,21 @@ def __init__( self.groups.currentKeyChanged.connect(self._on_current_group_changed) if data is not None: - self.groups.setRoot({group.name: group for group in data}) + self.setData(data) # after groups.setRoot to prevent a bunch of stuff when setting initial data self.presets.currentKeyChanged.connect(self._update_gui_from_model) # Public API ------------------------------------------------------- + def setData(self, data: Iterable[ConfigGroup]) -> None: + """Replace all data with the given collection of ConfigGroup objects.""" + self.groups.setRoot({group.name: group for group in data}) + + def data(self) -> Collection[ConfigGroup]: + """Return the current data as a collection of ConfigGroup objects.""" + return deepcopy(list(self.groups.root().values())) + def setCurrentGroup(self, group: str) -> None: """Set the current group by name. @@ -179,7 +183,7 @@ def setCurrentSettings(self, settings: Iterable[Setting]) -> None: @classmethod def create_from_core( cls, core: CMMCorePlus, parent: QWidget | None = None - ) -> ConfigGroupWidget: + ) -> ConfigGroupsEditor: """Create a new instance and update it with the given core instance.""" groups = ConfigGroup.all_config_groups(core) self = cls(data=groups.values(), parent=parent) diff --git a/src/pymmcore_widgets/config_presets/_unique_name_list.py b/src/pymmcore_widgets/config_presets/_unique_name_list.py index 6b400bd4d..cb1e469b5 100644 --- a/src/pymmcore_widgets/config_presets/_unique_name_list.py +++ b/src/pymmcore_widgets/config_presets/_unique_name_list.py @@ -2,7 +2,7 @@ import warnings from copy import deepcopy -from typing import TYPE_CHECKING, Any, Callable, Generic, TypeVar, overload +from typing import TYPE_CHECKING, Any, Callable, Generic, TypeVar from qtpy.QtCore import Qt, Signal from qtpy.QtWidgets import ( @@ -19,29 +19,10 @@ if TYPE_CHECKING: from collections.abc import Iterable, Mapping, MutableMapping + from PyQt6.QtGui import QKeyEvent -class UniqueListWidget(QListWidget): - @overload - def addItem(self, item: QListWidgetItem | None, /) -> None: ... - @overload - def addItem(self, label: str | None, /) -> None: ... - def addItem(self, item: QListWidgetItem | str | None) -> None: - if item is None: - item = QListWidgetItem() - txt = item.text() if isinstance(item, QListWidgetItem) else item - for i in self._iter_texts(): - if i == txt: - raise ValueError(f"Item with text {txt!r} already exists.") - super().addItem(item) - - def addItems(self, labels: Iterable[str | None]) -> None: - for label in labels: - self.addItem(label) - def _iter_texts(self) -> Iterable[str]: - for i in range(self.count()): - if item := self.item(i): - yield item.text() +class UniqueListWidget(QListWidget): ... class UniqueKeyList(QWidget): @@ -112,6 +93,7 @@ def __init__( self._list_widget.setEditTriggers( QListWidget.EditTrigger.DoubleClicked | QListWidget.EditTrigger.SelectedClicked + | QListWidget.EditTrigger.EditKeyPressed ) self.btn_new = QPushButton("New") @@ -269,6 +251,22 @@ def setBaseKey(self, base_key: str) -> None: """ self._base_key = base_key + # Overrides --------------------------------------------------- + + def keyPressEvent(self, a0: QKeyEvent | None) -> None: + if a0 is None: + return + key = a0.key() + if key in (Qt.Key.Key_Delete, Qt.Key.Key_Backspace): + self._remove_current() + # command-D or control-D should duplicate the current item + elif ( + key == Qt.Key.Key_D and a0.modifiers() & Qt.KeyboardModifier.ControlModifier + ): + self._duplicate_current() + else: + super().keyPressEvent(a0) + # PRIVATE --------------------------------------------------- def _iter_texts(self) -> Iterable[str]: