Skip to content

Commit d26ffde

Browse files
authored
feat: add ConfigGroupsTree (#448)
* feat: add ConfigGroupsTree * starting tests
1 parent e2fa856 commit d26ffde

File tree

7 files changed

+147
-0
lines changed

7 files changed

+147
-0
lines changed

examples/config_groups_tree.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
from pymmcore_plus import CMMCorePlus
2+
from qtpy.QtCore import QModelIndex
3+
from qtpy.QtWidgets import QApplication
4+
5+
from pymmcore_widgets import ConfigGroupsTree
6+
7+
app = QApplication([])
8+
9+
core = CMMCorePlus()
10+
core.loadSystemConfiguration()
11+
12+
tree = ConfigGroupsTree.create_from_core(core)
13+
tree.show()
14+
tree.expandRecursively(QModelIndex())
15+
tree.resize(600, 600)
16+
17+
app.exec()

src/pymmcore_widgets/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
"ChannelGroupWidget",
1515
"ChannelTable",
1616
"ChannelWidget",
17+
"ConfigGroupsTree",
1718
"ConfigWizard",
1819
"ConfigurationWidget",
1920
"CoreLogWidget",
@@ -48,6 +49,7 @@
4849
from ._install_widget import InstallWidget
4950
from ._log import CoreLogWidget
5051
from .config_presets import (
52+
ConfigGroupsTree,
5153
GroupPresetTableWidget,
5254
ObjectivesPixelConfigurationWidget,
5355
PixelConfigurationWidget,

src/pymmcore_widgets/config_presets/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@
44
from ._objectives_pixel_configuration_widget import ObjectivesPixelConfigurationWidget
55
from ._pixel_configuration_widget import PixelConfigurationWidget
66
from ._qmodel._config_model import QConfigGroupsModel
7+
from ._views._config_groups_tree import ConfigGroupsTree
78

89
__all__ = [
10+
"ConfigGroupsTree",
911
"GroupPresetTableWidget",
1012
"ObjectivesPixelConfigurationWidget",
1113
"PixelConfigurationWidget",

src/pymmcore_widgets/config_presets/_views/__init__.py

Whitespace-only changes.
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
from __future__ import annotations
2+
3+
from typing import TYPE_CHECKING
4+
5+
from qtpy.QtWidgets import QTreeView, QWidget
6+
7+
from pymmcore_widgets.config_presets._qmodel._config_model import QConfigGroupsModel
8+
from pymmcore_widgets.config_presets._views._property_setting_delegate import (
9+
PropertySettingDelegate,
10+
)
11+
12+
if TYPE_CHECKING:
13+
from pymmcore_plus import CMMCorePlus
14+
from qtpy.QtCore import QAbstractItemModel
15+
16+
17+
class ConfigGroupsTree(QTreeView):
18+
"""A tree widget that displays configuration groups."""
19+
20+
@classmethod
21+
def create_from_core(
22+
cls, core: CMMCorePlus, parent: QWidget | None = None
23+
) -> ConfigGroupsTree:
24+
"""Create a ConfigGroupsTree from a CMMCorePlus instance."""
25+
obj = cls(parent)
26+
model = QConfigGroupsModel.create_from_core(core)
27+
obj.setModel(model)
28+
return obj
29+
30+
def __init__(self, parent: QWidget | None = None) -> None:
31+
super().__init__(parent)
32+
self.setItemDelegateForColumn(2, PropertySettingDelegate(self))
33+
34+
def setModel(self, model: QAbstractItemModel | None) -> None:
35+
"""Set the model for the tree view."""
36+
super().setModel(model)
37+
if hh := self.header():
38+
for col in range(hh.count()):
39+
hh.setSectionResizeMode(col, hh.ResizeMode.Stretch)
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
from __future__ import annotations
2+
3+
from pymmcore_plus.model import Setting
4+
from qtpy.QtCore import QAbstractItemModel, QModelIndex, Qt
5+
from qtpy.QtWidgets import QStyledItemDelegate, QStyleOptionViewItem, QWidget
6+
7+
from pymmcore_widgets.device_properties import PropertyWidget
8+
9+
10+
class PropertySettingDelegate(QStyledItemDelegate):
11+
"""Item delegate that uses a PropertyWidget for editing PropertySetting values."""
12+
13+
def createEditor(
14+
self, parent: QWidget | None, option: QStyleOptionViewItem, index: QModelIndex
15+
) -> QWidget | None:
16+
if not isinstance((setting := index.data(Qt.ItemDataRole.UserRole)), Setting):
17+
return super().createEditor(parent, option, index) # pragma: no cover
18+
dev, prop, *_ = setting
19+
widget = PropertyWidget(dev, prop, parent=parent, connect_core=False)
20+
widget.setValue(setting.property_value) # avoids commitData warnings
21+
widget.valueChanged.connect(lambda: self.commitData.emit(widget))
22+
widget.setAutoFillBackground(True)
23+
return widget
24+
25+
def setEditorData(self, editor: QWidget | None, index: QModelIndex) -> None:
26+
setting = index.data(Qt.ItemDataRole.UserRole)
27+
if isinstance(setting, Setting) and isinstance(editor, PropertyWidget):
28+
editor.setValue(setting.property_value)
29+
else: # pragma: no cover
30+
super().setEditorData(editor, index)
31+
32+
def setModelData(
33+
self,
34+
editor: QWidget | None,
35+
model: QAbstractItemModel | None,
36+
index: QModelIndex,
37+
) -> None:
38+
if model and isinstance(editor, PropertyWidget):
39+
model.setData(index, editor.value(), Qt.ItemDataRole.EditRole)
40+
else: # pragma: no cover
41+
super().setModelData(editor, model, index)
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
from __future__ import annotations
2+
3+
from typing import TYPE_CHECKING
4+
5+
from pymmcore_plus import CMMCorePlus
6+
7+
from pymmcore_widgets import ConfigGroupsTree
8+
from pymmcore_widgets.config_presets._qmodel._config_model import QConfigGroupsModel
9+
from pymmcore_widgets.config_presets._views._property_setting_delegate import (
10+
PropertySettingDelegate,
11+
)
12+
from pymmcore_widgets.device_properties._property_widget import PropertyWidget
13+
14+
if TYPE_CHECKING:
15+
from pytestqt.qtbot import QtBot
16+
17+
18+
def test_config_groups_tree(qtbot: QtBot) -> None:
19+
core = CMMCorePlus()
20+
core.loadSystemConfiguration()
21+
tree = ConfigGroupsTree.create_from_core(core)
22+
qtbot.addWidget(tree)
23+
tree.show()
24+
model = tree.model()
25+
assert isinstance(model, QConfigGroupsModel)
26+
27+
# test the editor delegate -----------------------------
28+
29+
delegate = tree.itemDelegateForColumn(2)
30+
assert isinstance(delegate, PropertySettingDelegate)
31+
32+
setting_value = model.index(0, 2, model.index(0, 0, model.index(0, 0)))
33+
assert model.data(setting_value) == "1"
34+
35+
# open an editor
36+
tree.edit(setting_value)
37+
editor = tree.focusWidget()
38+
assert isinstance(editor, PropertyWidget)
39+
with qtbot.waitSignal(delegate.commitData):
40+
editor.setValue("2")
41+
42+
# make sure the model is updated
43+
assert model.data(setting_value) == "2"
44+
group0 = model.get_groups()[0]
45+
preset0 = next(iter(group0.presets.values()))
46+
assert preset0.settings[0].property_value == "2"

0 commit comments

Comments
 (0)