bec-widgets 2.17.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.
- .github/workflows/formatter.yml +1 -1
- CHANGELOG.md +49 -0
- PKG-INFO +1 -1
- bec_widgets/tests/utils.py +15 -3
- bec_widgets/utils/filter_io.py +125 -9
- bec_widgets/widgets/control/device_input/base_classes/device_input_base.py +16 -16
- bec_widgets/widgets/control/device_input/base_classes/device_signal_input_base.py +22 -12
- bec_widgets/widgets/control/device_input/device_combobox/device_combobox.py +2 -0
- bec_widgets/widgets/control/device_input/signal_combobox/signal_combobox.py +9 -5
- bec_widgets/widgets/plots/waveform/settings/curve_settings/curve_setting.py +7 -2
- bec_widgets/widgets/plots/waveform/settings/curve_settings/curve_tree.py +62 -19
- bec_widgets/widgets/plots/waveform/waveform.py +0 -1
- bec_widgets/widgets/services/device_browser/device_item/device_item.py +25 -5
- bec_widgets/widgets/services/device_browser/device_item/device_signal_display.py +102 -0
- bec_widgets/widgets/utility/signal_label/signal_label.py +24 -10
- {bec_widgets-2.17.0.dist-info → bec_widgets-2.19.0.dist-info}/METADATA +1 -1
- {bec_widgets-2.17.0.dist-info → bec_widgets-2.19.0.dist-info}/RECORD +21 -20
- pyproject.toml +1 -1
- {bec_widgets-2.17.0.dist-info → bec_widgets-2.19.0.dist-info}/WHEEL +0 -0
- {bec_widgets-2.17.0.dist-info → bec_widgets-2.19.0.dist-info}/entry_points.txt +0 -0
- {bec_widgets-2.17.0.dist-info → bec_widgets-2.19.0.dist-info}/licenses/LICENSE +0 -0
.github/workflows/formatter.yml
CHANGED
@@ -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,55 @@
|
|
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
|
+
|
23
|
+
## v2.18.0 (2025-06-22)
|
24
|
+
|
25
|
+
### Bug Fixes
|
26
|
+
|
27
|
+
- Make settings dialog resizable
|
28
|
+
([`5a564a5`](https://github.com/bec-project/bec_widgets/commit/5a564a5f3f3229e6407ea52a59d3e63319dc214a))
|
29
|
+
|
30
|
+
- **curve settings**: Add initial size hint
|
31
|
+
([`a9708f6`](https://github.com/bec-project/bec_widgets/commit/a9708f6d8f15c42b142488da1e392a8f3179932a))
|
32
|
+
|
33
|
+
### Features
|
34
|
+
|
35
|
+
- **curve settings**: Add combobox selection for device and signal
|
36
|
+
([`eea5f7e`](https://github.com/bec-project/bec_widgets/commit/eea5f7ebbd2b477b3ed19c7efcc76390dd391f26))
|
37
|
+
|
38
|
+
- **device combobox**: Emit reset event if validation fails
|
39
|
+
([`4c2c0c5`](https://github.com/bec-project/bec_widgets/commit/4c2c0c5525d593d8ec7fd554336cb11adbe32de2))
|
40
|
+
|
41
|
+
- **FilterIO**: Add support for item data
|
42
|
+
([`8e8acd6`](https://github.com/bec-project/bec_widgets/commit/8e8acd672c0deb8dcd928886fb574452ac956de7))
|
43
|
+
|
44
|
+
- **signal combobox**: Add reset_selection slot
|
45
|
+
([`b51de1a`](https://github.com/bec-project/bec_widgets/commit/b51de1a00e4b17c44cab23e5097391c6fa8ea0e2))
|
46
|
+
|
47
|
+
### Refactoring
|
48
|
+
|
49
|
+
- **device input**: Refactor to SafeProperty and SafeSlot
|
50
|
+
([`6e2f2ce`](https://github.com/bec-project/bec_widgets/commit/6e2f2cea91ba3af33e9891532506f4b0b65b90c8))
|
51
|
+
|
52
|
+
|
4
53
|
## v2.17.0 (2025-06-22)
|
5
54
|
|
6
55
|
### Bug Fixes
|
PKG-INFO
CHANGED
bec_widgets/tests/utils.py
CHANGED
@@ -96,9 +96,21 @@ class FakePositioner(BECPositioner):
|
|
96
96
|
}
|
97
97
|
self._info = {
|
98
98
|
"signals": {
|
99
|
-
"readback": {
|
100
|
-
|
101
|
-
|
99
|
+
"readback": {
|
100
|
+
"kind_str": "hinted",
|
101
|
+
"component_name": "readback",
|
102
|
+
"obj_name": self.name,
|
103
|
+
}, # hinted
|
104
|
+
"setpoint": {
|
105
|
+
"kind_str": "normal",
|
106
|
+
"component_name": "setpoint",
|
107
|
+
"obj_name": f"{self.name}_setpoint",
|
108
|
+
}, # normal
|
109
|
+
"velocity": {
|
110
|
+
"kind_str": "config",
|
111
|
+
"component_name": "velocity",
|
112
|
+
"obj_name": f"{self.name}_velocity",
|
113
|
+
}, # config
|
102
114
|
}
|
103
115
|
}
|
104
116
|
self.signals = {
|
bec_widgets/utils/filter_io.py
CHANGED
@@ -8,6 +8,8 @@ from bec_lib.logger import bec_logger
|
|
8
8
|
from qtpy.QtCore import QStringListModel
|
9
9
|
from qtpy.QtWidgets import QComboBox, QCompleter, QLineEdit
|
10
10
|
|
11
|
+
from bec_widgets.utils.ophyd_kind_util import Kind
|
12
|
+
|
11
13
|
logger = bec_logger.logger
|
12
14
|
|
13
15
|
|
@@ -15,11 +17,13 @@ class WidgetFilterHandler(ABC):
|
|
15
17
|
"""Abstract base class for widget filter handlers"""
|
16
18
|
|
17
19
|
@abstractmethod
|
18
|
-
def set_selection(self, widget, selection: list) -> None:
|
20
|
+
def set_selection(self, widget, selection: list[str | tuple]) -> None:
|
19
21
|
"""Set the filtered_selection for the widget
|
20
22
|
|
21
23
|
Args:
|
22
|
-
|
24
|
+
widget: Widget instance
|
25
|
+
selection (list[str | tuple]): Filtered selection of items.
|
26
|
+
If tuple, it contains (text, data) pairs.
|
23
27
|
"""
|
24
28
|
|
25
29
|
@abstractmethod
|
@@ -34,17 +38,37 @@ class WidgetFilterHandler(ABC):
|
|
34
38
|
bool: True if the input text is in the filtered selection
|
35
39
|
"""
|
36
40
|
|
41
|
+
@abstractmethod
|
42
|
+
def update_with_kind(
|
43
|
+
self, kind: Kind, signal_filter: set, device_info: dict, device_name: str
|
44
|
+
) -> list[str | tuple]:
|
45
|
+
"""Update the selection based on the kind of signal.
|
46
|
+
|
47
|
+
Args:
|
48
|
+
kind (Kind): The kind of signal to filter.
|
49
|
+
signal_filter (set): Set of signal kinds to filter.
|
50
|
+
device_info (dict): Dictionary containing device information.
|
51
|
+
device_name (str): Name of the device.
|
52
|
+
|
53
|
+
Returns:
|
54
|
+
list[str | tuple]: A list of filtered signals based on the kind.
|
55
|
+
"""
|
56
|
+
# This method should be implemented in subclasses or extended as needed
|
57
|
+
|
37
58
|
|
38
59
|
class LineEditFilterHandler(WidgetFilterHandler):
|
39
60
|
"""Handler for QLineEdit widget"""
|
40
61
|
|
41
|
-
def set_selection(self, widget: QLineEdit, selection: list) -> None:
|
62
|
+
def set_selection(self, widget: QLineEdit, selection: list[str | tuple]) -> None:
|
42
63
|
"""Set the selection for the widget to the completer model
|
43
64
|
|
44
65
|
Args:
|
45
66
|
widget (QLineEdit): The QLineEdit widget
|
46
|
-
selection (list): Filtered selection of items
|
67
|
+
selection (list[str | tuple]): Filtered selection of items. If tuple, it contains (text, data) pairs.
|
47
68
|
"""
|
69
|
+
if isinstance(selection, tuple):
|
70
|
+
# If selection is a tuple, it contains (text, data) pairs
|
71
|
+
selection = [text for text, _ in selection]
|
48
72
|
if not isinstance(widget.completer, QCompleter):
|
49
73
|
completer = QCompleter(widget)
|
50
74
|
widget.setCompleter(completer)
|
@@ -64,19 +88,47 @@ class LineEditFilterHandler(WidgetFilterHandler):
|
|
64
88
|
model_data = [model.data(model.index(i)) for i in range(model.rowCount())]
|
65
89
|
return text in model_data
|
66
90
|
|
91
|
+
def update_with_kind(
|
92
|
+
self, kind: Kind, signal_filter: set, device_info: dict, device_name: str
|
93
|
+
) -> list[str | tuple]:
|
94
|
+
"""Update the selection based on the kind of signal.
|
95
|
+
|
96
|
+
Args:
|
97
|
+
kind (Kind): The kind of signal to filter.
|
98
|
+
signal_filter (set): Set of signal kinds to filter.
|
99
|
+
device_info (dict): Dictionary containing device information.
|
100
|
+
device_name (str): Name of the device.
|
101
|
+
|
102
|
+
Returns:
|
103
|
+
list[str | tuple]: A list of filtered signals based on the kind.
|
104
|
+
"""
|
105
|
+
|
106
|
+
return [
|
107
|
+
signal
|
108
|
+
for signal, signal_info in device_info.items()
|
109
|
+
if kind in signal_filter and (signal_info.get("kind_str", None) == str(kind.name))
|
110
|
+
]
|
111
|
+
|
67
112
|
|
68
113
|
class ComboBoxFilterHandler(WidgetFilterHandler):
|
69
114
|
"""Handler for QComboBox widget"""
|
70
115
|
|
71
|
-
def set_selection(self, widget: QComboBox, selection: list) -> None:
|
116
|
+
def set_selection(self, widget: QComboBox, selection: list[str | tuple]) -> None:
|
72
117
|
"""Set the selection for the widget to the completer model
|
73
118
|
|
74
119
|
Args:
|
75
120
|
widget (QComboBox): The QComboBox widget
|
76
|
-
selection (list): Filtered selection of items
|
121
|
+
selection (list[str | tuple]): Filtered selection of items. If tuple, it contains (text, data) pairs.
|
77
122
|
"""
|
78
123
|
widget.clear()
|
79
|
-
|
124
|
+
if len(selection) == 0:
|
125
|
+
return
|
126
|
+
for element in selection:
|
127
|
+
if isinstance(element, str):
|
128
|
+
widget.addItem(element)
|
129
|
+
elif isinstance(element, tuple):
|
130
|
+
# If element is a tuple, it contains (text, data) pairs
|
131
|
+
widget.addItem(*element)
|
80
132
|
|
81
133
|
def check_input(self, widget: QComboBox, text: str) -> bool:
|
82
134
|
"""Check if the input text is in the filtered selection
|
@@ -90,6 +142,40 @@ class ComboBoxFilterHandler(WidgetFilterHandler):
|
|
90
142
|
"""
|
91
143
|
return text in [widget.itemText(i) for i in range(widget.count())]
|
92
144
|
|
145
|
+
def update_with_kind(
|
146
|
+
self, kind: Kind, signal_filter: set, device_info: dict, device_name: str
|
147
|
+
) -> list[str | tuple]:
|
148
|
+
"""Update the selection based on the kind of signal.
|
149
|
+
|
150
|
+
Args:
|
151
|
+
kind (Kind): The kind of signal to filter.
|
152
|
+
signal_filter (set): Set of signal kinds to filter.
|
153
|
+
device_info (dict): Dictionary containing device information.
|
154
|
+
device_name (str): Name of the device.
|
155
|
+
|
156
|
+
Returns:
|
157
|
+
list[str | tuple]: A list of filtered signals based on the kind.
|
158
|
+
"""
|
159
|
+
out = []
|
160
|
+
for signal, signal_info in device_info.items():
|
161
|
+
if kind not in signal_filter or (signal_info.get("kind_str", None) != str(kind.name)):
|
162
|
+
continue
|
163
|
+
obj_name = signal_info.get("obj_name", "")
|
164
|
+
component_name = signal_info.get("component_name", "")
|
165
|
+
signal_wo_device = obj_name.removeprefix(f"{device_name}_")
|
166
|
+
if not signal_wo_device:
|
167
|
+
signal_wo_device = obj_name
|
168
|
+
|
169
|
+
if signal_wo_device != signal and component_name.replace(".", "_") != signal_wo_device:
|
170
|
+
# If the object name is not the same as the signal name, we use the object name
|
171
|
+
# to display in the combobox.
|
172
|
+
out.append((f"{signal_wo_device} ({signal})", signal_info))
|
173
|
+
else:
|
174
|
+
# If the object name is the same as the signal name, we do not change it.
|
175
|
+
out.append((signal, signal_info))
|
176
|
+
|
177
|
+
return out
|
178
|
+
|
93
179
|
|
94
180
|
class FilterIO:
|
95
181
|
"""Public interface to set filters for input widgets.
|
@@ -99,13 +185,14 @@ class FilterIO:
|
|
99
185
|
_handlers = {QLineEdit: LineEditFilterHandler, QComboBox: ComboBoxFilterHandler}
|
100
186
|
|
101
187
|
@staticmethod
|
102
|
-
def set_selection(widget, selection: list, ignore_errors=True):
|
188
|
+
def set_selection(widget, selection: list[str | tuple], ignore_errors=True):
|
103
189
|
"""
|
104
190
|
Retrieve value from the widget instance.
|
105
191
|
|
106
192
|
Args:
|
107
193
|
widget: Widget instance.
|
108
|
-
selection(list):
|
194
|
+
selection (list[str | tuple]): Filtered selection of items.
|
195
|
+
If tuple, it contains (text, data) pairs.
|
109
196
|
ignore_errors(bool, optional): Whether to ignore if no handler is found.
|
110
197
|
"""
|
111
198
|
handler_class = FilterIO._find_handler(widget)
|
@@ -139,6 +226,35 @@ class FilterIO:
|
|
139
226
|
)
|
140
227
|
return None
|
141
228
|
|
229
|
+
@staticmethod
|
230
|
+
def update_with_kind(
|
231
|
+
widget, kind: Kind, signal_filter: set, device_info: dict, device_name: str
|
232
|
+
) -> list[str | tuple]:
|
233
|
+
"""
|
234
|
+
Update the selection based on the kind of signal.
|
235
|
+
|
236
|
+
Args:
|
237
|
+
widget: Widget instance.
|
238
|
+
kind (Kind): The kind of signal to filter.
|
239
|
+
signal_filter (set): Set of signal kinds to filter.
|
240
|
+
device_info (dict): Dictionary containing device information.
|
241
|
+
device_name (str): Name of the device.
|
242
|
+
|
243
|
+
Returns:
|
244
|
+
list[str | tuple]: A list of filtered signals based on the kind.
|
245
|
+
"""
|
246
|
+
handler_class = FilterIO._find_handler(widget)
|
247
|
+
if handler_class:
|
248
|
+
return handler_class().update_with_kind(
|
249
|
+
kind=kind,
|
250
|
+
signal_filter=signal_filter,
|
251
|
+
device_info=device_info,
|
252
|
+
device_name=device_name,
|
253
|
+
)
|
254
|
+
raise ValueError(
|
255
|
+
f"No matching handler for widget type: {type(widget)} in handler list {FilterIO._handlers}"
|
256
|
+
)
|
257
|
+
|
142
258
|
@staticmethod
|
143
259
|
def _find_handler(widget):
|
144
260
|
"""
|
@@ -6,10 +6,10 @@ from bec_lib.device import ComputedSignal, Device, Positioner, ReadoutPriority
|
|
6
6
|
from bec_lib.device import Signal as BECSignal
|
7
7
|
from bec_lib.logger import bec_logger
|
8
8
|
from pydantic import field_validator
|
9
|
-
from qtpy.QtCore import Property, Signal, Slot
|
10
9
|
|
11
10
|
from bec_widgets.utils import ConnectionConfig
|
12
11
|
from bec_widgets.utils.bec_widget import BECWidget
|
12
|
+
from bec_widgets.utils.error_popups import SafeProperty, SafeSlot
|
13
13
|
from bec_widgets.utils.filter_io import FilterIO
|
14
14
|
from bec_widgets.utils.widget_io import WidgetIO
|
15
15
|
|
@@ -100,7 +100,7 @@ class DeviceInputBase(BECWidget):
|
|
100
100
|
|
101
101
|
### QtSlots ###
|
102
102
|
|
103
|
-
@
|
103
|
+
@SafeSlot(str)
|
104
104
|
def set_device(self, device: str):
|
105
105
|
"""
|
106
106
|
Set the device.
|
@@ -114,7 +114,7 @@ class DeviceInputBase(BECWidget):
|
|
114
114
|
else:
|
115
115
|
logger.warning(f"Device {device} is not in the filtered selection.")
|
116
116
|
|
117
|
-
@
|
117
|
+
@SafeSlot()
|
118
118
|
def update_devices_from_filters(self):
|
119
119
|
"""Update the devices based on the current filter selection
|
120
120
|
in self.device_filter and self.readout_filter. If apply_filter is False,
|
@@ -133,7 +133,7 @@ class DeviceInputBase(BECWidget):
|
|
133
133
|
self.devices = [device.name for device in devs]
|
134
134
|
self.set_device(current_device)
|
135
135
|
|
136
|
-
@
|
136
|
+
@SafeSlot(list)
|
137
137
|
def set_available_devices(self, devices: list[str]):
|
138
138
|
"""
|
139
139
|
Set the devices. If a device in the list is not valid, it will not be considered.
|
@@ -146,7 +146,7 @@ class DeviceInputBase(BECWidget):
|
|
146
146
|
|
147
147
|
### QtProperties ###
|
148
148
|
|
149
|
-
@
|
149
|
+
@SafeProperty(
|
150
150
|
"QStringList",
|
151
151
|
doc="List of devices. If updated, it will disable the apply filters property.",
|
152
152
|
)
|
@@ -165,7 +165,7 @@ class DeviceInputBase(BECWidget):
|
|
165
165
|
self.config.devices = value
|
166
166
|
FilterIO.set_selection(widget=self, selection=value)
|
167
167
|
|
168
|
-
@
|
168
|
+
@SafeProperty(str)
|
169
169
|
def default(self):
|
170
170
|
"""Get the default device name. If set through this property, it will update only if the device is within the filtered selection."""
|
171
171
|
return self.config.default
|
@@ -177,7 +177,7 @@ class DeviceInputBase(BECWidget):
|
|
177
177
|
self.config.default = value
|
178
178
|
WidgetIO.set_value(widget=self, value=value)
|
179
179
|
|
180
|
-
@
|
180
|
+
@SafeProperty(bool)
|
181
181
|
def apply_filter(self):
|
182
182
|
"""Apply the filters on the devices."""
|
183
183
|
return self.config.apply_filter
|
@@ -187,7 +187,7 @@ class DeviceInputBase(BECWidget):
|
|
187
187
|
self.config.apply_filter = value
|
188
188
|
self.update_devices_from_filters()
|
189
189
|
|
190
|
-
@
|
190
|
+
@SafeProperty(bool)
|
191
191
|
def filter_to_device(self):
|
192
192
|
"""Include devices in filters."""
|
193
193
|
return BECDeviceFilter.DEVICE in self.device_filter
|
@@ -200,7 +200,7 @@ class DeviceInputBase(BECWidget):
|
|
200
200
|
self._device_filter.remove(BECDeviceFilter.DEVICE)
|
201
201
|
self.update_devices_from_filters()
|
202
202
|
|
203
|
-
@
|
203
|
+
@SafeProperty(bool)
|
204
204
|
def filter_to_positioner(self):
|
205
205
|
"""Include devices of type Positioner in filters."""
|
206
206
|
return BECDeviceFilter.POSITIONER in self.device_filter
|
@@ -213,7 +213,7 @@ class DeviceInputBase(BECWidget):
|
|
213
213
|
self._device_filter.remove(BECDeviceFilter.POSITIONER)
|
214
214
|
self.update_devices_from_filters()
|
215
215
|
|
216
|
-
@
|
216
|
+
@SafeProperty(bool)
|
217
217
|
def filter_to_signal(self):
|
218
218
|
"""Include devices of type Signal in filters."""
|
219
219
|
return BECDeviceFilter.SIGNAL in self.device_filter
|
@@ -226,7 +226,7 @@ class DeviceInputBase(BECWidget):
|
|
226
226
|
self._device_filter.remove(BECDeviceFilter.SIGNAL)
|
227
227
|
self.update_devices_from_filters()
|
228
228
|
|
229
|
-
@
|
229
|
+
@SafeProperty(bool)
|
230
230
|
def filter_to_computed_signal(self):
|
231
231
|
"""Include devices of type ComputedSignal in filters."""
|
232
232
|
return BECDeviceFilter.COMPUTED_SIGNAL in self.device_filter
|
@@ -239,7 +239,7 @@ class DeviceInputBase(BECWidget):
|
|
239
239
|
self._device_filter.remove(BECDeviceFilter.COMPUTED_SIGNAL)
|
240
240
|
self.update_devices_from_filters()
|
241
241
|
|
242
|
-
@
|
242
|
+
@SafeProperty(bool)
|
243
243
|
def readout_monitored(self):
|
244
244
|
"""Include devices with readout priority Monitored in filters."""
|
245
245
|
return ReadoutPriority.MONITORED in self.readout_filter
|
@@ -252,7 +252,7 @@ class DeviceInputBase(BECWidget):
|
|
252
252
|
self._readout_filter.remove(ReadoutPriority.MONITORED)
|
253
253
|
self.update_devices_from_filters()
|
254
254
|
|
255
|
-
@
|
255
|
+
@SafeProperty(bool)
|
256
256
|
def readout_baseline(self):
|
257
257
|
"""Include devices with readout priority Baseline in filters."""
|
258
258
|
return ReadoutPriority.BASELINE in self.readout_filter
|
@@ -265,7 +265,7 @@ class DeviceInputBase(BECWidget):
|
|
265
265
|
self._readout_filter.remove(ReadoutPriority.BASELINE)
|
266
266
|
self.update_devices_from_filters()
|
267
267
|
|
268
|
-
@
|
268
|
+
@SafeProperty(bool)
|
269
269
|
def readout_async(self):
|
270
270
|
"""Include devices with readout priority Async in filters."""
|
271
271
|
return ReadoutPriority.ASYNC in self.readout_filter
|
@@ -278,7 +278,7 @@ class DeviceInputBase(BECWidget):
|
|
278
278
|
self._readout_filter.remove(ReadoutPriority.ASYNC)
|
279
279
|
self.update_devices_from_filters()
|
280
280
|
|
281
|
-
@
|
281
|
+
@SafeProperty(bool)
|
282
282
|
def readout_continuous(self):
|
283
283
|
"""Include devices with readout priority continuous in filters."""
|
284
284
|
return ReadoutPriority.CONTINUOUS in self.readout_filter
|
@@ -291,7 +291,7 @@ class DeviceInputBase(BECWidget):
|
|
291
291
|
self._readout_filter.remove(ReadoutPriority.CONTINUOUS)
|
292
292
|
self.update_devices_from_filters()
|
293
293
|
|
294
|
-
@
|
294
|
+
@SafeProperty(bool)
|
295
295
|
def readout_on_request(self):
|
296
296
|
"""Include devices with readout priority OnRequest in filters."""
|
297
297
|
return ReadoutPriority.ON_REQUEST in self.readout_filter
|
@@ -6,7 +6,7 @@ from qtpy.QtCore import Property
|
|
6
6
|
from bec_widgets.utils import ConnectionConfig
|
7
7
|
from bec_widgets.utils.bec_widget import BECWidget
|
8
8
|
from bec_widgets.utils.error_popups import SafeSlot
|
9
|
-
from bec_widgets.utils.filter_io import FilterIO
|
9
|
+
from bec_widgets.utils.filter_io import FilterIO, LineEditFilterHandler
|
10
10
|
from bec_widgets.utils.ophyd_kind_util import Kind
|
11
11
|
from bec_widgets.utils.widget_io import WidgetIO
|
12
12
|
|
@@ -108,25 +108,32 @@ class DeviceSignalInputBase(BECWidget):
|
|
108
108
|
if not self.validate_device(self._device):
|
109
109
|
self._device = None
|
110
110
|
self.config.device = self._device
|
111
|
+
self._signals = []
|
112
|
+
self._hinted_signals = []
|
113
|
+
self._normal_signals = []
|
114
|
+
self._config_signals = []
|
115
|
+
FilterIO.set_selection(widget=self, selection=self._signals)
|
111
116
|
return
|
112
117
|
device = self.get_device_object(self._device)
|
118
|
+
device_info = device._info.get("signals", {})
|
119
|
+
|
113
120
|
# See above convention for Signals and ComputedSignals
|
114
121
|
if isinstance(device, Signal):
|
115
|
-
self._signals = [self._device]
|
116
|
-
self._hinted_signals = [self._device]
|
122
|
+
self._signals = [(self._device, {})]
|
123
|
+
self._hinted_signals = [(self._device, {})]
|
117
124
|
self._normal_signals = []
|
118
125
|
self._config_signals = []
|
119
126
|
FilterIO.set_selection(widget=self, selection=self._signals)
|
120
127
|
return
|
121
|
-
device_info = device._info.get("signals", {})
|
122
128
|
|
123
129
|
def _update(kind: Kind):
|
124
|
-
return
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
+
return FilterIO.update_with_kind(
|
131
|
+
widget=self,
|
132
|
+
kind=kind,
|
133
|
+
signal_filter=self.signal_filter,
|
134
|
+
device_info=device_info,
|
135
|
+
device_name=self._device,
|
136
|
+
)
|
130
137
|
|
131
138
|
self._hinted_signals = _update(Kind.hinted)
|
132
139
|
self._normal_signals = _update(Kind.normal)
|
@@ -271,8 +278,11 @@ class DeviceSignalInputBase(BECWidget):
|
|
271
278
|
Args:
|
272
279
|
signal(str): Signal to validate.
|
273
280
|
"""
|
274
|
-
|
275
|
-
|
281
|
+
for entry in self.signals:
|
282
|
+
if isinstance(entry, tuple):
|
283
|
+
entry = entry[0]
|
284
|
+
if entry == signal:
|
285
|
+
return True
|
276
286
|
return False
|
277
287
|
|
278
288
|
def _process_config_input(self, config: DeviceSignalInputBaseConfig | dict | None):
|
@@ -34,6 +34,7 @@ class DeviceComboBox(DeviceInputBase, QComboBox):
|
|
34
34
|
PLUGIN = True
|
35
35
|
|
36
36
|
device_selected = Signal(str)
|
37
|
+
device_reset = Signal()
|
37
38
|
device_config_update = Signal()
|
38
39
|
|
39
40
|
def __init__(
|
@@ -147,6 +148,7 @@ class DeviceComboBox(DeviceInputBase, QComboBox):
|
|
147
148
|
self.device_selected.emit(input_text)
|
148
149
|
else:
|
149
150
|
self._is_valid_input = False
|
151
|
+
self.device_reset.emit()
|
150
152
|
self.update()
|
151
153
|
|
152
154
|
def validate_device(self, device: str) -> bool: # type: ignore[override]
|
@@ -90,6 +90,14 @@ class SignalComboBox(DeviceSignalInputBase, QComboBox):
|
|
90
90
|
self.insertItem(0, "Hinted Signals")
|
91
91
|
self.model().item(0).setEnabled(False)
|
92
92
|
|
93
|
+
@SafeSlot()
|
94
|
+
def reset_selection(self):
|
95
|
+
"""Reset the selection of the combobox."""
|
96
|
+
self.clear()
|
97
|
+
self.setItemText(0, "Select a device")
|
98
|
+
self.update_signals_from_filters()
|
99
|
+
self.device_signal_changed.emit("")
|
100
|
+
|
93
101
|
@SafeSlot(str)
|
94
102
|
def on_text_changed(self, text: str):
|
95
103
|
"""Slot for text changed. If a device is selected and the signal is changed and valid it emits a signal.
|
@@ -102,11 +110,7 @@ class SignalComboBox(DeviceSignalInputBase, QComboBox):
|
|
102
110
|
return
|
103
111
|
if self.validate_signal(text) is False:
|
104
112
|
return
|
105
|
-
|
106
|
-
device_signal = self.device
|
107
|
-
else:
|
108
|
-
device_signal = f"{self.device}_{text}"
|
109
|
-
self.device_signal_changed.emit(device_signal)
|
113
|
+
self.device_signal_changed.emit(text)
|
110
114
|
|
111
115
|
|
112
116
|
if __name__ == "__main__": # pragma: no cover
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
2
2
|
|
3
3
|
from typing import TYPE_CHECKING
|
4
4
|
|
5
|
+
from qtpy.QtCore import QSize
|
5
6
|
from qtpy.QtWidgets import (
|
6
7
|
QComboBox,
|
7
8
|
QGroupBox,
|
@@ -35,7 +36,11 @@ class CurveSetting(SettingWidget):
|
|
35
36
|
self._init_x_box()
|
36
37
|
self._init_y_box()
|
37
38
|
|
38
|
-
|
39
|
+
def sizeHint(self) -> QSize:
|
40
|
+
"""
|
41
|
+
Returns the size hint for the settings widget.
|
42
|
+
"""
|
43
|
+
return QSize(800, 500)
|
39
44
|
|
40
45
|
def _init_x_box(self):
|
41
46
|
self.x_axis_box = QGroupBox("X Axis")
|
@@ -97,7 +102,7 @@ class CurveSetting(SettingWidget):
|
|
97
102
|
|
98
103
|
self.layout.addWidget(self.y_axis_box)
|
99
104
|
|
100
|
-
@SafeSlot()
|
105
|
+
@SafeSlot(popup_error=True)
|
101
106
|
def accept_changes(self):
|
102
107
|
"""
|
103
108
|
Accepts the changes made in the settings widget and applies them to the target widget.
|
@@ -5,13 +5,12 @@ from typing import TYPE_CHECKING
|
|
5
5
|
|
6
6
|
from bec_lib.logger import bec_logger
|
7
7
|
from bec_qthemes._icon.material_icons import material_icon
|
8
|
-
from qtpy.
|
8
|
+
from qtpy.QtCore import Qt
|
9
9
|
from qtpy.QtWidgets import (
|
10
|
-
QColorDialog,
|
11
10
|
QComboBox,
|
12
11
|
QHBoxLayout,
|
12
|
+
QHeaderView,
|
13
13
|
QLabel,
|
14
|
-
QLineEdit,
|
15
14
|
QPushButton,
|
16
15
|
QSizePolicy,
|
17
16
|
QSpinBox,
|
@@ -27,9 +26,8 @@ from bec_widgets.utils import ConnectionConfig, EntryValidator
|
|
27
26
|
from bec_widgets.utils.bec_widget import BECWidget
|
28
27
|
from bec_widgets.utils.colors import Colors
|
29
28
|
from bec_widgets.utils.toolbar import MaterialIconAction, ModularToolBar
|
30
|
-
from bec_widgets.widgets.control.device_input.
|
31
|
-
|
32
|
-
)
|
29
|
+
from bec_widgets.widgets.control.device_input.device_combobox.device_combobox import DeviceComboBox
|
30
|
+
from bec_widgets.widgets.control.device_input.signal_combobox.signal_combobox import SignalComboBox
|
33
31
|
from bec_widgets.widgets.dap.dap_combo_box.dap_combo_box import DapComboBox
|
34
32
|
from bec_widgets.widgets.plots.waveform.curve import CurveConfig, DeviceSignal
|
35
33
|
from bec_widgets.widgets.utility.visual.color_button_native.color_button_native import (
|
@@ -125,11 +123,40 @@ class CurveRow(QTreeWidgetItem):
|
|
125
123
|
"""Create columns 1 and 2. For device rows, we have device/entry edits; for dap rows, label/model combo."""
|
126
124
|
if self.source == "device":
|
127
125
|
# Device row: columns 1..2 are device line edits
|
128
|
-
self.device_edit =
|
129
|
-
self.
|
126
|
+
self.device_edit = DeviceComboBox(parent=self.tree)
|
127
|
+
self.device_edit.insertItem(0, "")
|
128
|
+
self.device_edit.setEditable(True)
|
129
|
+
self.entry_edit = SignalComboBox(parent=self.tree)
|
130
|
+
self.entry_edit.include_config_signals = False
|
131
|
+
self.entry_edit.insertItem(0, "")
|
132
|
+
self.entry_edit.setEditable(True)
|
133
|
+
self.device_edit.currentTextChanged.connect(self.entry_edit.set_device)
|
134
|
+
self.device_edit.device_reset.connect(self.entry_edit.reset_selection)
|
130
135
|
if self.config.signal:
|
131
|
-
self.device_edit.
|
132
|
-
|
136
|
+
device_index = self.device_edit.findText(self.config.signal.name or "")
|
137
|
+
if device_index >= 0:
|
138
|
+
self.device_edit.setCurrentIndex(device_index)
|
139
|
+
# Force the entry_edit to update based on the device name
|
140
|
+
self.device_edit.currentTextChanged.emit(self.device_edit.currentText())
|
141
|
+
else:
|
142
|
+
# If the device name is not found, set the first enabled item
|
143
|
+
self.device_edit.setCurrentIndex(0)
|
144
|
+
|
145
|
+
for i in range(self.entry_edit.count()):
|
146
|
+
entry_data = self.entry_edit.itemData(i)
|
147
|
+
if entry_data and entry_data.get("obj_name") == self.config.signal.entry:
|
148
|
+
# If the device name matches an object name, set it
|
149
|
+
self.entry_edit.setCurrentIndex(i)
|
150
|
+
break
|
151
|
+
else:
|
152
|
+
# If no match found, set the first enabled item
|
153
|
+
for i in range(self.entry_edit.count()):
|
154
|
+
model = self.entry_edit.model()
|
155
|
+
if model.flags(model.index(i, 0)) & Qt.ItemIsEnabled:
|
156
|
+
self.entry_edit.setCurrentIndex(i)
|
157
|
+
break
|
158
|
+
else:
|
159
|
+
self.entry_edit.setCurrentIndex(0)
|
133
160
|
|
134
161
|
self.tree.setItemWidget(self, 1, self.device_edit)
|
135
162
|
self.tree.setItemWidget(self, 2, self.entry_edit)
|
@@ -268,13 +295,22 @@ class CurveRow(QTreeWidgetItem):
|
|
268
295
|
# Gather device name/entry
|
269
296
|
device_name = ""
|
270
297
|
device_entry = ""
|
298
|
+
|
299
|
+
## TODO: Move this to itemData
|
271
300
|
if hasattr(self, "device_edit"):
|
272
|
-
device_name = self.device_edit.
|
301
|
+
device_name = self.device_edit.currentText()
|
273
302
|
if hasattr(self, "entry_edit"):
|
274
|
-
device_entry = self.
|
275
|
-
|
276
|
-
|
277
|
-
|
303
|
+
device_entry = self.entry_edit.currentText()
|
304
|
+
index = self.entry_edit.findText(device_entry)
|
305
|
+
if index > -1:
|
306
|
+
device_entry_info = self.entry_edit.itemData(index)
|
307
|
+
if device_entry_info:
|
308
|
+
device_entry = device_entry_info.get("obj_name", device_entry)
|
309
|
+
else:
|
310
|
+
device_entry = self.entry_validator.validate_signal(
|
311
|
+
name=device_name, entry=device_entry
|
312
|
+
)
|
313
|
+
|
278
314
|
self.config.signal = DeviceSignal(name=device_name, entry=device_entry)
|
279
315
|
self.config.source = "device"
|
280
316
|
self.config.label = f"{device_name}-{device_entry}"
|
@@ -390,13 +426,20 @@ class CurveTree(BECWidget, QWidget):
|
|
390
426
|
self.tree = QTreeWidget()
|
391
427
|
self.tree.setColumnCount(7)
|
392
428
|
self.tree.setHeaderLabels(["Actions", "Name", "Entry", "Color", "Style", "Width", "Symbol"])
|
429
|
+
|
430
|
+
header = self.tree.header()
|
431
|
+
for idx in range(self.tree.columnCount()):
|
432
|
+
if idx in (1, 2): # Device name and entry should stretch
|
433
|
+
header.setSectionResizeMode(idx, QHeaderView.Stretch)
|
434
|
+
else:
|
435
|
+
header.setSectionResizeMode(idx, QHeaderView.Fixed)
|
436
|
+
header.setStretchLastSection(False)
|
393
437
|
self.tree.setColumnWidth(0, 90)
|
394
|
-
self.tree.setColumnWidth(1, 100)
|
395
|
-
self.tree.setColumnWidth(2, 100)
|
396
438
|
self.tree.setColumnWidth(3, 70)
|
397
439
|
self.tree.setColumnWidth(4, 80)
|
398
|
-
self.tree.setColumnWidth(5,
|
399
|
-
self.tree.setColumnWidth(6,
|
440
|
+
self.tree.setColumnWidth(5, 50)
|
441
|
+
self.tree.setColumnWidth(6, 50)
|
442
|
+
|
400
443
|
self.layout.addWidget(self.tree)
|
401
444
|
|
402
445
|
def _init_color_buffer(self, size: int):
|
@@ -330,7 +330,6 @@ class Waveform(PlotBase):
|
|
330
330
|
self.curve_settings_dialog = SettingsDialog(
|
331
331
|
self, settings_widget=curve_setting, window_title="Curve Settings", modal=False
|
332
332
|
)
|
333
|
-
self.curve_settings_dialog.setFixedWidth(580)
|
334
333
|
# When the dialog is closed, update the toolbar icon and clear the reference
|
335
334
|
self.curve_settings_dialog.finished.connect(self._curve_settings_closed)
|
336
335
|
self.curve_settings_dialog.show()
|
@@ -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.
|
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.
|
245
|
-
self.
|
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.
|
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
|
263
|
-
|
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 =
|
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
|
-
|
282
|
-
|
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
|
-
|
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
|
|
@@ -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=
|
5
|
+
CHANGELOG.md,sha256=TiLkAVElyNh0hZUpQhRMUL2ia_4XUKViiXQ1tXGbUQc,308560
|
6
6
|
LICENSE,sha256=Daeiu871NcAp8uYi4eB_qHgvypG-HX0ioRQyQxFwjeg,1531
|
7
|
-
PKG-INFO,sha256=
|
7
|
+
PKG-INFO,sha256=FfdkFV0tAjMMbo_ISSEu8ttPkPCUnvuyTKflfEfOibY,1256
|
8
8
|
README.md,sha256=oY5Jc1uXehRASuwUJ0umin2vfkFh7tHF-LLruHTaQx0,3560
|
9
|
-
pyproject.toml,sha256=
|
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=WtICNGG17S1KW6kBrJDyfxvmwRA9jldVw_eVyMyX8B0,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=
|
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
|
@@ -59,7 +59,7 @@ bec_widgets/examples/plugin_example_pyside/tictactoe.py,sha256=s3rCurXloVcmMdzZi
|
|
59
59
|
bec_widgets/examples/plugin_example_pyside/tictactoeplugin.py,sha256=MFMwONn4EZ3V8DboEG4I3BXpURE9JDbKB7XTzzfZl5w,1978
|
60
60
|
bec_widgets/examples/plugin_example_pyside/tictactoetaskmenu.py,sha256=SiJaoX3OYA8YMkSwU1d7KEfSUjQQUsQgpRAxSSlr8oQ,2376
|
61
61
|
bec_widgets/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
62
|
-
bec_widgets/tests/utils.py,sha256=
|
62
|
+
bec_widgets/tests/utils.py,sha256=T_8ycpWxtI8gdXHD21r4ua-mjdHhavSVdnTVfm-zecw,8970
|
63
63
|
bec_widgets/utils/__init__.py,sha256=1930ji1Jj6dVuY81Wd2kYBhHYNV-2R0bN_L4o9zBj1U,533
|
64
64
|
bec_widgets/utils/bec_connector.py,sha256=ATOSyZqryn1QHPc7aotiDnUtzFhlj_gmcukMT_pqjHQ,19272
|
65
65
|
bec_widgets/utils/bec_designer.py,sha256=ehNl_i743rijmhPiIGNd1bihE7-l4oJzTVoa4yjPjls,5426
|
@@ -77,7 +77,7 @@ bec_widgets/utils/crosshair.py,sha256=nqBPQqWzoTLZ-sPBR6ONm7M1TtGGD2EpRwm2iSNpoF
|
|
77
77
|
bec_widgets/utils/entry_validator.py,sha256=lwT8HP0RDG1FXENIeZ3IDEF2DQmD8KXGkRxPoMXbryk,1817
|
78
78
|
bec_widgets/utils/error_popups.py,sha256=UBAmD1YlAgKodpihudyf0VWtI59KGFiLgnjiKmKGjgk,13254
|
79
79
|
bec_widgets/utils/expandable_frame.py,sha256=BvR5kyALWPcOz7l5zarqG_M-EWFY2LMw0jfdDWC5-hI,4025
|
80
|
-
bec_widgets/utils/filter_io.py,sha256=
|
80
|
+
bec_widgets/utils/filter_io.py,sha256=DVWonV3ibc4eCB5SPNGG9EWKX68_flnhMBA3OBPvjHo,9995
|
81
81
|
bec_widgets/utils/fps_counter.py,sha256=seuCWwiNP5q2e2OEztloa66pNb3Sygh-0lEHAcYaDfc,2612
|
82
82
|
bec_widgets/utils/generate_designer_plugin.py,sha256=d_-cWQJ2s8Ff-XD_YQUgnVsRCMHBPcszXOAuJSeIWHM,5676
|
83
83
|
bec_widgets/utils/layout_manager.py,sha256=H0nKsIMaPxRkof1MEXlSmW6w1dFxA6astaGzf4stI84,4727
|
@@ -180,12 +180,12 @@ bec_widgets/widgets/control/device_control/positioner_group/positioner_group_plu
|
|
180
180
|
bec_widgets/widgets/control/device_control/positioner_group/register_positioner_group.py,sha256=vjoIwn1CQbg3gEwKzgY2MuscfGab2agM5YV0r1_UL0k,531
|
181
181
|
bec_widgets/widgets/control/device_input/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
182
182
|
bec_widgets/widgets/control/device_input/base_classes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
183
|
-
bec_widgets/widgets/control/device_input/base_classes/device_input_base.py,sha256=
|
184
|
-
bec_widgets/widgets/control/device_input/base_classes/device_signal_input_base.py,sha256=
|
183
|
+
bec_widgets/widgets/control/device_input/base_classes/device_input_base.py,sha256=WxTPrZm54JRDckrVXQ4XBcUwOsad8ZyF5suZcyF_hyQ,15994
|
184
|
+
bec_widgets/widgets/control/device_input/base_classes/device_signal_input_base.py,sha256=YAVhvOGj2RLNKmkAI6DbbK6BilBM845u84sQ4NKic1s,9702
|
185
185
|
bec_widgets/widgets/control/device_input/device_combobox/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
186
186
|
bec_widgets/widgets/control/device_input/device_combobox/device_combo_box.pyproject,sha256=wI2eXR5ky_IM9-BCHJnH_9CEqYcZwIuLcgitSEr8OJU,40
|
187
187
|
bec_widgets/widgets/control/device_input/device_combobox/device_combo_box_plugin.py,sha256=E8LD9T4O2w621q25uHqBqZLDiQ6zpMR25ZDuf51jrPw,1434
|
188
|
-
bec_widgets/widgets/control/device_input/device_combobox/device_combobox.py,sha256
|
188
|
+
bec_widgets/widgets/control/device_input/device_combobox/device_combobox.py,sha256=-alAbGia6RA3EBaDiQC4yxc6sB6wHXBsVa8InBnemTQ,7316
|
189
189
|
bec_widgets/widgets/control/device_input/device_combobox/register_device_combo_box.py,sha256=elw4M4xfIFWe8C0MkdqqqyfnyOVrdl0g0j6bqwOU1GE,526
|
190
190
|
bec_widgets/widgets/control/device_input/device_line_edit/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
191
191
|
bec_widgets/widgets/control/device_input/device_line_edit/device_line_edit.py,sha256=k87NtUD2TUB4c6-Ks-eZgafWbjTwbk0RKiqf9-OkjMk,7415
|
@@ -196,7 +196,7 @@ bec_widgets/widgets/control/device_input/signal_combobox/__init__.py,sha256=47DE
|
|
196
196
|
bec_widgets/widgets/control/device_input/signal_combobox/register_signal_combo_box.py,sha256=VEdFRUfLph7JE2arcnzHw8etsE-4wZkwyzlNLMJBsZk,526
|
197
197
|
bec_widgets/widgets/control/device_input/signal_combobox/signal_combo_box.pyproject,sha256=xod6iyRD-WD0Uk6LWXjSxFJCQy-831pvTkKcw2FAdnM,33
|
198
198
|
bec_widgets/widgets/control/device_input/signal_combobox/signal_combo_box_plugin.py,sha256=sstqm2KtyR5wwOIYJRbzOqHMq5_9ExKP-YS5qV5ACrA,1373
|
199
|
-
bec_widgets/widgets/control/device_input/signal_combobox/signal_combobox.py,sha256=
|
199
|
+
bec_widgets/widgets/control/device_input/signal_combobox/signal_combobox.py,sha256=ihZYx1wN8vXtOco3_WqTOgz6J7wxd3n3rIL-tWWyCIA,4913
|
200
200
|
bec_widgets/widgets/control/device_input/signal_line_edit/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
201
201
|
bec_widgets/widgets/control/device_input/signal_line_edit/register_signal_line_edit.py,sha256=aQLTy_3gbji0vq5VvvAddHFimpwGGaMYJy5iGgX23aM,527
|
202
202
|
bec_widgets/widgets/control/device_input/signal_line_edit/signal_line_edit.py,sha256=-y_Oy8A7pQVQbzjvHznGxTX-wCisP-4l5py7WOm1_EY,6008
|
@@ -315,13 +315,13 @@ bec_widgets/widgets/plots/toolbar_bundles/save_state.py,sha256=H3fu-bRzNIycCUFb2
|
|
315
315
|
bec_widgets/widgets/plots/waveform/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
316
316
|
bec_widgets/widgets/plots/waveform/curve.py,sha256=KlcGbd60lPO9BYcca08fMhWkfx5qu4O9IbSNTwvajGM,10401
|
317
317
|
bec_widgets/widgets/plots/waveform/register_waveform.py,sha256=pdcLCYKkLkSb-5DqbJdC6M3JyoXQBRVAKf7BZVCto80,467
|
318
|
-
bec_widgets/widgets/plots/waveform/waveform.py,sha256=
|
318
|
+
bec_widgets/widgets/plots/waveform/waveform.py,sha256=XOajidVbmrYeaybAH-ic5EzH3gbx_chlIet-j3akxuE,77180
|
319
319
|
bec_widgets/widgets/plots/waveform/waveform.pyproject,sha256=X2T6d4JGt9YSI28e-myjXh1YkUM4Yr3kNb0-F84KvUA,26
|
320
320
|
bec_widgets/widgets/plots/waveform/waveform_plugin.py,sha256=2AZPtBHs75l9cdhwQnY3jpIMRPUUqK3RNvQbTvrFyvg,1237
|
321
321
|
bec_widgets/widgets/plots/waveform/settings/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
322
322
|
bec_widgets/widgets/plots/waveform/settings/curve_settings/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
323
|
-
bec_widgets/widgets/plots/waveform/settings/curve_settings/curve_setting.py,sha256=
|
324
|
-
bec_widgets/widgets/plots/waveform/settings/curve_settings/curve_tree.py,sha256=
|
323
|
+
bec_widgets/widgets/plots/waveform/settings/curve_settings/curve_setting.py,sha256=jnhd_FqSvLI8xqeXWe1DizP3o_JRfU64swXxqP9Sk6U,4580
|
324
|
+
bec_widgets/widgets/plots/waveform/settings/curve_settings/curve_tree.py,sha256=XQXIfo7eVMc4P5iafh-BQifG9qcI8Ql5-TfIWKrwWHk,23066
|
325
325
|
bec_widgets/widgets/plots/waveform/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
326
326
|
bec_widgets/widgets/plots/waveform/utils/roi_manager.py,sha256=zCl3-p3qP02zi837Udz8VUzsbUAwiP-26K3VpLsvaUU,2964
|
327
327
|
bec_widgets/widgets/progress/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -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=
|
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=
|
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.
|
422
|
-
bec_widgets-2.
|
423
|
-
bec_widgets-2.
|
424
|
-
bec_widgets-2.
|
425
|
-
bec_widgets-2.
|
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
File without changes
|
File without changes
|
File without changes
|