bec-widgets 2.18.0__py3-none-any.whl → 2.19.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.
@@ -21,7 +21,7 @@ jobs:
21
21
  isort --check --diff ./
22
22
 
23
23
  - name: Check for disallowed imports from PySide
24
- run: '! grep -re "from PySide6\." bec_widgets/ | grep -v -e "PySide6.QtDesigner" -e "PySide6.scripts"'
24
+ run: '! grep -re "from PySide6\." bec_widgets/ tests/ | grep -v -e "PySide6.QtDesigner" -e "PySide6.scripts"'
25
25
 
26
26
  Pylint:
27
27
  runs-on: ubuntu-latest
CHANGELOG.md CHANGED
@@ -1,6 +1,25 @@
1
1
  # CHANGELOG
2
2
 
3
3
 
4
+ ## v2.19.0 (2025-06-23)
5
+
6
+ ### Bug Fixes
7
+
8
+ - **ci**: Extend check for pyside import to tests
9
+ ([`d5a40da`](https://github.com/bec-project/bec_widgets/commit/d5a40dabc74753acad05e3eb6b121499fc1e03d7))
10
+
11
+ ### Features
12
+
13
+ - (#494) add signal display to device browser
14
+ ([`f3da6e9`](https://github.com/bec-project/bec_widgets/commit/f3da6e959e0416827ee5d02e34e6ad0ecfc8e5e7))
15
+
16
+ - (#494) add tabbed layout for device item
17
+ ([`3378051`](https://github.com/bec-project/bec_widgets/commit/337805125098c3e028a17b74ef6d9ae4b9ba3d6d))
18
+
19
+ - (#494) display device signals
20
+ ([`3a10341`](https://github.com/bec-project/bec_widgets/commit/3a103410e7448256a56b59bb3276fee056ec42a0))
21
+
22
+
4
23
  ## v2.18.0 (2025-06-22)
5
24
 
6
25
  ### Bug Fixes
PKG-INFO CHANGED
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bec_widgets
3
- Version: 2.18.0
3
+ Version: 2.19.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
@@ -8,7 +8,7 @@ from bec_lib.logger import bec_logger
8
8
  from bec_qthemes import material_icon
9
9
  from qtpy.QtCore import QMimeData, QSize, Qt, Signal
10
10
  from qtpy.QtGui import QDrag
11
- from qtpy.QtWidgets import QApplication, QHBoxLayout, QToolButton, QWidget
11
+ from qtpy.QtWidgets import QApplication, QHBoxLayout, QTabWidget, QToolButton, QVBoxLayout, QWidget
12
12
 
13
13
  from bec_widgets.utils.error_popups import SafeSlot
14
14
  from bec_widgets.utils.expandable_frame import ExpandableGroupFrame
@@ -18,6 +18,9 @@ from bec_widgets.widgets.services.device_browser.device_item.device_config_dialo
18
18
  from bec_widgets.widgets.services.device_browser.device_item.device_config_form import (
19
19
  DeviceConfigForm,
20
20
  )
21
+ from bec_widgets.widgets.services.device_browser.device_item.device_signal_display import (
22
+ SignalDisplay,
23
+ )
21
24
 
22
25
  if TYPE_CHECKING: # pragma: no cover
23
26
  from qtpy.QtGui import QMouseEvent
@@ -38,10 +41,25 @@ class DeviceItem(ExpandableGroupFrame):
38
41
  self._expanded_first_time = False
39
42
  self._data = None
40
43
  self.device = device
41
- layout = QHBoxLayout()
42
- layout.setContentsMargins(0, 0, 0, 0)
43
- self.set_layout(layout)
44
44
 
45
+ self._layout = QHBoxLayout()
46
+ self._layout.setContentsMargins(0, 0, 0, 0)
47
+ self._tab_widget = QTabWidget(tabShape=QTabWidget.TabShape.Rounded)
48
+ self._tab_widget.setDocumentMode(True)
49
+ self._layout.addWidget(self._tab_widget)
50
+
51
+ self.set_layout(self._layout)
52
+
53
+ self._form_page = QWidget()
54
+ self._form_page_layout = QVBoxLayout()
55
+ self._form_page.setLayout(self._form_page_layout)
56
+
57
+ self._signal_page = QWidget()
58
+ self._signal_page_layout = QVBoxLayout()
59
+ self._signal_page.setLayout(self._signal_page_layout)
60
+
61
+ self._tab_widget.addTab(self._form_page, "Configuration")
62
+ self._tab_widget.addTab(self._signal_page, "Signals")
45
63
  self.adjustSize()
46
64
 
47
65
  def _create_title_layout(self, title: str, icon: str):
@@ -64,7 +82,9 @@ class DeviceItem(ExpandableGroupFrame):
64
82
  if not self.expanded and not self._expanded_first_time:
65
83
  self._expanded_first_time = True
66
84
  self.form = DeviceConfigForm(parent=self, pretty_display=True)
67
- self._contents.layout().addWidget(self.form)
85
+ self._form_page_layout.addWidget(self.form)
86
+ self.signals = SignalDisplay(parent=self, device=self.device)
87
+ self._signal_page_layout.addWidget(self.signals)
68
88
  self._reload_config()
69
89
  self.broadcast_size_hint.emit(self.sizeHint())
70
90
  super().switch_expanded_state()
@@ -0,0 +1,102 @@
1
+ from bec_qthemes import material_icon
2
+ from qtpy.QtCore import Qt
3
+ from qtpy.QtWidgets import QHBoxLayout, QLabel, QToolButton, QVBoxLayout, QWidget
4
+
5
+ from bec_widgets.utils.bec_connector import ConnectionConfig
6
+ from bec_widgets.utils.bec_widget import BECWidget
7
+ from bec_widgets.utils.error_popups import SafeProperty, SafeSlot
8
+ from bec_widgets.widgets.containers.dock.dock import BECDock
9
+ from bec_widgets.widgets.utility.signal_label.signal_label import SignalLabel
10
+
11
+
12
+ class SignalDisplay(BECWidget, QWidget):
13
+ RPC = False
14
+
15
+ def __init__(
16
+ self,
17
+ client=None,
18
+ device: str = "",
19
+ config: ConnectionConfig = None,
20
+ gui_id: str | None = None,
21
+ theme_update: bool = False,
22
+ parent_dock: BECDock | None = None,
23
+ **kwargs,
24
+ ):
25
+ """A widget to display all the signals from a given device, and allow getting
26
+ a fresh reading."""
27
+ super().__init__(client, config, gui_id, theme_update, parent_dock, **kwargs)
28
+ self.get_bec_shortcuts()
29
+ self._layout = QVBoxLayout()
30
+ self.setLayout(self._layout)
31
+ self._content = QWidget()
32
+ self._layout.addWidget(self._content)
33
+ self._device = device
34
+ self.device = device
35
+
36
+ @SafeSlot()
37
+ def _refresh(self):
38
+ if self.device in self.dev:
39
+ self.dev.get(self.device).read(cached=False)
40
+ self.dev.get(self.device).read_configuration(cached=False)
41
+
42
+ def _add_refresh_button(self):
43
+ button_holder = QWidget()
44
+ button_holder.setLayout(QHBoxLayout())
45
+ button_holder.layout().setAlignment(Qt.AlignmentFlag.AlignRight)
46
+ button_holder.layout().setContentsMargins(0, 0, 0, 0)
47
+ refresh_button = QToolButton()
48
+ refresh_button.setIcon(
49
+ material_icon(icon_name="refresh", size=(20, 20), convert_to_pixmap=False)
50
+ )
51
+ refresh_button.clicked.connect(self._refresh)
52
+ button_holder.layout().addWidget(refresh_button)
53
+ self._content_layout.addWidget(button_holder)
54
+
55
+ def _populate(self):
56
+ self._content.deleteLater()
57
+ self._content = QWidget()
58
+ self._layout.addWidget(self._content)
59
+ self._content_layout = QVBoxLayout()
60
+ self._content_layout.setContentsMargins(0, 0, 0, 0)
61
+ self._content.setLayout(self._content_layout)
62
+
63
+ self._add_refresh_button()
64
+
65
+ if self._device in self.dev:
66
+ for sig in self.dev[self.device]._info.get("signals", {}).keys():
67
+ self._content_layout.addWidget(
68
+ SignalLabel(
69
+ device=self._device,
70
+ signal=sig,
71
+ show_select_button=False,
72
+ show_default_units=True,
73
+ )
74
+ )
75
+ self._content_layout.addStretch(1)
76
+ else:
77
+ self._content_layout.addWidget(
78
+ QLabel(f"Device {self.device} not found in device manager!")
79
+ )
80
+
81
+ @SafeProperty(str)
82
+ def device(self):
83
+ return self._device
84
+
85
+ @device.setter
86
+ def device(self, value: str):
87
+ self._device = value
88
+ self._populate()
89
+
90
+
91
+ if __name__ == "__main__": # pragma: no cover
92
+ import sys
93
+
94
+ from qtpy.QtWidgets import QApplication
95
+
96
+ from bec_widgets.utils.colors import set_theme
97
+
98
+ app = QApplication(sys.argv)
99
+ set_theme("light")
100
+ widget = SignalDisplay(device="samx")
101
+ widget.show()
102
+ sys.exit(app.exec_())
@@ -16,7 +16,6 @@ from qtpy.QtWidgets import (
16
16
  QGroupBox,
17
17
  QHBoxLayout,
18
18
  QLabel,
19
- QLineEdit,
20
19
  QToolButton,
21
20
  QVBoxLayout,
22
21
  QWidget,
@@ -180,6 +179,7 @@ class SignalLabel(BECWidget, QWidget):
180
179
  self._custom_units: str = custom_units
181
180
  self._show_default_units: bool = show_default_units
182
181
  self._decimal_places = 3
182
+ self._dtype = None
183
183
 
184
184
  self._show_hinted_signals: bool = True
185
185
  self._show_normal_signals: bool = False
@@ -241,8 +241,10 @@ class SignalLabel(BECWidget, QWidget):
241
241
  """Subscribe to the Redis topic for the device to display"""
242
242
  if not self._connected and self._device and self._device in self.dev:
243
243
  self._connected = True
244
- self._readback_endpoint = MessageEndpoints.device_readback(self._device)
245
- self.bec_dispatcher.connect_slot(self.on_device_readback, self._readback_endpoint)
244
+ self._read_endpoint = MessageEndpoints.device_read(self._device)
245
+ self._read_config_endpoint = MessageEndpoints.device_read_configuration(self._device)
246
+ self.bec_dispatcher.connect_slot(self.on_device_readback, self._read_endpoint)
247
+ self.bec_dispatcher.connect_slot(self.on_device_readback, self._read_config_endpoint)
246
248
  self._manual_read()
247
249
  self.set_display_value(self._value)
248
250
 
@@ -250,7 +252,8 @@ class SignalLabel(BECWidget, QWidget):
250
252
  """Unsubscribe from the Redis topic for the device to display"""
251
253
  if self._connected:
252
254
  self._connected = False
253
- self.bec_dispatcher.disconnect_slot(self.on_device_readback, self._readback_endpoint)
255
+ self.bec_dispatcher.disconnect_slot(self.on_device_readback, self._read_endpoint)
256
+ self.bec_dispatcher.disconnect_slot(self.on_device_readback, self._read_config_endpoint)
254
257
 
255
258
  def _manual_read(self):
256
259
  if self._device is None or not isinstance(
@@ -259,8 +262,13 @@ class SignalLabel(BECWidget, QWidget):
259
262
  self._units = ""
260
263
  self._value = "__"
261
264
  return
262
- signal: Signal = (
263
- getattr(device, self.signal, None) if isinstance(device, Device) else device
265
+ signal, info = (
266
+ (
267
+ getattr(device, self.signal, None),
268
+ device._info.get("signals", {}).get(self._signal, {}).get("describe", {}),
269
+ )
270
+ if isinstance(device, Device)
271
+ else (device, device.describe().get(self._device))
264
272
  )
265
273
  if not isinstance(signal, Signal): # Avoid getting other attributes of device, e.g. methods
266
274
  signal = None
@@ -269,7 +277,8 @@ class SignalLabel(BECWidget, QWidget):
269
277
  self._value = "__"
270
278
  return
271
279
  self._value = signal.get()
272
- self._units = signal.get_device_config().get("egu", "")
280
+ self._units = info.get("egu", "")
281
+ self._dtype = info.get("dtype", "float")
273
282
 
274
283
  @SafeSlot(dict, dict)
275
284
  def on_device_readback(self, msg: dict, metadata: dict) -> None:
@@ -278,8 +287,10 @@ class SignalLabel(BECWidget, QWidget):
278
287
  """
279
288
  try:
280
289
  signal_to_read = self._patch_hinted_signal()
281
- self._value = msg["signals"][signal_to_read]["value"]
282
- self.set_display_value(self._value)
290
+ _value = msg["signals"].get(signal_to_read, {}).get("value")
291
+ if _value is not None:
292
+ self._value = _value
293
+ self.set_display_value(self._value)
283
294
  except Exception as e:
284
295
  self._display.setText("ERROR!")
285
296
  self._display.setToolTip(
@@ -401,7 +412,10 @@ class SignalLabel(BECWidget, QWidget):
401
412
  if self._decimal_places == 0:
402
413
  return value
403
414
  try:
404
- return f"{float(value):0.{self._decimal_places}f}"
415
+ if self._dtype in ("integer", "float"):
416
+ return f"{float(value):0.{self._decimal_places}f}"
417
+ else:
418
+ return str(value)
405
419
  except ValueError:
406
420
  return value
407
421
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bec_widgets
3
- Version: 2.18.0
3
+ Version: 2.19.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
@@ -2,11 +2,11 @@
2
2
  .gitlab-ci.yml,sha256=1nMYldzVk0tFkBWYTcUjumOrdSADASheWOAc0kOFDYs,9509
3
3
  .pylintrc,sha256=eeY8YwSI74oFfq6IYIbCqnx3Vk8ZncKaatv96n_Y8Rs,18544
4
4
  .readthedocs.yaml,sha256=ivqg3HTaOxNbEW3bzWh9MXAkrekuGoNdj0Mj3SdRYuw,639
5
- CHANGELOG.md,sha256=zfDppLaiLTes_UGwttLvRQ1ztQ8GIcpcHIR2a7ysTxw,307898
5
+ CHANGELOG.md,sha256=TiLkAVElyNh0hZUpQhRMUL2ia_4XUKViiXQ1tXGbUQc,308560
6
6
  LICENSE,sha256=Daeiu871NcAp8uYi4eB_qHgvypG-HX0ioRQyQxFwjeg,1531
7
- PKG-INFO,sha256=uLBdC3AdtmHCvBnnCoGIRiKijr8A-2xyDAKNVVCuvYQ,1256
7
+ PKG-INFO,sha256=FfdkFV0tAjMMbo_ISSEu8ttPkPCUnvuyTKflfEfOibY,1256
8
8
  README.md,sha256=oY5Jc1uXehRASuwUJ0umin2vfkFh7tHF-LLruHTaQx0,3560
9
- pyproject.toml,sha256=fkRRDfPjHIHnke8FkImzcWn2zwiPeOYhhQE5lzMuUD4,2837
9
+ pyproject.toml,sha256=lynGhtdSMDjC80at55Wf9uEKKQFkvXfd6S1Gb20bQ70,2837
10
10
  .git_hooks/pre-commit,sha256=n3RofIZHJl8zfJJIUomcMyYGFi_rwq4CC19z0snz3FI,286
11
11
  .github/pull_request_template.md,sha256=F_cJXzooWMFgMGtLK-7KeGcQt0B4AYFse5oN0zQ9p6g,801
12
12
  .github/ISSUE_TEMPLATE/bug_report.yml,sha256=WdRnt7HGxvsIBLzhkaOWNfg8IJQYa_oV9_F08Ym6znQ,1081
@@ -18,7 +18,7 @@ pyproject.toml,sha256=fkRRDfPjHIHnke8FkImzcWn2zwiPeOYhhQE5lzMuUD4,2837
18
18
  .github/workflows/check_pr.yml,sha256=jKMtYBJTNRAdw_MAS4JzdKVSoNrv8nxUQMqobsYFAXw,903
19
19
  .github/workflows/ci.yml,sha256=OVZt0UxN4wQInuMucuQcKsvHDiz27sLeuQRskxjtkY0,1863
20
20
  .github/workflows/end2end-conda.yml,sha256=yTH-CS8xxQ7kMo4BDpWwOeef1xmsV6gyrgqnFPHTo30,2278
21
- .github/workflows/formatter.yml,sha256=CrYpMfUaZmFFvCk8sK-_7dTtngVxob0Cb1PaJZAZYWw,1918
21
+ .github/workflows/formatter.yml,sha256=yldGjVF8zckPXho9bgN2_YNCq-5ZWN8pxTYrJNDvCbY,1925
22
22
  .github/workflows/generate-cli-check.yml,sha256=b6TcK8F5Hy0sFjgXpk0w3BO9eMDZw9WysTl3P7zEPuQ,1742
23
23
  .github/workflows/pytest-matrix.yml,sha256=0gL5wNPJKJF1JapqstlYNYiJ44ko05uaTD7epa7smVw,1834
24
24
  .github/workflows/pytest.yml,sha256=hYOB7XK_79MaiELaTH7zDT-WRw-pRDe4mHyB_WfcGDc,1747
@@ -365,7 +365,8 @@ bec_widgets/widgets/services/device_browser/util.py,sha256=aDtRa53L4-CGn4rM9IKqU
365
365
  bec_widgets/widgets/services/device_browser/device_item/__init__.py,sha256=VGY-uNVCnpcY-q-gijteB2N8KxFNgYR-qQ209MVu1QI,36
366
366
  bec_widgets/widgets/services/device_browser/device_item/device_config_dialog.py,sha256=183LABi4767PptQHy8mfV1I6MFlDGmC1vekFqjtkOuk,8748
367
367
  bec_widgets/widgets/services/device_browser/device_item/device_config_form.py,sha256=Bw2sxvszs2qPe5WCUV_J4P6iuWSEQc0I37cyr9Wat30,2138
368
- bec_widgets/widgets/services/device_browser/device_item/device_item.py,sha256=LxkeUphf6BjzQadOlyF080TUoPtFdivO7b7c-SjdK94,5251
368
+ bec_widgets/widgets/services/device_browser/device_item/device_item.py,sha256=OdBPwIzqKSeeOWZ86sct5d_m17HdIy5aQlCL-Hic754,6135
369
+ bec_widgets/widgets/services/device_browser/device_item/device_signal_display.py,sha256=Olt5AHl7qEHKAiNvjYanA_3ZuZyC0ZMswmlwGGoOPkI,3457
369
370
  bec_widgets/widgets/utility/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
370
371
  bec_widgets/widgets/utility/logpanel/__init__.py,sha256=HldSvPLYgrqBjCgIQj0f7Wa4slkSMksk4bsRJOQi__Y,91
371
372
  bec_widgets/widgets/utility/logpanel/_util.py,sha256=GqzHbdOTmWBru9OR4weeYdziWj_cWxqSJhS4_6W3Qjg,1836
@@ -374,7 +375,7 @@ bec_widgets/widgets/utility/logpanel/log_panel_plugin.py,sha256=KY7eS1uGZzLYtDAd
374
375
  bec_widgets/widgets/utility/logpanel/logpanel.py,sha256=5c59r1Z368mqIZhS_0075P4gg2G1sK5NvPFMK5B1DuQ,20861
375
376
  bec_widgets/widgets/utility/logpanel/register_log_panel.py,sha256=LFUE5JzCYvIwJQtTqZASLVAHYy3gO1nrHzPVH_kpCEY,470
376
377
  bec_widgets/widgets/utility/signal_label/register_signal_label.py,sha256=wDB4Q3dSbZ51hsxnuB74oXdMRoLgDRd-XfhaomYY2OA,483
377
- bec_widgets/widgets/utility/signal_label/signal_label.py,sha256=bht1zpHKxrslfFCknnLe3Q9FeF8Do0j6onWAiLXZan0,15875
378
+ bec_widgets/widgets/utility/signal_label/signal_label.py,sha256=ErIyoqKmmga65wBJeXhvWt1FhTdybfkKkqMFzdVl_ig,16571
378
379
  bec_widgets/widgets/utility/signal_label/signal_label.pyproject,sha256=DXgt__AGnPCqXo5A92aTPH0SxbWlvgyNzKraWUuumWg,30
379
380
  bec_widgets/widgets/utility/signal_label/signal_label_plugin.py,sha256=ZXR8oYl4NkPcXJ8pgDcdXcg3teB0MHtoNZoiGDmgCoU,1298
380
381
  bec_widgets/widgets/utility/spinbox/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -418,8 +419,8 @@ bec_widgets/widgets/utility/visual/dark_mode_button/dark_mode_button.py,sha256=O
418
419
  bec_widgets/widgets/utility/visual/dark_mode_button/dark_mode_button.pyproject,sha256=Lbi9zb6HNlIq14k6hlzR-oz6PIFShBuF7QxE6d87d64,34
419
420
  bec_widgets/widgets/utility/visual/dark_mode_button/dark_mode_button_plugin.py,sha256=CzChz2SSETYsR8-36meqWnsXCT-FIy_J_xeU5coWDY8,1350
420
421
  bec_widgets/widgets/utility/visual/dark_mode_button/register_dark_mode_button.py,sha256=rMpZ1CaoucwobgPj1FuKTnt07W82bV1GaSYdoqcdMb8,521
421
- bec_widgets-2.18.0.dist-info/METADATA,sha256=uLBdC3AdtmHCvBnnCoGIRiKijr8A-2xyDAKNVVCuvYQ,1256
422
- bec_widgets-2.18.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
423
- bec_widgets-2.18.0.dist-info/entry_points.txt,sha256=dItMzmwA1wizJ1Itx15qnfJ0ZzKVYFLVJ1voxT7K7D4,214
424
- bec_widgets-2.18.0.dist-info/licenses/LICENSE,sha256=Daeiu871NcAp8uYi4eB_qHgvypG-HX0ioRQyQxFwjeg,1531
425
- bec_widgets-2.18.0.dist-info/RECORD,,
422
+ bec_widgets-2.19.0.dist-info/METADATA,sha256=FfdkFV0tAjMMbo_ISSEu8ttPkPCUnvuyTKflfEfOibY,1256
423
+ bec_widgets-2.19.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
424
+ bec_widgets-2.19.0.dist-info/entry_points.txt,sha256=dItMzmwA1wizJ1Itx15qnfJ0ZzKVYFLVJ1voxT7K7D4,214
425
+ bec_widgets-2.19.0.dist-info/licenses/LICENSE,sha256=Daeiu871NcAp8uYi4eB_qHgvypG-HX0ioRQyQxFwjeg,1531
426
+ bec_widgets-2.19.0.dist-info/RECORD,,
pyproject.toml CHANGED
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "bec_widgets"
7
- version = "2.18.0"
7
+ version = "2.19.0"
8
8
  description = "BEC Widgets"
9
9
  requires-python = ">=3.10"
10
10
  classifiers = [