bec-widgets 2.18.0__py3-none-any.whl → 2.19.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.
@@ -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,33 @@
1
1
  # CHANGELOG
2
2
 
3
3
 
4
+ ## v2.19.1 (2025-06-23)
5
+
6
+ ### Bug Fixes
7
+
8
+ - **launch_window**: Number of remaining connections extended to 4
9
+ ([`7484f51`](https://github.com/bec-project/bec_widgets/commit/7484f5160c8c6d632fd27996035ff6c0dda2e657))
10
+
11
+
12
+ ## v2.19.0 (2025-06-23)
13
+
14
+ ### Bug Fixes
15
+
16
+ - **ci**: Extend check for pyside import to tests
17
+ ([`d5a40da`](https://github.com/bec-project/bec_widgets/commit/d5a40dabc74753acad05e3eb6b121499fc1e03d7))
18
+
19
+ ### Features
20
+
21
+ - (#494) add signal display to device browser
22
+ ([`f3da6e9`](https://github.com/bec-project/bec_widgets/commit/f3da6e959e0416827ee5d02e34e6ad0ecfc8e5e7))
23
+
24
+ - (#494) add tabbed layout for device item
25
+ ([`3378051`](https://github.com/bec-project/bec_widgets/commit/337805125098c3e028a17b74ef6d9ae4b9ba3d6d))
26
+
27
+ - (#494) display device signals
28
+ ([`3a10341`](https://github.com/bec-project/bec_widgets/commit/3a103410e7448256a56b59bb3276fee056ec42a0))
29
+
30
+
4
31
  ## v2.18.0 (2025-06-22)
5
32
 
6
33
  ### 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.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
@@ -542,7 +542,7 @@ class LaunchWindow(BECMainWindow):
542
542
  remaining_connections = [
543
543
  connection for connection in connections.values() if connection.parent_id != self.gui_id
544
544
  ]
545
- return len(remaining_connections) <= 2
545
+ return len(remaining_connections) <= 4
546
546
 
547
547
  def _turn_off_the_lights(self, connections: dict):
548
548
  """
@@ -0,0 +1,115 @@
1
+ import sys
2
+
3
+ from qtpy.QtCore import QPoint, Qt
4
+ from qtpy.QtWidgets import QApplication, QHBoxLayout, QLabel, QProgressBar, QVBoxLayout, QWidget
5
+
6
+
7
+ class WidgetTooltip(QWidget):
8
+ """Frameless, always-on-top window that behaves like a tooltip."""
9
+
10
+ def __init__(self, content: QWidget) -> None:
11
+ super().__init__(None, Qt.ToolTip | Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint)
12
+ self.setAttribute(Qt.WA_ShowWithoutActivating)
13
+ self.setMouseTracking(True)
14
+ self.content = content
15
+
16
+ layout = QVBoxLayout(self)
17
+ layout.setContentsMargins(6, 6, 6, 6)
18
+ layout.addWidget(self.content)
19
+ self.adjustSize()
20
+
21
+ def leaveEvent(self, _event) -> None:
22
+ self.hide()
23
+
24
+ def show_above(self, global_pos: QPoint, offset: int = 8) -> None:
25
+ self.adjustSize()
26
+ screen = QApplication.screenAt(global_pos) or QApplication.primaryScreen()
27
+ screen_geo = screen.availableGeometry()
28
+ geom = self.geometry()
29
+
30
+ x = global_pos.x() - geom.width() // 2
31
+ y = global_pos.y() - geom.height() - offset
32
+
33
+ x = max(screen_geo.left(), min(x, screen_geo.right() - geom.width()))
34
+ y = max(screen_geo.top(), min(y, screen_geo.bottom() - geom.height()))
35
+
36
+ self.move(x, y)
37
+ self.show()
38
+
39
+
40
+ class HoverWidget(QWidget):
41
+
42
+ def __init__(self, parent: QWidget | None = None, *, simple: QWidget, full: QWidget):
43
+ super().__init__(parent)
44
+ self._simple = simple
45
+ self._full = full
46
+ self._full.setVisible(False)
47
+ self._tooltip = None
48
+
49
+ lay = QVBoxLayout(self)
50
+ lay.setContentsMargins(0, 0, 0, 0)
51
+ lay.addWidget(simple)
52
+
53
+ def enterEvent(self, event):
54
+ # suppress empty-label tooltips for labels
55
+ if isinstance(self._full, QLabel) and not self._full.text():
56
+ return
57
+
58
+ if self._tooltip is None: # first time only
59
+ self._tooltip = WidgetTooltip(self._full)
60
+ self._full.setVisible(True)
61
+
62
+ centre = self.mapToGlobal(self.rect().center())
63
+ self._tooltip.show_above(centre)
64
+ super().enterEvent(event)
65
+
66
+ def leaveEvent(self, event):
67
+ if self._tooltip and self._tooltip.isVisible():
68
+ self._tooltip.hide()
69
+ super().leaveEvent(event)
70
+
71
+ def close(self):
72
+ if self._tooltip:
73
+ self._tooltip.close()
74
+ self._tooltip.deleteLater()
75
+ self._tooltip = None
76
+ super().close()
77
+
78
+
79
+ ################################################################################
80
+ # Demo
81
+ # Just a simple example to show how the HoverWidget can be used to display
82
+ # a tooltip with a full widget inside (two different widgets are used
83
+ # for the simple and full versions).
84
+ ################################################################################
85
+
86
+
87
+ class DemoSimpleWidget(QLabel): # pragma: no cover
88
+ """A simple widget to be used as a trigger for the tooltip."""
89
+
90
+ def __init__(self) -> None:
91
+ super().__init__()
92
+ self.setText("Hover me for a preview!")
93
+
94
+
95
+ class DemoFullWidget(QProgressBar): # pragma: no cover
96
+ """A full widget to be shown in the tooltip."""
97
+
98
+ def __init__(self) -> None:
99
+ super().__init__()
100
+ self.setRange(0, 100)
101
+ self.setValue(75)
102
+ self.setFixedWidth(320)
103
+ self.setFixedHeight(30)
104
+
105
+
106
+ if __name__ == "__main__": # pragma: no cover
107
+ app = QApplication(sys.argv)
108
+
109
+ window = QWidget()
110
+ window.layout = QHBoxLayout(window)
111
+ hover_widget = HoverWidget(simple=DemoSimpleWidget(), full=DemoFullWidget())
112
+ window.layout.addWidget(hover_widget)
113
+ window.show()
114
+
115
+ sys.exit(app.exec_())
@@ -3,15 +3,7 @@ from __future__ import annotations
3
3
  import os
4
4
 
5
5
  from bec_lib.endpoints import MessageEndpoints
6
- from qtpy.QtCore import (
7
- QAbstractAnimation,
8
- QEasingCurve,
9
- QEvent,
10
- QPropertyAnimation,
11
- QSize,
12
- Qt,
13
- QTimer,
14
- )
6
+ from qtpy.QtCore import QEasingCurve, QEvent, QPropertyAnimation, QSize, Qt, QTimer
15
7
  from qtpy.QtGui import QAction, QActionGroup, QIcon
16
8
  from qtpy.QtWidgets import (
17
9
  QApplication,
@@ -30,6 +22,7 @@ from bec_widgets.utils.bec_widget import BECWidget
30
22
  from bec_widgets.utils.colors import apply_theme
31
23
  from bec_widgets.utils.error_popups import SafeSlot
32
24
  from bec_widgets.utils.widget_io import WidgetHierarchy
25
+ from bec_widgets.widgets.containers.main_window.addons.hover_widget import HoverWidget
33
26
  from bec_widgets.widgets.containers.main_window.addons.scroll_label import ScrollLabel
34
27
  from bec_widgets.widgets.containers.main_window.addons.web_links import BECWebLinksMixin
35
28
  from bec_widgets.widgets.progress.scan_progressbar.scan_progressbar import ScanProgressBar
@@ -96,33 +89,60 @@ class BECMainWindow(BECWidget, QMainWindow):
96
89
  self._add_separator()
97
90
 
98
91
  # Centre: Client‑info label (stretch=1 so it expands)
99
- self._client_info_label = ScrollLabel()
92
+ self._add_client_info_label()
93
+
94
+ # Add scan_progress bar with display logic
95
+ self._add_scan_progress_bar()
96
+
97
+ ################################################################################
98
+ # Client message status bar widget helpers
99
+
100
+ def _add_client_info_label(self):
101
+ """
102
+ Add a client info label to the status bar.
103
+ This label will display messages from the BEC dispatcher.
104
+ """
105
+
106
+ # Scroll label for client info in Status Bar
107
+ self._client_info_label = ScrollLabel(self)
100
108
  self._client_info_label.setAlignment(
101
109
  Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignVCenter
102
110
  )
103
- self.status_bar.addWidget(self._client_info_label, 1)
111
+ # Full label used in the hover widget
112
+ self._client_info_label_full = QLabel(self)
113
+ self._client_info_label_full.setAlignment(
114
+ Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignVCenter
115
+ )
116
+ # Hover widget to show the full client info label
117
+ self._client_info_hover = HoverWidget(
118
+ self, simple=self._client_info_label, full=self._client_info_label_full
119
+ )
120
+ self.status_bar.addWidget(self._client_info_hover, 1)
104
121
 
105
122
  # Timer to automatically clear client messages once they expire
106
123
  self._client_info_expire_timer = QTimer(self)
107
124
  self._client_info_expire_timer.setSingleShot(True)
108
125
  self._client_info_expire_timer.timeout.connect(lambda: self._client_info_label.setText(""))
109
-
110
- # Add scan_progress bar with display logic
111
- self._add_scan_progress_bar()
126
+ self._client_info_expire_timer.timeout.connect(
127
+ lambda: self._client_info_label_full.setText("")
128
+ )
112
129
 
113
130
  ################################################################################
114
131
  # Progress‑bar helpers
115
132
  def _add_scan_progress_bar(self):
116
133
 
117
- # --- Progress bar -------------------------------------------------
118
- # Scan progress bar minimalistic design setup
119
- self._scan_progress_bar = ScanProgressBar(self, one_line_design=True)
120
- self._scan_progress_bar.show_elapsed_time = False
121
- self._scan_progress_bar.show_remaining_time = False
122
- self._scan_progress_bar.show_source_label = False
123
- self._scan_progress_bar.progressbar.label_template = ""
124
- self._scan_progress_bar.progressbar.setFixedHeight(8)
125
- self._scan_progress_bar.progressbar.setFixedWidth(80)
134
+ # Setting HoverWidget for the scan progress bar - minimal and full version
135
+ self._scan_progress_bar_simple = ScanProgressBar(self, one_line_design=True)
136
+ self._scan_progress_bar_simple.show_elapsed_time = False
137
+ self._scan_progress_bar_simple.show_remaining_time = False
138
+ self._scan_progress_bar_simple.show_source_label = False
139
+ self._scan_progress_bar_simple.progressbar.label_template = ""
140
+ self._scan_progress_bar_simple.progressbar.setFixedHeight(8)
141
+ self._scan_progress_bar_simple.progressbar.setFixedWidth(80)
142
+ self._scan_progress_bar_full = ScanProgressBar(self)
143
+ self._scan_progress_hover = HoverWidget(
144
+ self, simple=self._scan_progress_bar_simple, full=self._scan_progress_bar_full
145
+ )
126
146
 
127
147
  # Bundle the progress bar with a separator
128
148
  separator = self._add_separator(separate_object=True)
@@ -133,7 +153,7 @@ class BECMainWindow(BECWidget, QMainWindow):
133
153
  self._scan_progress_bar_with_separator.layout.setContentsMargins(0, 0, 0, 0)
134
154
  self._scan_progress_bar_with_separator.layout.setSpacing(0)
135
155
  self._scan_progress_bar_with_separator.layout.addWidget(separator)
136
- self._scan_progress_bar_with_separator.layout.addWidget(self._scan_progress_bar)
156
+ self._scan_progress_bar_with_separator.layout.addWidget(self._scan_progress_hover)
137
157
 
138
158
  # Set Size
139
159
  self._scan_progress_bar_target_width = self.SCAN_PROGRESS_WIDTH
@@ -152,8 +172,8 @@ class BECMainWindow(BECWidget, QMainWindow):
152
172
  self._scan_progress_hide_timer.timeout.connect(self._animate_hide_scan_progress_bar)
153
173
 
154
174
  # Show / hide behaviour
155
- self._scan_progress_bar.progress_started.connect(self._show_scan_progress_bar)
156
- self._scan_progress_bar.progress_finished.connect(self._delay_hide_scan_progress_bar)
175
+ self._scan_progress_bar_simple.progress_started.connect(self._show_scan_progress_bar)
176
+ self._scan_progress_bar_simple.progress_finished.connect(self._delay_hide_scan_progress_bar)
157
177
 
158
178
  def _show_scan_progress_bar(self):
159
179
  if self._scan_progress_hide_timer.isActive():
@@ -342,10 +362,10 @@ class BECMainWindow(BECWidget, QMainWindow):
342
362
  msg(dict): The message to display, should contain:
343
363
  meta(dict): Metadata about the message, usually empty.
344
364
  """
345
- # self._client_info_label.setText("")
346
365
  message = msg.get("message", "")
347
366
  expiration = msg.get("expire", 0) # 0 → never expire
348
367
  self._client_info_label.setText(message)
368
+ self._client_info_label_full.setText(message)
349
369
 
350
370
  # Restart the expiration timer if necessary
351
371
  if hasattr(self, "_client_info_expire_timer") and self._client_info_expire_timer.isActive():
@@ -393,10 +413,20 @@ class BECMainWindow(BECWidget, QMainWindow):
393
413
  if hasattr(self, "_scan_progress_hide_timer") and self._scan_progress_hide_timer.isActive():
394
414
  self._scan_progress_hide_timer.stop()
395
415
 
416
+ ########################################
396
417
  # Status bar widgets cleanup
418
+
419
+ # Client info label cleanup
397
420
  self._client_info_label.cleanup()
398
- self._scan_progress_bar.close()
399
- self._scan_progress_bar.deleteLater()
421
+ self._client_info_hover.close()
422
+ self._client_info_hover.deleteLater()
423
+ # Scan progress bar cleanup
424
+ self._scan_progress_bar_simple.close()
425
+ self._scan_progress_bar_simple.deleteLater()
426
+ self._scan_progress_bar_full.close()
427
+ self._scan_progress_bar_full.deleteLater()
428
+ self._scan_progress_hover.close()
429
+ self._scan_progress_hover.deleteLater()
400
430
  super().cleanup()
401
431
 
402
432
 
@@ -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.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
@@ -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=cgljCv9qwHDRGSH8ZR2OgXnJcjHWFaBJPykgoZPfRGc,308777
6
6
  LICENSE,sha256=Daeiu871NcAp8uYi4eB_qHgvypG-HX0ioRQyQxFwjeg,1531
7
- PKG-INFO,sha256=uLBdC3AdtmHCvBnnCoGIRiKijr8A-2xyDAKNVVCuvYQ,1256
7
+ PKG-INFO,sha256=OWmeyV7UILojOYZKa4X1xvw0OtxLZATOEfDb-RZWXUE,1256
8
8
  README.md,sha256=oY5Jc1uXehRASuwUJ0umin2vfkFh7tHF-LLruHTaQx0,3560
9
- pyproject.toml,sha256=fkRRDfPjHIHnke8FkImzcWn2zwiPeOYhhQE5lzMuUD4,2837
9
+ pyproject.toml,sha256=n4otjWlEQp1vBhXY8Aqi0GQK7KwHlyQ4V8BHQWgWwAE,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
@@ -28,7 +28,7 @@ pyproject.toml,sha256=fkRRDfPjHIHnke8FkImzcWn2zwiPeOYhhQE5lzMuUD4,2837
28
28
  bec_widgets/__init__.py,sha256=mZhbU6zfFt8-A7q_do74ie89budSevwpKZ6FKtEBdmo,170
29
29
  bec_widgets/applications/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
30
30
  bec_widgets/applications/bw_launch.py,sha256=4lngXb8Ht_cvQtCwjjbAoqPNuE2V0Ja5WIPHpwgjcRI,687
31
- bec_widgets/applications/launch_window.py,sha256=dXe4FCWMdTfBmJz0hNaQsZQPGzGIG5ItBgm-ti6Z2Yw,21878
31
+ bec_widgets/applications/launch_window.py,sha256=KoUDoUr3gnZU4G075v4QkPUDW_gSpACwiqARlDD4_wQ,21878
32
32
  bec_widgets/assets/app_icons/BEC-General-App.png,sha256=hc2ktly53DZAbl_rE3cb-vdRa5gtdCmBEjfwm2y5P4g,447581
33
33
  bec_widgets/assets/app_icons/alignment_1d.png,sha256=5VouaWieb4lVv3wUBNHaO5ovUW2Fk25aTKYQzOWy0mg,2071069
34
34
  bec_widgets/assets/app_icons/auto_update.png,sha256=YKoAJTWAlWzreYvOm0BttDRFr29tulolZgKirRhAFlA,2197066
@@ -121,8 +121,9 @@ bec_widgets/widgets/containers/dock/register_dock_area.py,sha256=L7BL4qknCjtqsDP
121
121
  bec_widgets/widgets/containers/layout_manager/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
122
122
  bec_widgets/widgets/containers/layout_manager/layout_manager.py,sha256=V7s8mtB3VLPstyGVaR9YKcoTVlfMMOYNpIJUsw2WQVc,35198
123
123
  bec_widgets/widgets/containers/main_window/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
124
- bec_widgets/widgets/containers/main_window/main_window.py,sha256=qW0AwMTTVAPWMcFUetZ2LAZbwSOEyyWS3_dvzQColvI,15326
124
+ bec_widgets/widgets/containers/main_window/main_window.py,sha256=mYNqS7ncMXZxoxWBjBvgvixB8a1_RyQwOC0dbCz0RY0,16972
125
125
  bec_widgets/widgets/containers/main_window/addons/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
126
+ bec_widgets/widgets/containers/main_window/addons/hover_widget.py,sha256=KO639YbTx2Ru1yzQhB3PS6Y_shS4BGcj64pqPCYdEV8,3644
126
127
  bec_widgets/widgets/containers/main_window/addons/scroll_label.py,sha256=RDiLWDkIrXFAdCXNMm6eE6U9h3zjfPhJDqVwdV8aSL8,3743
127
128
  bec_widgets/widgets/containers/main_window/addons/web_links.py,sha256=d5OgzgI9zb-NAC0pOGanOtJX3nZoe4x8QuQTw-_hK_8,434
128
129
  bec_widgets/widgets/control/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -365,7 +366,8 @@ bec_widgets/widgets/services/device_browser/util.py,sha256=aDtRa53L4-CGn4rM9IKqU
365
366
  bec_widgets/widgets/services/device_browser/device_item/__init__.py,sha256=VGY-uNVCnpcY-q-gijteB2N8KxFNgYR-qQ209MVu1QI,36
366
367
  bec_widgets/widgets/services/device_browser/device_item/device_config_dialog.py,sha256=183LABi4767PptQHy8mfV1I6MFlDGmC1vekFqjtkOuk,8748
367
368
  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
369
+ bec_widgets/widgets/services/device_browser/device_item/device_item.py,sha256=OdBPwIzqKSeeOWZ86sct5d_m17HdIy5aQlCL-Hic754,6135
370
+ bec_widgets/widgets/services/device_browser/device_item/device_signal_display.py,sha256=Olt5AHl7qEHKAiNvjYanA_3ZuZyC0ZMswmlwGGoOPkI,3457
369
371
  bec_widgets/widgets/utility/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
370
372
  bec_widgets/widgets/utility/logpanel/__init__.py,sha256=HldSvPLYgrqBjCgIQj0f7Wa4slkSMksk4bsRJOQi__Y,91
371
373
  bec_widgets/widgets/utility/logpanel/_util.py,sha256=GqzHbdOTmWBru9OR4weeYdziWj_cWxqSJhS4_6W3Qjg,1836
@@ -374,7 +376,7 @@ bec_widgets/widgets/utility/logpanel/log_panel_plugin.py,sha256=KY7eS1uGZzLYtDAd
374
376
  bec_widgets/widgets/utility/logpanel/logpanel.py,sha256=5c59r1Z368mqIZhS_0075P4gg2G1sK5NvPFMK5B1DuQ,20861
375
377
  bec_widgets/widgets/utility/logpanel/register_log_panel.py,sha256=LFUE5JzCYvIwJQtTqZASLVAHYy3gO1nrHzPVH_kpCEY,470
376
378
  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
379
+ bec_widgets/widgets/utility/signal_label/signal_label.py,sha256=ErIyoqKmmga65wBJeXhvWt1FhTdybfkKkqMFzdVl_ig,16571
378
380
  bec_widgets/widgets/utility/signal_label/signal_label.pyproject,sha256=DXgt__AGnPCqXo5A92aTPH0SxbWlvgyNzKraWUuumWg,30
379
381
  bec_widgets/widgets/utility/signal_label/signal_label_plugin.py,sha256=ZXR8oYl4NkPcXJ8pgDcdXcg3teB0MHtoNZoiGDmgCoU,1298
380
382
  bec_widgets/widgets/utility/spinbox/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -418,8 +420,8 @@ bec_widgets/widgets/utility/visual/dark_mode_button/dark_mode_button.py,sha256=O
418
420
  bec_widgets/widgets/utility/visual/dark_mode_button/dark_mode_button.pyproject,sha256=Lbi9zb6HNlIq14k6hlzR-oz6PIFShBuF7QxE6d87d64,34
419
421
  bec_widgets/widgets/utility/visual/dark_mode_button/dark_mode_button_plugin.py,sha256=CzChz2SSETYsR8-36meqWnsXCT-FIy_J_xeU5coWDY8,1350
420
422
  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,,
423
+ bec_widgets-2.19.1.dist-info/METADATA,sha256=OWmeyV7UILojOYZKa4X1xvw0OtxLZATOEfDb-RZWXUE,1256
424
+ bec_widgets-2.19.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
425
+ bec_widgets-2.19.1.dist-info/entry_points.txt,sha256=dItMzmwA1wizJ1Itx15qnfJ0ZzKVYFLVJ1voxT7K7D4,214
426
+ bec_widgets-2.19.1.dist-info/licenses/LICENSE,sha256=Daeiu871NcAp8uYi4eB_qHgvypG-HX0ioRQyQxFwjeg,1531
427
+ bec_widgets-2.19.1.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.1"
8
8
  description = "BEC Widgets"
9
9
  requires-python = ">=3.10"
10
10
  classifiers = [