bec-widgets 2.11.0__py3-none-any.whl → 2.12.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.
@@ -0,0 +1,21 @@
1
+ import bec_qthemes
2
+
3
+
4
+ def pretty_display_theme(theme: str = "dark"):
5
+ palette = bec_qthemes.load_palette(theme)
6
+ foreground = palette.text().color().name()
7
+ background = palette.base().color().name()
8
+ border = palette.shadow().color().name()
9
+ accent = palette.accent().color().name()
10
+ return f"""
11
+ QWidget {{color: {foreground}; background-color: {background}}}
12
+ QLabel {{ font-weight: bold; }}
13
+ QLineEdit,QLabel,QTreeView {{ border-style: solid; border-width: 2px; border-color: {border} }}
14
+ QRadioButton {{ color: {foreground}; }}
15
+ QRadioButton::indicator::checked {{ color: {accent}; }}
16
+ QCheckBox {{ color: {accent}; }}
17
+ """
18
+
19
+
20
+ if __name__ == "__main__":
21
+ print(pretty_display_theme())
@@ -8,6 +8,9 @@ from qtpy.QtCore import QObject
8
8
  from bec_widgets.utils.name_utils import pascal_to_snake
9
9
 
10
10
  EXCLUDED_PLUGINS = ["BECConnector", "BECDockArea", "BECDock", "BECFigure"]
11
+ _PARENT_ARG_REGEX = r".__init__\(\s*(?:parent\)|parent=parent,?|parent,?)"
12
+ _SELF_PARENT_ARG_REGEX = r".__init__\(\s*self,\s*(?:parent\)|parent=parent,?|parent,?)"
13
+ SUPER_INIT_REGEX = re.compile(r"super\(\)" + _PARENT_ARG_REGEX, re.MULTILINE)
11
14
 
12
15
 
13
16
  class PluginFilenames(NamedTuple):
@@ -90,34 +93,20 @@ class DesignerPluginGenerator:
90
93
 
91
94
  # Check if the widget class calls the super constructor with parent argument
92
95
  init_source = inspect.getsource(self.widget.__init__)
