bec-widgets 2.15.0__py3-none-any.whl → 2.16.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- CHANGELOG.md +72 -0
- PKG-INFO +1 -1
- bec_widgets/tests/utils.py +2 -2
- bec_widgets/utils/expandable_frame.py +12 -7
- bec_widgets/utils/forms_from_types/forms.py +40 -22
- bec_widgets/utils/forms_from_types/items.py +282 -32
- bec_widgets/widgets/containers/main_window/addons/scroll_label.py +24 -3
- bec_widgets/widgets/containers/main_window/main_window.py +32 -1
- bec_widgets/widgets/editors/dict_backed_table.py +69 -9
- bec_widgets/widgets/editors/scan_metadata/_util.py +3 -1
- bec_widgets/widgets/services/device_browser/device_browser.py +5 -6
- bec_widgets/widgets/services/device_browser/device_item/device_config_dialog.py +254 -0
- bec_widgets/widgets/services/device_browser/device_item/device_config_form.py +60 -0
- bec_widgets/widgets/services/device_browser/device_item/device_item.py +52 -54
- bec_widgets/widgets/utility/toggle/toggle.py +9 -0
- {bec_widgets-2.15.0.dist-info → bec_widgets-2.16.0.dist-info}/METADATA +1 -1
- {bec_widgets-2.15.0.dist-info → bec_widgets-2.16.0.dist-info}/RECORD +21 -19
- pyproject.toml +1 -1
- {bec_widgets-2.15.0.dist-info → bec_widgets-2.16.0.dist-info}/WHEEL +0 -0
- {bec_widgets-2.15.0.dist-info → bec_widgets-2.16.0.dist-info}/entry_points.txt +0 -0
- {bec_widgets-2.15.0.dist-info → bec_widgets-2.16.0.dist-info}/licenses/LICENSE +0 -0
CHANGELOG.md
CHANGED
@@ -1,6 +1,78 @@
|
|
1
1
|
# CHANGELOG
|
2
2
|
|
3
3
|
|
4
|
+
## v2.16.0 (2025-06-17)
|
5
|
+
|
6
|
+
### Bug Fixes
|
7
|
+
|
8
|
+
- Adjust height of list widget
|
9
|
+
([`11131ef`](https://github.com/bec-project/bec_widgets/commit/11131ef14c7e8714a4eaf70256da9e5835d60810))
|
10
|
+
|
11
|
+
- Make website test robust
|
12
|
+
([`4d8c07c`](https://github.com/bec-project/bec_widgets/commit/4d8c07cdd142bab4c0d8224c43e66517a02da7c1))
|
13
|
+
|
14
|
+
- Parse config on submission and reload after
|
15
|
+
([`5e4c129`](https://github.com/bec-project/bec_widgets/commit/5e4c129af6ae6644e4bb94f4129c6770fd26542d))
|
16
|
+
|
17
|
+
- Pass on kwargs from PydanticModelForm
|
18
|
+
([`a55f561`](https://github.com/bec-project/bec_widgets/commit/a55f561971a9ce2295cd835cd5cb6ce436d6c693))
|
19
|
+
|
20
|
+
- Put waiting in thread
|
21
|
+
([`1a350c3`](https://github.com/bec-project/bec_widgets/commit/1a350c3b16da0d990afd53d14934040e5e063177))
|
22
|
+
|
23
|
+
- Reset dict table properly
|
24
|
+
([`5623547`](https://github.com/bec-project/bec_widgets/commit/5623547e926b86eeb5e2164fa6ec9e36b99b8f63))
|
25
|
+
|
26
|
+
- Scale dict widget height
|
27
|
+
([`dea2568`](https://github.com/bec-project/bec_widgets/commit/dea2568de370450ca871fe7bf3573eec9acf8122))
|
28
|
+
|
29
|
+
- Tidy up form widget formatting
|
30
|
+
([`8f4c8e4`](https://github.com/bec-project/bec_widgets/commit/8f4c8e45b3d4a15c67e36cd52d475c3117eca1d3))
|
31
|
+
|
32
|
+
### Features
|
33
|
+
|
34
|
+
- Add a widget to edit lists in forms
|
35
|
+
([`7fc85ba`](https://github.com/bec-project/bec_widgets/commit/7fc85bac7fff8555b73d28eefe9a538540d574b9))
|
36
|
+
|
37
|
+
- Add set form item
|
38
|
+
([`be73349`](https://github.com/bec-project/bec_widgets/commit/be73349c706582c144813f70dbc477372057de86))
|
39
|
+
|
40
|
+
- Allow editing device config from browser
|
41
|
+
([`886964b`](https://github.com/bec-project/bec_widgets/commit/886964bb54d2f3923fb6baf198652bb05cf28eb2))
|
42
|
+
|
43
|
+
- Generate combobox for literal str
|
44
|
+
([`138d4ca`](https://github.com/bec-project/bec_widgets/commit/138d4cabbd50e3c86ab18e9cdc25bbb5cdabc511))
|
45
|
+
|
46
|
+
### Performance Improvements
|
47
|
+
|
48
|
+
- Replace wait with waitUntil
|
49
|
+
([`d626caa`](https://github.com/bec-project/bec_widgets/commit/d626caae3dc71683134cc47073bc131eba4820f5))
|
50
|
+
|
51
|
+
### Refactoring
|
52
|
+
|
53
|
+
- Move device config form to module
|
54
|
+
([`9ce31c9`](https://github.com/bec-project/bec_widgets/commit/9ce31c9833ae38721b2246cdcac50f1154fba99d))
|
55
|
+
|
56
|
+
- Rename field widgets
|
57
|
+
([`b0d03c0`](https://github.com/bec-project/bec_widgets/commit/b0d03c0648cd365143dfed27d4755d6f5b9c7a45))
|
58
|
+
|
59
|
+
### Testing
|
60
|
+
|
61
|
+
- Add tests for config dialog
|
62
|
+
([`a9613a0`](https://github.com/bec-project/bec_widgets/commit/a9613a07b0cd9cd9455fd996d124c77218c9388f))
|
63
|
+
|
64
|
+
|
65
|
+
## v2.15.1 (2025-06-16)
|
66
|
+
|
67
|
+
### Bug Fixes
|
68
|
+
|
69
|
+
- **main_window**: Added expiration timer for scroll label for ClientInfoMessage
|
70
|
+
([`187bf49`](https://github.com/bec-project/bec_widgets/commit/187bf493a5b18299a10939901b9ed7e308435092))
|
71
|
+
|
72
|
+
- **scroll_label**: Updating label during scrolling is done imminently, regardless scrolling
|
73
|
+
([`1612933`](https://github.com/bec-project/bec_widgets/commit/1612933dd9689f2bf480ad81811c051201a9ff70))
|
74
|
+
|
75
|
+
|
4
76
|
## v2.15.0 (2025-06-15)
|
5
77
|
|
6
78
|
### Bug Fixes
|
PKG-INFO
CHANGED
bec_widgets/tests/utils.py
CHANGED
@@ -19,7 +19,7 @@ class FakeDevice(BECDevice):
|
|
19
19
|
"readoutPriority": "baseline",
|
20
20
|
"deviceClass": "ophyd.Device",
|
21
21
|
"deviceConfig": {},
|
22
|
-
"deviceTags":
|
22
|
+
"deviceTags": {"user device"},
|
23
23
|
"enabled": enabled,
|
24
24
|
"readOnly": False,
|
25
25
|
"name": self.name,
|
@@ -89,7 +89,7 @@ class FakePositioner(BECPositioner):
|
|
89
89
|
"readoutPriority": "baseline",
|
90
90
|
"deviceClass": "ophyd_devices.SimPositioner",
|
91
91
|
"deviceConfig": {"delay": 1, "tolerance": 0.01, "update_frequency": 400},
|
92
|
-
"deviceTags":
|
92
|
+
"deviceTags": {"user motors"},
|
93
93
|
"enabled": enabled,
|
94
94
|
"readOnly": False,
|
95
95
|
"name": self.name,
|
@@ -37,6 +37,16 @@ class ExpandableGroupFrame(QFrame):
|
|
37
37
|
self._layout.setContentsMargins(0, 0, 0, 0)
|
38
38
|
self.setLayout(self._layout)
|
39
39
|
|
40
|
+
self._create_title_layout(title, icon)
|
41
|
+
|
42
|
+
self._contents = QWidget(self)
|
43
|
+
self._layout.addWidget(self._contents)
|
44
|
+
|
45
|
+
self._expansion_button.clicked.connect(self.switch_expanded_state)
|
46
|
+
self.expanded = self._expanded # type: ignore
|
47
|
+
self.expansion_state_changed.emit()
|
48
|
+
|
49
|
+
def _create_title_layout(self, title: str, icon: str):
|
40
50
|
self._title_layout = QHBoxLayout()
|
41
51
|
self._layout.addLayout(self._title_layout)
|
42
52
|
|
@@ -45,6 +55,8 @@ class ExpandableGroupFrame(QFrame):
|
|
45
55
|
self._title_layout.addWidget(self._title_icon)
|
46
56
|
self._title_layout.addWidget(self._title)
|
47
57
|
self.icon_name = icon
|
58
|
+
self._title.clicked.connect(self.switch_expanded_state)
|
59
|
+
self._title_icon.clicked.connect(self.switch_expanded_state)
|
48
60
|
|
49
61
|
self._title_layout.addStretch(1)
|
50
62
|
|
@@ -52,13 +64,6 @@ class ExpandableGroupFrame(QFrame):
|
|
52
64
|
self._update_expansion_icon()
|
53
65
|
self._title_layout.addWidget(self._expansion_button, stretch=1)
|
54
66
|
|
55
|
-
self._contents = QWidget(self)
|
56
|
-
self._layout.addWidget(self._contents)
|
57
|
-
|
58
|
-
self._expansion_button.clicked.connect(self.switch_expanded_state)
|
59
|
-
self.expanded = self._expanded # type: ignore
|
60
|
-
self.expansion_state_changed.emit()
|
61
|
-
|
62
67
|
def set_layout(self, layout: QLayout) -> None:
|
63
68
|
self._contents.setLayout(layout)
|
64
69
|
self._contents.layout().setContentsMargins(0, 0, 0, 0) # type: ignore
|
@@ -1,6 +1,5 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
-
from decimal import Decimal
|
4
3
|
from types import NoneType
|
5
4
|
from typing import NamedTuple
|
6
5
|
|
@@ -8,11 +7,12 @@ from bec_lib.logger import bec_logger
|
|
8
7
|
from bec_qthemes import material_icon
|
9
8
|
from pydantic import BaseModel, ValidationError
|
10
9
|
from qtpy.QtCore import Signal # type: ignore
|
11
|
-
from qtpy.QtWidgets import QGridLayout, QLabel,
|
10
|
+
from qtpy.QtWidgets import QApplication, QGridLayout, QLabel, QSizePolicy, QVBoxLayout, QWidget
|
12
11
|
|
13
12
|
from bec_widgets.utils.bec_widget import BECWidget
|
14
13
|
from bec_widgets.utils.compact_popup import CompactPopupWidget
|
15
14
|
from bec_widgets.utils.error_popups import SafeProperty
|
15
|
+
from bec_widgets.utils.forms_from_types import styles
|
16
16
|
from bec_widgets.utils.forms_from_types.items import (
|
17
17
|
DynamicFormItem,
|
18
18
|
DynamicFormItemType,
|
@@ -68,15 +68,11 @@ class TypedForm(BECWidget, QWidget):
|
|
68
68
|
logger.error("Must specify one and only one of items and form_item_specs!")
|
69
69
|
items = []
|
70
70
|
super().__init__(parent=parent, client=client, **kwargs)
|
71
|
-
self._items =
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
for name, item_type in items # type: ignore
|
77
|
-
]
|
78
|
-
)
|
79
|
-
self.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding)
|
71
|
+
self._items = form_item_specs or [
|
72
|
+
FormItemSpec(name=name, item_type=item_type, pretty_display=pretty_display)
|
73
|
+
for name, item_type in items # type: ignore
|
74
|
+
]
|
75
|
+
self.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Minimum)
|
80
76
|
self._layout = QVBoxLayout()
|
81
77
|
self._layout.setContentsMargins(0, 0, 0, 0)
|
82
78
|
self.setLayout(self._layout)
|
@@ -84,15 +80,19 @@ class TypedForm(BECWidget, QWidget):
|
|
84
80
|
self._enabled: bool = enabled
|
85
81
|
|
86
82
|
self._form_grid_container = QWidget(parent=self)
|
87
|
-
self._form_grid_container.setSizePolicy(
|
88
|
-
QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding
|
89
|
-
)
|
83
|
+
self._form_grid_container.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Minimum)
|
90
84
|
self._form_grid = QWidget(parent=self._form_grid_container)
|
91
|
-
self._form_grid.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.
|
85
|
+
self._form_grid.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Minimum)
|
92
86
|
self._layout.addWidget(self._form_grid_container)
|
93
87
|
self._form_grid_container.setLayout(QVBoxLayout())
|
94
88
|
self._form_grid.setLayout(self._new_grid_layout())
|
95
89
|
|
90
|
+
self._widget_types: dict | None = None
|
91
|
+
self._widget_from_type = widget_from_type
|
92
|
+
self._post_init()
|
93
|
+
|
94
|
+
def _post_init(self):
|
95
|
+
"""Override this if a subclass should do things after super().__init__ and before populate()"""
|
96
96
|
self.populate()
|
97
97
|
self.enabled = self._enabled # type: ignore # QProperty
|
98
98
|
|
@@ -100,6 +100,8 @@ class TypedForm(BECWidget, QWidget):
|
|
100
100
|
self._clear_grid()
|
101
101
|
for r, item in enumerate(self._items):
|
102
102
|
self._add_griditem(item, r)
|
103
|
+
gl: QGridLayout = self._form_grid.layout()
|
104
|
+
gl.setRowStretch(gl.rowCount(), 1)
|
103
105
|
|
104
106
|
def _add_griditem(self, item: FormItemSpec, row: int):
|
105
107
|
grid = self._form_grid.layout()
|
@@ -107,16 +109,16 @@ class TypedForm(BECWidget, QWidget):
|
|
107
109
|
label.setProperty("_model_field_name", item.name)
|
108
110
|
label.setToolTip(item.info.description or item.name)
|
109
111
|
grid.addWidget(label, row, 0)
|
110
|
-
widget =
|
112
|
+
widget = self._widget_from_type(item, self._widget_types)(parent=self, spec=item)
|
111
113
|
widget.valueChanged.connect(self.value_changed)
|
112
|
-
widget.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.
|
114
|
+
widget.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Minimum)
|
113
115
|
grid.addWidget(widget, row, 1)
|
114
116
|
|
115
117
|
def enumerate_form_widgets(self):
|
116
118
|
"""Return a generator over the rows of the form, with the row number, the label widget (to
|
117
|
-
which the field name is attached as a property), and the entry widget"""
|
119
|
+
which the field name is attached as a property "_model_field_name"), and the entry widget"""
|
118
120
|
grid: QGridLayout = self._form_grid.layout() # type: ignore
|
119
|
-
for i in range(grid.rowCount()):
|
121
|
+
for i in range(grid.rowCount() - 1): # One extra row for stretch
|
120
122
|
yield GridRow(i, grid.itemAtPosition(i, 0).widget(), grid.itemAtPosition(i, 1).widget())
|
121
123
|
|
122
124
|
def _dict_from_grid(self) -> dict[str, DynamicFormItemType]:
|
@@ -135,7 +137,7 @@ class TypedForm(BECWidget, QWidget):
|
|
135
137
|
old_layout.deleteLater()
|
136
138
|
self._form_grid.deleteLater()
|
137
139
|
self._form_grid = QWidget()
|
138
|
-
self._form_grid.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.
|
140
|
+
self._form_grid.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Minimum)
|
139
141
|
self._form_grid.setLayout(self._new_grid_layout())
|
140
142
|
self._form_grid_container.layout().addWidget(self._form_grid)
|
141
143
|
|
@@ -186,14 +188,18 @@ class PydanticModelForm(TypedForm):
|
|
186
188
|
|
187
189
|
Args:
|
188
190
|
data_model (type[BaseModel]): the model class for which to generate a form.
|
189
|
-
enabled (bool): whether fields are enabled for editing.
|
191
|
+
enabled (bool, optional): whether fields are enabled for editing.
|
190
192
|
pretty_display (bool, optional): Whether to use a pretty display for the widget. Defaults to False. If True, disables the widget, doesn't add a clear button, and adapts the stylesheet for non-editable display.
|
191
193
|
|
192
194
|
"""
|
193
195
|
self._pretty_display = pretty_display
|
194
196
|
self._md_schema = data_model
|
195
197
|
super().__init__(
|
196
|
-
parent=parent,
|
198
|
+
parent=parent,
|
199
|
+
form_item_specs=self._form_item_specs(),
|
200
|
+
enabled=enabled,
|
201
|
+
client=client,
|
202
|
+
**kwargs,
|
197
203
|
)
|
198
204
|
|
199
205
|
self._validity = CompactPopupWidget()
|
@@ -207,6 +213,18 @@ class PydanticModelForm(TypedForm):
|
|
207
213
|
self._layout.addWidget(self._validity)
|
208
214
|
self.value_changed.connect(self.validate_form)
|
209
215
|
|
216
|
+
self._connect_to_theme_change()
|
217
|
+
|
218
|
+
def set_pretty_display_theme(self, theme: str = "dark"):
|
219
|
+
if self._pretty_display:
|
220
|
+
self.setStyleSheet(styles.pretty_display_theme(theme))
|
221
|
+
|
222
|
+
def _connect_to_theme_change(self):
|
223
|
+
"""Connect to the theme change signal."""
|
224
|
+
qapp = QApplication.instance()
|
225
|
+
if hasattr(qapp, "theme_signal"):
|
226
|
+
qapp.theme_signal.theme_updated.connect(self.set_pretty_display_theme) # type: ignore
|
227
|
+
|
210
228
|
def set_schema(self, schema: type[BaseModel]):
|
211
229
|
self._md_schema = schema
|
212
230
|
self.populate()
|