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 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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bec_widgets
3
- Version: 2.15.0
3
+ Version: 2.16.0
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()