bec-widgets 2.15.1__py3-none-any.whl → 2.16.1__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 CHANGED
@@ -1,6 +1,75 @@
1
1
  # CHANGELOG
2
2
 
3
3
 
4
+ ## v2.16.1 (2025-06-20)
5
+
6
+ ### Bug Fixes
7
+
8
+ - **scatter**: Fix tab order
9
+ ([`235aabf`](https://github.com/bec-project/bec_widgets/commit/235aabf307ef0c01a51a5cd8be4eb53915ed360c))
10
+
11
+
12
+ ## v2.16.0 (2025-06-17)
13
+
14
+ ### Bug Fixes
15
+
16
+ - Adjust height of list widget
17
+ ([`11131ef`](https://github.com/bec-project/bec_widgets/commit/11131ef14c7e8714a4eaf70256da9e5835d60810))
18
+
19
+ - Make website test robust
20
+ ([`4d8c07c`](https://github.com/bec-project/bec_widgets/commit/4d8c07cdd142bab4c0d8224c43e66517a02da7c1))
21
+
22
+ - Parse config on submission and reload after
23
+ ([`5e4c129`](https://github.com/bec-project/bec_widgets/commit/5e4c129af6ae6644e4bb94f4129c6770fd26542d))
24
+
25
+ - Pass on kwargs from PydanticModelForm
26
+ ([`a55f561`](https://github.com/bec-project/bec_widgets/commit/a55f561971a9ce2295cd835cd5cb6ce436d6c693))
27
+
28
+ - Put waiting in thread
29
+ ([`1a350c3`](https://github.com/bec-project/bec_widgets/commit/1a350c3b16da0d990afd53d14934040e5e063177))
30
+
31
+ - Reset dict table properly
32
+ ([`5623547`](https://github.com/bec-project/bec_widgets/commit/5623547e926b86eeb5e2164fa6ec9e36b99b8f63))
33
+
34
+ - Scale dict widget height
35
+ ([`dea2568`](https://github.com/bec-project/bec_widgets/commit/dea2568de370450ca871fe7bf3573eec9acf8122))
36
+
37
+ - Tidy up form widget formatting
38
+ ([`8f4c8e4`](https://github.com/bec-project/bec_widgets/commit/8f4c8e45b3d4a15c67e36cd52d475c3117eca1d3))
39
+
40
+ ### Features
41
+
42
+ - Add a widget to edit lists in forms
43
+ ([`7fc85ba`](https://github.com/bec-project/bec_widgets/commit/7fc85bac7fff8555b73d28eefe9a538540d574b9))
44
+
45
+ - Add set form item
46
+ ([`be73349`](https://github.com/bec-project/bec_widgets/commit/be73349c706582c144813f70dbc477372057de86))
47
+
48
+ - Allow editing device config from browser
49
+ ([`886964b`](https://github.com/bec-project/bec_widgets/commit/886964bb54d2f3923fb6baf198652bb05cf28eb2))
50
+
51
+ - Generate combobox for literal str
52
+ ([`138d4ca`](https://github.com/bec-project/bec_widgets/commit/138d4cabbd50e3c86ab18e9cdc25bbb5cdabc511))
53
+
54
+ ### Performance Improvements
55
+
56
+ - Replace wait with waitUntil
57
+ ([`d626caa`](https://github.com/bec-project/bec_widgets/commit/d626caae3dc71683134cc47073bc131eba4820f5))
58
+
59
+ ### Refactoring
60
+
61
+ - Move device config form to module
62
+ ([`9ce31c9`](https://github.com/bec-project/bec_widgets/commit/9ce31c9833ae38721b2246cdcac50f1154fba99d))
63
+
64
+ - Rename field widgets
65
+ ([`b0d03c0`](https://github.com/bec-project/bec_widgets/commit/b0d03c0648cd365143dfed27d4755d6f5b9c7a45))
66
+
67
+ ### Testing
68
+
69
+ - Add tests for config dialog
70
+ ([`a9613a0`](https://github.com/bec-project/bec_widgets/commit/a9613a07b0cd9cd9455fd996d124c77218c9388f))
71
+
72
+
4
73
  ## v2.15.1 (2025-06-16)
5
74
 
6
75
  ### Bug Fixes
PKG-INFO CHANGED
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bec_widgets
3
- Version: 2.15.1
3
+ Version: 2.16.1
4
4
  Summary: BEC Widgets
5
5
  Project-URL: Bug Tracker, https://gitlab.psi.ch/bec/bec_widgets/issues
6
6
  Project-URL: Homepage, https://gitlab.psi.ch/bec/bec_widgets
@@ -19,7 +19,7 @@ class FakeDevice(BECDevice):
19
19
  "readoutPriority": "baseline",
20
20
  "deviceClass": "ophyd.Device",
21
21
  "deviceConfig": {},
22
- "deviceTags": ["user device"],
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": ["user motors"],
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, QLayout, QSizePolicy, QVBoxLayout, QWidget
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
- form_item_specs
73
- if form_item_specs is not None
74
- else [
75
- FormItemSpec(name=name, item_type=item_type, pretty_display=pretty_display)
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.MinimumExpanding)
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 = widget_from_type(item.item_type)(parent=self, spec=item)
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.MinimumExpanding)
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.MinimumExpanding)
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, form_item_specs=self._form_item_specs(), enabled=enabled, client=client
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()