93
- cls_init_found = (
94
- bool(init_source.find(f"{base_cls[0].__name__}.__init__(self, parent=parent") > 0)
95
- or bool(init_source.find(f"{base_cls[0].__name__}.__init__(self, parent)") > 0)
96
- or bool(init_source.find(f"{base_cls[0].__name__}.__init__(self, parent,") > 0)
97
- )
98
- super_init_found = (
99
- bool(
100
- init_source.find(f"super({base_cls[0].__name__}, self).__init__(parent=parent") > 0
101
- )
102
- or bool(init_source.find(f"super({base_cls[0].__name__}, self).__init__(parent,") > 0)
103
- or bool(init_source.find(f"super({base_cls[0].__name__}, self).__init__(parent)") > 0)
96
+ class_re = re.compile(base_cls[0].__name__ + _SELF_PARENT_ARG_REGEX, re.MULTILINE)
97
+ cls_init_found = class_re.search(init_source) is not None
98
+ super_self_re = re.compile(
99
+ rf"super\({base_cls[0].__name__}, self\)" + _PARENT_ARG_REGEX, re.MULTILINE
104
100
  )
101
+ super_init_found = super_self_re.search(init_source) is not None
105
102
  if issubclass(self.widget.__bases__[0], QObject) and not super_init_found:
106
- super_init_found = (
107
- bool(init_source.find("super().__init__(parent=parent") > 0)
108
- or bool(init_source.find("super().__init__(parent,") > 0)
109
- or bool(init_source.find("super().__init__(parent)") > 0)
110
- )
103
+ super_init_found = SUPER_INIT_REGEX.search(init_source) is not None
111
104
 
112
105
  # for the new style classes, we only have one super call. We can therefore check if the
113
106
  # number of __init__ calls is 2 (the class itself and the super class)
114
107
  num_inits = re.findall(r"__init__", init_source)
115
108
  if len(num_inits) == 2 and not super_init_found:
116
- super_init_found = bool(
117
- init_source.find("super().__init__(parent=parent") > 0
118
- or init_source.find("super().__init__(parent,") > 0
119
- or init_source.find("super().__init__(parent)") > 0
120
- )
109
+ super_init_found = SUPER_INIT_REGEX.search(init_source) is not None
121
110
 
122
111
  if not cls_init_found and not super_init_found:
123
112
  raise ValueError(
@@ -89,6 +89,7 @@ class ScanControl(BECWidget, QWidget):
89
89
  self.config.allowed_scans = allowed_scans
90
90
 
91
91
  self._scan_metadata: dict | None = None
92
+ self._metadata_form = ScanMetadata(parent=self)
92
93
 
93
94
  # Create and set main layout
94
95
  self._init_UI()
@@ -165,7 +166,6 @@ class ScanControl(BECWidget, QWidget):
165
166
  self.layout.addStretch()
166
167
 
167
168
  def _add_metadata_form(self):
168
- self._metadata_form = ScanMetadata(parent=self)
169
169
  self.layout.addWidget(self._metadata_form)
170
170
  self._metadata_form.update_with_new_scan(self.comboBox_scan_selection.currentText())
171
171
  self.scan_selected.connect(self._metadata_form.update_with_new_scan)
@@ -2,6 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  from typing import Any
4
4
 
5
+ from qtpy import QtWidgets
5
6
  from qtpy.QtCore import QAbstractTableModel, QModelIndex, Qt, Signal # type: ignore
6
7
  from qtpy.QtWidgets import (
7
8
  QApplication,
@@ -45,7 +46,11 @@ class DictBackedTableModel(QAbstractTableModel):
45
46
 
46
47
  def data(self, index, role=Qt.ItemDataRole):
47
48
  if index.isValid():
48
- if role == Qt.ItemDataRole.DisplayRole or role == Qt.ItemDataRole.EditRole:
49
+ if role in [
50
+ Qt.ItemDataRole.DisplayRole,
51
+ Qt.ItemDataRole.EditRole,
52
+ Qt.ItemDataRole.ToolTipRole,
53
+ ]:
49
54
  return str(self._data[index.row()][index.column()])
50
55
 
51
56
  def setData(self, index, value, role):
@@ -57,6 +62,11 @@ class DictBackedTableModel(QAbstractTableModel):
57
62
  return True
58
63
  return False
59
64
 
65
+ def replaceData(self, data: dict):
66
+ self.resetInternalData()
67
+ self._data = [[k, v] for k, v in data.items()]
68
+ self.dataChanged.emit(self.index(0, 0), self.index(len(self._data), 0))
69
+
60
70
  def update_disallowed_keys(self, keys: list[str]):
61
71
  """Set the list of keys which may not be used.
62
72
 
@@ -110,16 +120,16 @@ class DictBackedTableModel(QAbstractTableModel):
110
120
 
111
121
  class DictBackedTable(QWidget):
112
122
  delete_rows = Signal(list)
113
- data_updated = Signal()
123
+ data_changed = Signal(dict)
114
124
 
115
- def __init__(self, initial_data: list[list[str]]):
125
+ def __init__(self, parent: QWidget | None = None, initial_data: list[list[str]] = []):
116
126
  """Widget which uses a DictBackedTableModel to display an editable table
117
127
  which can be extracted as a dict.
118
128
 
119
129
  Args:
120
130
  initial_data (list[list[str]]): list of key-value pairs to initialise with
121
131
  """
122
- super().__init__()
132
+ super().__init__(parent)
123
133
 
124
134
  self._layout = QHBoxLayout()
125
135
  self.setLayout(self._layout)
@@ -127,13 +137,17 @@ class DictBackedTable(QWidget):
127
137
  self._table_view = QTreeView()
128
138
  self._table_view.setModel(self._table_model)
129
139
  self._table_view.setSizePolicy(
130
- QSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Minimum)
140
+ QSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)
131
141
  )
132
142
  self._table_view.setAlternatingRowColors(True)
143
+ self._table_view.header().setSectionResizeMode(QtWidgets.QHeaderView.ResizeToContents)
144
+ self._table_view.header().setSectionResizeMode(5, QtWidgets.QHeaderView.Stretch)
133
145
  self._layout.addWidget(self._table_view)
134
146
 
147
+ self._button_holder = QWidget()
135
148
  self._buttons = QVBoxLayout()
136
- self._layout.addLayout(self._buttons)
149
+ self._button_holder.setLayout(self._buttons)
150
+ self._layout.addWidget(self._button_holder)
137
151
  self._add_button = QPushButton("+")
138
152
  self._add_button.setToolTip("add a new row")
139
153
  self._remove_button = QPushButton("-")
@@ -143,11 +157,17 @@ class DictBackedTable(QWidget):
143
157
  self._add_button.clicked.connect(self._table_model.add_row)
144
158
  self._remove_button.clicked.connect(self.delete_selected_rows)
145
159
  self.delete_rows.connect(self._table_model.delete_rows)
146
- self._table_model.dataChanged.connect(self._emit_data_updated)
160
+ self._table_model.dataChanged.connect(lambda *_: self.data_changed.emit(self.dump_dict()))
161
+
162
+ def set_button_visibility(self, value: bool):
163
+ self._button_holder.setVisible(value)
164
+
165
+ @SafeSlot()
166
+ def clear(self):
167
+ self._table_model.replaceData({})
147
168
 
148
- def _emit_data_updated(self, *args, **kwargs):
149
- """Just to swallow the args"""
150
- self.data_updated.emit()
169
+ def replace_data(self, data: dict):
170
+ self._table_model.replaceData(data)
151
171
 
152
172
  def delete_selected_rows(self):
153
173
  """Delete rows which are part of the selection model"""
@@ -174,6 +194,6 @@ if __name__ == "__main__": # pragma: no cover
174
194
  app = QApplication([])
175
195
  set_theme("dark")
176
196
 
177
- window = DictBackedTable([["key1", "value1"], ["key2", "value2"], ["key3", "value3"]])
197
+ window = DictBackedTable(None, [["key1", "value1"], ["key2", "value2"], ["key3", "value3"]])
178
198
  window.show()
179
199
  app.exec()
@@ -2,7 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  import sys
4
4
  from decimal import Decimal
5
- from math import inf, nextafter
5
+ from math import copysign, inf, nextafter
6
6
  from typing import TYPE_CHECKING, TypeVar, get_args
7
7
 
8
8
  from annotated_types import Ge, Gt, Le, Lt
@@ -23,16 +23,19 @@ _MAXFLOAT = sys.float_info.max
23
23
  T = TypeVar("T", int, float, Decimal)
24
24
 
25
25
 
26
- def field_limits(info: FieldInfo, type_: type[T]) -> tuple[T, T]:
26
+ def field_limits(info: FieldInfo, type_: type[T], prec: int | None = None) -> tuple[T, T]:
27
+ def _nextafter(x, y):
28
+ return nextafter(x, y) if prec is None else x + (10 ** (-prec)) * (copysign(1, y))
29
+
27
30
  _min = _MININT if type_ is int else _MINFLOAT
28
31
  _max = _MAXINT if type_ is int else _MAXFLOAT
29
32
  for md in info.metadata:
30
33
  if isinstance(md, Ge):
31
34
  _min = type_(md.ge) # type: ignore
32
35
  if isinstance(md, Gt):
33
- _min = type_(md.gt) + 1 if type_ is int else nextafter(type_(md.gt), inf) # type: ignore
36
+ _min = type_(md.gt) + 1 if type_ is int else _nextafter(type_(md.gt), inf) # type: ignore
34
37
  if isinstance(md, Lt):
35
- _max = type_(md.lt) - 1 if type_ is int else nextafter(type_(md.lt), -inf) # type: ignore
38
+ _max = type_(md.lt) - 1 if type_ is int else _nextafter(type_(md.lt), -inf) # type: ignore
36
39
  if isinstance(md, Le):
37
40
  _max = type_(md.le) # type: ignore
38
41
  return _min, _max # type: ignore
@@ -16,6 +16,9 @@ logger = bec_logger.logger
16
16
 
17
17
 
18
18
  class ScanMetadata(PydanticModelForm):
19
+
20
+ RPC = False
21
+
19
22
  def __init__(
20
23
  self,
21
24
  parent=None,
@@ -36,16 +39,18 @@ class ScanMetadata(PydanticModelForm):
36
39
 
37
40
  # self.populate() gets called in super().__init__
38
41
  # so make sure self._additional_metadata exists
39
- self._additional_md_box = ExpandableGroupFrame("Additional metadata", expanded=False)
42
+ self._additional_md_box = ExpandableGroupFrame(
43
+ parent, "Additional metadata", expanded=False
44
+ )
40
45
  self._additional_md_box_layout = QHBoxLayout()
41
46
  self._additional_md_box.set_layout(self._additional_md_box_layout)
42
47
 
43
- self._additional_metadata = DictBackedTable(initial_extras or [])
48
+ self._additional_metadata = DictBackedTable(parent, initial_extras or [])
44
49
  self._scan_name = scan_name or ""
45
50
  self._md_schema = get_metadata_schema_for_scan(self._scan_name)
46
- self._additional_metadata.data_updated.connect(self.validate_form)
51
+ self._additional_metadata.data_changed.connect(self.validate_form)
47
52
 
48
- super().__init__(parent=parent, metadata_model=self._md_schema, client=client, **kwargs)
53
+ super().__init__(parent=parent, data_model=self._md_schema, client=client, **kwargs)
49
54
 
50
55
  self._layout.addWidget(self._additional_md_box)
51
56
  self._additional_md_box_layout.addWidget(self._additional_metadata)
@@ -127,6 +132,7 @@ if __name__ == "__main__": # pragma: no cover
127
132
  w.setLayout(layout)
128
133
 
129
134
  scan_metadata = ScanMetadata(
135
+ parent=w,
130
136
  scan_name="grid_scan",
131
137
  initial_extras=[["key1", "value1"], ["key2", "value2"], ["key3", "value3"]],
132
138
  )
@@ -1,15 +1,22 @@
1
1
  import os
2
2
  import re
3
- from typing import Optional
3
+ from functools import partial
4
4
 
5
5
  from bec_lib.callback_handler import EventType
6
+ from bec_lib.logger import bec_logger
7
+ from bec_lib.messages import ConfigAction
6
8
  from pyqtgraph import SignalProxy
7
- from qtpy.QtCore import Signal, Slot
8
- from qtpy.QtWidgets import QListWidgetItem, QVBoxLayout, QWidget
9
+ from qtpy.QtCore import QSize, Signal
10
+ from qtpy.QtWidgets import QListWidget, QListWidgetItem, QVBoxLayout, QWidget
9
11
 
12
+ from bec_widgets.cli.rpc.rpc_register import RPCRegister
10
13
  from bec_widgets.utils.bec_widget import BECWidget
14
+ from bec_widgets.utils.error_popups import SafeSlot
11
15
  from bec_widgets.utils.ui_loader import UILoader
12
16
  from bec_widgets.widgets.services.device_browser.device_item import DeviceItem
17
+ from bec_widgets.widgets.services.device_browser.util import map_device_type_to_icon
18
+
19
+ logger = bec_logger.logger
13
20
 
14
21
 
15
22
  class DeviceBrowser(BECWidget, QWidget):
@@ -23,18 +30,18 @@ class DeviceBrowser(BECWidget, QWidget):
23
30
 
24
31
  def __init__(
25
32
  self,
26
- parent: Optional[QWidget] = None,
33
+ parent: QWidget | None = None,
27
34
  config=None,
28
35
  client=None,
29
- gui_id: Optional[str] = None,
36
+ gui_id: str | None = None,
30
37
  **kwargs,
31
38
  ) -> None:
32
39
  super().__init__(parent=parent, client=client, gui_id=gui_id, config=config, **kwargs)
33
-
34
40
  self.get_bec_shortcuts()
35
41
  self.ui = None
36
42
  self.ini_ui()
37
-
43
+ self.dev_list: QListWidget = self.ui.device_list
44
+ self.dev_list.setVerticalScrollMode(QListWidget.ScrollMode.ScrollPerPixel)
38
45
  self.proxy_device_update = SignalProxy(
39
46
  self.ui.filter_input.textChanged, rateLimit=500, slot=self.update_device_list
40
47
  )
@@ -43,6 +50,7 @@ class DeviceBrowser(BECWidget, QWidget):
43
50
  )
44
51
  self.device_update.connect(self.update_device_list)
45
52
 
53
+ self.init_device_list()
46
54
  self.update_device_list()
47
55
 
48
56
  def ini_ui(self) -> None:
@@ -50,14 +58,12 @@ class DeviceBrowser(BECWidget, QWidget):
50
58
  Initialize the UI by loading the UI file and setting the layout.
51
59
  """
52
60
  layout = QVBoxLayout()
53
- layout.setContentsMargins(0, 0, 0, 0)
54
-
55
61
  ui_file_path = os.path.join(os.path.dirname(__file__), "device_browser.ui")
56
62
  self.ui = UILoader(self).loader(ui_file_path)
57
63
  layout.addWidget(self.ui)
58
64
  self.setLayout(layout)
59
65
 
60
- def on_device_update(self, action: str, content: dict) -> None:
66
+ def on_device_update(self, action: ConfigAction, content: dict) -> None:
61
67
  """
62
68
  Callback for device update events. Triggers the device_update signal.
63
69
 
@@ -68,8 +74,43 @@ class DeviceBrowser(BECWidget, QWidget):
68
74
  if action in ["add", "remove", "reload"]:
69
75
  self.device_update.emit()
70
76
 
71
- @Slot()
72
- def update_device_list(self) -> None:
77
+ def init_device_list(self):
78
+ self.dev_list.clear()
79
+ self._device_items: dict[str, QListWidgetItem] = {}
80
+
81
+ def _updatesize(item: QListWidgetItem, device_item: DeviceItem):
82
+ device_item.adjustSize()
83
+ item.setSizeHint(QSize(device_item.width(), device_item.height()))
84
+ logger.debug(f"Adjusting {item} size to {device_item.width(), device_item.height()}")
85
+
86
+ with RPCRegister.delayed_broadcast():
87
+ for device, device_obj in self.dev.items():
88
+ item = QListWidgetItem(self.dev_list)
89
+ device_item = DeviceItem(
90
+ parent=self, device=device, icon=map_device_type_to_icon(device_obj)
91
+ )
92
+
93
+ device_item.expansion_state_changed.connect(partial(_updatesize, item, device_item))
94
+
95
+ device_config = self.dev[device]._config # pylint: disable=protected-access
96
+ device_item.set_display_config(device_config)
97
+ tooltip = device_config.get("description", "")
98
+ device_item.setToolTip(tooltip)
99
+ device_item.broadcast_size_hint.connect(item.setSizeHint)
100
+ item.setSizeHint(device_item.sizeHint())
101
+
102
+ self.dev_list.setItemWidget(item, device_item)
103
+ self.dev_list.addItem(item)
104
+ self._device_items[device] = item
105
+
106
+ @SafeSlot()
107
+ def reset_device_list(self) -> None:
108
+ self.init_device_list()
109
+ self.update_device_list()
110
+
111
+ @SafeSlot()
112
+ @SafeSlot(str)
113
+ def update_device_list(self, *_) -> None:
73
114
  """
74
115
  Update the device list based on the filter input.
75
116
  There are two ways to trigger this function:
@@ -80,23 +121,14 @@ class DeviceBrowser(BECWidget, QWidget):
80
121
  """
81
122
  filter_text = self.ui.filter_input.text()
82
123
  try:
83
- regex = re.compile(filter_text, re.IGNORECASE)
124
+ self.regex = re.compile(filter_text, re.IGNORECASE)
84
125
  except re.error:
85
- regex = None # Invalid regex, disable filtering
86
-
87
- dev_list = self.ui.device_list
88
- dev_list.clear()
126
+ self.regex = None # Invalid regex, disable filtering
127
+ for device in self.dev:
128
+ self._device_items[device].setHidden(False)
129
+ return
89
130
  for device in self.dev:
90
- if regex is None or regex.search(device):
91
- item = QListWidgetItem(dev_list)
92
- device_item = DeviceItem(device)
93
-
94
- # pylint: disable=protected-access
95
- tooltip = self.dev[device]._config.get("description", "")
96
- device_item.setToolTip(tooltip)
97
- item.setSizeHint(device_item.sizeHint())
98
- dev_list.setItemWidget(item, device_item)
99
- dev_list.addItem(item)
131
+ self._device_items[device].setHidden(not self.regex.search(device))
100
132
 
101
133
 
102
134
  if __name__ == "__main__": # pragma: no cover
@@ -104,10 +136,10 @@ if __name__ == "__main__": # pragma: no cover
104
136
 
105
137
  from qtpy.QtWidgets import QApplication
106
138
 
107
- from bec_widgets.utils.colors import apply_theme
139
+ from bec_widgets.utils.colors import set_theme
108
140
 
109
141
  app = QApplication(sys.argv)
110
- apply_theme("light")
142
+ set_theme("light")
111
143
  widget = DeviceBrowser()
112
144
  widget.show()
113
145
  sys.exit(app.exec_())
@@ -2,10 +2,18 @@ from __future__ import annotations
2
2
 
3
3
  from typing import TYPE_CHECKING
4
4
 
5
+ from bec_lib.atlas_models import Device as DeviceConfigModel
5
6
  from bec_lib.logger import bec_logger
6
- from qtpy.QtCore import QMimeData, Qt
7
+ from qtpy.QtCore import QMimeData, QSize, Qt, Signal
7
8
  from qtpy.QtGui import QDrag
8
- from qtpy.QtWidgets import QApplication, QHBoxLayout, QLabel, QWidget
9
+ from qtpy.QtWidgets import QApplication, QHBoxLayout, QWidget
10
+
11
+ from bec_widgets.utils.colors import get_theme_name
12
+ from bec_widgets.utils.error_popups import SafeSlot
13
+ from bec_widgets.utils.expandable_frame import ExpandableGroupFrame
14
+ from bec_widgets.utils.forms_from_types import styles
15
+ from bec_widgets.utils.forms_from_types.forms import PydanticModelForm
16
+ from bec_widgets.widgets.utility.visual.dark_mode_button.dark_mode_button import DarkModeButton
9
17
 
10
18
  if TYPE_CHECKING: # pragma: no cover
11
19
  from qtpy.QtGui import QMouseEvent
@@ -13,26 +21,77 @@ if TYPE_CHECKING: # pragma: no cover
13
21
  logger = bec_logger.logger
14
22
 
15
23
 
16
- class DeviceItem(QWidget):
17
- def __init__(self, device: str) -> None:
18
- super().__init__()
24
+ class DeviceItemForm(PydanticModelForm):
25
+ RPC = False
26
+ PLUGIN = False
27
+
28
+ def __init__(self, parent=None, client=None, pretty_display=False, **kwargs):
29
+ super().__init__(
30
+ parent=parent,
31
+ data_model=DeviceConfigModel,
32
+ pretty_display=pretty_display,
33
+ client=client,
34
+ **kwargs,
35
+ )
36
+ self._validity.setVisible(False)
37
+ self._connect_to_theme_change()
38
+
39
+ def set_pretty_display_theme(self, theme: str | None = None):
40
+ if theme is None:
41
+ theme = get_theme_name()
42
+ self.setStyleSheet(styles.pretty_display_theme(theme))
43
+
44
+ def _connect_to_theme_change(self):
45
+ """Connect to the theme change signal."""
46
+ qapp = QApplication.instance()
47
+ if hasattr(qapp, "theme_signal"):
48
+ qapp.theme_signal.theme_updated.connect(self.set_pretty_display_theme) # type: ignore
19
49
 
20
- self._drag_pos = None
21
50
 
51
+ class DeviceItem(ExpandableGroupFrame):
52
+ broadcast_size_hint = Signal(QSize)
53
+
54
+ RPC = False
55
+
56
+ def __init__(self, parent, device: str, icon: str = "") -> None:
57
+ super().__init__(parent, title=device, expanded=False, icon=icon)
58
+
59
+ self._drag_pos = None
60
+ self._expanded_first_time = False
61
+ self._data = None
22
62
  self.device = device
23
63
  layout = QHBoxLayout()
24
- layout.setContentsMargins(10, 2, 10, 2)
25
- self.label = QLabel(device)
26
- layout.addWidget(self.label)
27
- self.setLayout(layout)
28
-
29
- self.setStyleSheet(
30
- """
31
- border: 1px solid #ddd;
32
- border-radius: 5px;
33
- padding: 10px;
34
- """
35
- )
64
+ layout.setContentsMargins(0, 0, 0, 0)
65
+ self.set_layout(layout)
66
+
67
+ self.adjustSize()
68
+ self._title.clicked.connect(self.switch_expanded_state)
69
+ self._title_icon.clicked.connect(self.switch_expanded_state)
70
+
71
+ @SafeSlot()
72
+ def switch_expanded_state(self):
73
+ if not self.expanded and not self._expanded_first_time:
74
+ self._expanded_first_time = True
75
+ self.form = DeviceItemForm(parent=self, pretty_display=True)
76
+ self._contents.layout().addWidget(self.form)
77
+ if self._data:
78
+ self.form.set_data(self._data)
79
+ self.broadcast_size_hint.emit(self.sizeHint())
80
+ super().switch_expanded_state()
81
+ if self._expanded_first_time:
82
+ self.form.adjustSize()
83
+ self.updateGeometry()
84
+ if self._expanded:
85
+ self.form.set_pretty_display_theme()
86
+ self.adjustSize()
87
+ self.broadcast_size_hint.emit(self.sizeHint())
88
+
89
+ def set_display_config(self, config_dict: dict):
90
+ """Set the displayed information from a device config dict, which must conform to the
91
+ bec_lib.atlas_models.Device config model."""
92
+ self._data = DeviceConfigModel.model_validate(config_dict)
93
+ if self._expanded_first_time:
94
+ self.form.set_data(self._data)
36
95
 
37
96
  def mousePressEvent(self, event: QMouseEvent) -> None:
38
97
  super().mousePressEvent(event)
@@ -63,6 +122,25 @@ if __name__ == "__main__": # pragma: no cover
63
122
  from qtpy.QtWidgets import QApplication
64
123
 
65
124
  app = QApplication(sys.argv)
66
- widget = DeviceItem("Device")
125
+ widget = QWidget()
126
+ layout = QHBoxLayout()
127
+ widget.setLayout(layout)
128
+ item = DeviceItem("Device")
129
+ layout.addWidget(DarkModeButton())
130
+ layout.addWidget(item)
131
+ item.set_display_config(
132
+ {
133
+ "name": "Test Device",
134
+ "enabled": True,
135
+ "deviceClass": "FakeDeviceClass",
136
+ "deviceConfig": {"kwarg1": "value1"},
137
+ "readoutPriority": "baseline",
138
+ "description": "A device for testing out a widget",
139
+ "readOnly": True,
140
+ "softwareTrigger": False,
141
+ "deviceTags": ["tag1", "tag2", "tag3"],
142
+ "userParameter": {"some_setting": "some_ value"},
143
+ }
144
+ )
67
145
  widget.show()
68
146
  sys.exit(app.exec_())
@@ -0,0 +1,11 @@
1
+ from bec_lib.device import Device
2
+
3
+
4
+ def map_device_type_to_icon(device_obj: Device) -> str:
5
+ """Associate device types with material icon names"""
6
+ match device_obj._info.get("device_base_class", "").lower():
7
+ case "positioner":
8
+ return "precision_manufacturing"
9
+ case "signal":
10
+ return "vital_signs"
11
+ return "deployed_code"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bec_widgets
3
- Version: 2.11.0
3
+ Version: 2.12.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