bec-widgets 0.82.2__py3-none-any.whl → 0.83.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.
- .gitlab-ci.yml +3 -0
- CHANGELOG.md +26 -28
- PKG-INFO +1 -1
- bec_widgets/utils/bec_connector.py +2 -0
- bec_widgets/utils/bec_widget.py +6 -0
- bec_widgets/utils/generate_designer_plugin.py +6 -5
- bec_widgets/utils/reference_utils.py +92 -0
- bec_widgets/widgets/console/console.py +1 -1
- bec_widgets/widgets/device_box/__init__.py +0 -0
- bec_widgets/widgets/device_box/device_box.py +197 -0
- bec_widgets/widgets/device_box/device_box.pyproject +1 -0
- bec_widgets/widgets/device_box/device_box.ui +179 -0
- bec_widgets/widgets/device_box/device_box_plugin.py +54 -0
- bec_widgets/widgets/device_box/register_device_box.py +15 -0
- bec_widgets/widgets/figure/figure.py +3 -3
- bec_widgets/widgets/position_indicator/position_indicator.py +71 -0
- bec_widgets/widgets/position_indicator/position_indicator.pyproject +1 -0
- bec_widgets/widgets/position_indicator/position_indicator_plugin.py +54 -0
- bec_widgets/widgets/position_indicator/register_position_indicator.py +17 -0
- bec_widgets/widgets/spinner/__init__.py +0 -0
- bec_widgets/widgets/spinner/register_spinner_widget.py +15 -0
- bec_widgets/widgets/spinner/spinner.py +85 -0
- bec_widgets/widgets/spinner/spinner_widget.pyproject +1 -0
- bec_widgets/widgets/spinner/spinner_widget_plugin.py +54 -0
- bec_widgets/widgets/vscode/vscode.py +0 -14
- bec_widgets/widgets/website/website.py +1 -1
- {bec_widgets-0.82.2.dist-info → bec_widgets-0.83.0.dist-info}/METADATA +1 -1
- {bec_widgets-0.82.2.dist-info → bec_widgets-0.83.0.dist-info}/RECORD +40 -18
- pyproject.toml +1 -1
- tests/references/SpinnerWidget/SpinnerWidget_darwin.png +0 -0
- tests/references/SpinnerWidget/SpinnerWidget_linux.png +0 -0
- tests/references/SpinnerWidget/SpinnerWidget_started_darwin.png +0 -0
- tests/references/SpinnerWidget/SpinnerWidget_started_linux.png +0 -0
- tests/unit_tests/client_mocks.py +9 -1
- tests/unit_tests/test_device_box.py +98 -0
- tests/unit_tests/test_spinner.py +30 -0
- tests/unit_tests/test_vscode_widget.py +27 -32
- {bec_widgets-0.82.2.dist-info → bec_widgets-0.83.0.dist-info}/WHEEL +0 -0
- {bec_widgets-0.82.2.dist-info → bec_widgets-0.83.0.dist-info}/entry_points.txt +0 -0
- {bec_widgets-0.82.2.dist-info → bec_widgets-0.83.0.dist-info}/licenses/LICENSE +0 -0
.gitlab-ci.yml
CHANGED
CHANGELOG.md
CHANGED
@@ -1,5 +1,31 @@
|
|
1
1
|
# CHANGELOG
|
2
2
|
|
3
|
+
## v0.83.0 (2024-07-08)
|
4
|
+
|
5
|
+
### Feature
|
6
|
+
|
7
|
+
* feat: added reference utils to compare renderings of widgets ([`2988fd3`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/2988fd387e6b8076fffec1d57e3ccab89ddb2aeb))
|
8
|
+
|
9
|
+
* feat(widgets): added device box with spinner ([`1b017ed`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/1b017edfad8e78fa079210486123976695b8915c))
|
10
|
+
|
11
|
+
* feat(designer): added option to skip the widget validation for DesignerPluginGenerator ([`41bcb80`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/41bcb801674ab6c4d6069bba34ffee09c9e665db))
|
12
|
+
|
13
|
+
### Fix
|
14
|
+
|
15
|
+
* fix(terminal): added default args to avoid designer crashes on startup ([`360d171`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/360d17135573e44b80ab517756da3c0b31daab0f))
|
16
|
+
|
17
|
+
* fix(widget): fixed widget cleanup routine ([`2b29e34`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/2b29e34b52d056349647bb2fcf649b749a60d292))
|
18
|
+
|
19
|
+
* fix(bec_widget): added cleanup method to bec widget base class ([`fd8766e`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/fd8766ed87770661da6591aeb4df5abdaf38afc7))
|
20
|
+
|
21
|
+
* fix(website): fixed dummy input ([`903ce7d`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/903ce7d46b5d37d40486d0fda92d3694d3faca62))
|
22
|
+
|
23
|
+
### Test
|
24
|
+
|
25
|
+
* test(vscode): fixed vscode tests for new cleanup routine ([`eb26e2a`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/eb26e2a11b229a52efe2e6d4fb28d760d3740136))
|
26
|
+
|
27
|
+
* test(vscode): improved vscode test ([`5de8804`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/5de8804da1e41eafad2472344904b3324438c13b))
|
28
|
+
|
3
29
|
## v0.82.2 (2024-07-08)
|
4
30
|
|
5
31
|
### Fix
|
@@ -117,31 +143,3 @@
|
|
117
143
|
* fix(toolbar): change default color to black to match BECFigure theme ([`b8774e0`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/b8774e0b0bc43dcd00f94f42539a778e507ca27d))
|
118
144
|
|
119
145
|
* fix(motor_map): fixed bug with residual trace after changing motors ([`aaa0d10`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/aaa0d1003d2e94b45bafe4f700852c2c05288aea))
|
120
|
-
|
121
|
-
* fix(widget_io): widget handler adjusted for spinboxes and comboboxes ([`3dc0532`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/3dc0532df05b6ec0a2522107fa0b1e210ce7d91b))
|
122
|
-
|
123
|
-
### Refactor
|
124
|
-
|
125
|
-
* refactor(toolbar): cleanup and adjusted colors ([`96863ad`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/96863adf53c15112645d20eb6200733617801c6d))
|
126
|
-
|
127
|
-
## v0.78.1 (2024-07-02)
|
128
|
-
|
129
|
-
### Fix
|
130
|
-
|
131
|
-
* fix(ui_loader): ui loader is compatible with bec plugins ([`b787759`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/b787759f44486dc7af2c03811efb156041e4b6cb))
|
132
|
-
|
133
|
-
## v0.78.0 (2024-07-02)
|
134
|
-
|
135
|
-
### Feature
|
136
|
-
|
137
|
-
* feat(color_button): patched ColorButton from pyqtgraph to be able to be opened in another QDialog ([`c36bb80`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/c36bb80d6a4939802a4a1c8e5452c7b94bac185e))
|
138
|
-
|
139
|
-
## v0.77.0 (2024-07-02)
|
140
|
-
|
141
|
-
### Fix
|
142
|
-
|
143
|
-
* fix(waveform): scatter 2D brush error ([`215d59c`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/215d59c8bfe7fda9aff8cec8353bef9e1ce2eca1))
|
144
|
-
|
145
|
-
* fix(figure): API cleanup ([`008a33a`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/008a33a9b192473cc58e90cd6d98c5bcb5f7b8c0))
|
146
|
-
|
147
|
-
* fix(figure): if/else logic corrected in subplot_factory ([`3e78723`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/3e787234c7274b0698423d7bf9a4c54ec46bad5f))
|
PKG-INFO
CHANGED
@@ -289,6 +289,8 @@ class BECConnector(BECWidget):
|
|
289
289
|
print("No more connections. Shutting down GUI BEC client.")
|
290
290
|
self.bec_dispatcher.disconnect_all()
|
291
291
|
self.client.shutdown()
|
292
|
+
if hasattr(super(), "cleanup"):
|
293
|
+
super().cleanup()
|
292
294
|
|
293
295
|
# def closeEvent(self, event):
|
294
296
|
# self.cleanup()
|
bec_widgets/utils/bec_widget.py
CHANGED
@@ -58,11 +58,12 @@ class DesignerPluginGenerator:
|
|
58
58
|
os.path.dirname(os.path.abspath(__file__)), "plugin_templates"
|
59
59
|
)
|
60
60
|
|
61
|
-
def run(self):
|
61
|
+
def run(self, validate=True):
|
62
62
|
if self._excluded:
|
63
63
|
print(f"Plugin {self.widget.__name__} is excluded from generation.")
|
64
64
|
return
|
65
|
-
|
65
|
+
if validate:
|
66
|
+
self._check_class_validity()
|
66
67
|
self._load_templates()
|
67
68
|
self._write_templates()
|
68
69
|
|
@@ -142,7 +143,7 @@ class DesignerPluginGenerator:
|
|
142
143
|
|
143
144
|
if __name__ == "__main__": # pragma: no cover
|
144
145
|
# from bec_widgets.widgets.bec_queue.bec_queue import BECQueue
|
145
|
-
from bec_widgets.widgets.
|
146
|
+
from bec_widgets.widgets.spinner.spinner import SpinnerWidget
|
146
147
|
|
147
|
-
generator = DesignerPluginGenerator(
|
148
|
-
generator.run()
|
148
|
+
generator = DesignerPluginGenerator(SpinnerWidget)
|
149
|
+
generator.run(validate=False)
|
@@ -0,0 +1,92 @@
|
|
1
|
+
import os
|
2
|
+
import sys
|
3
|
+
|
4
|
+
from PIL import Image, ImageChops
|
5
|
+
from qtpy.QtGui import QPixmap
|
6
|
+
|
7
|
+
import bec_widgets
|
8
|
+
|
9
|
+
REFERENCE_DIR = os.path.join(
|
10
|
+
os.path.dirname(os.path.dirname(bec_widgets.__file__)), "tests/references"
|
11
|
+
)
|
12
|
+
REFERENCE_DIR_FAILURES = os.path.join(
|
13
|
+
os.path.dirname(os.path.dirname(bec_widgets.__file__)), "tests/reference_failures"
|
14
|
+
)
|
15
|
+
|
16
|
+
|
17
|
+
def compare_images(image1_path: str, reference_image_path: str):
|
18
|
+
"""
|
19
|
+
Load two images and compare them pixel by pixel
|
20
|
+
|
21
|
+
Args:
|
22
|
+
image1_path(str): The path to the first image
|
23
|
+
reference_image_path(str): The path to the reference image
|
24
|
+
|
25
|
+
Raises:
|
26
|
+
ValueError: If the images are different
|
27
|
+
"""
|
28
|
+
image1 = Image.open(image1_path)
|
29
|
+
image2 = Image.open(reference_image_path)
|
30
|
+
if image1.size != image2.size:
|
31
|
+
raise ValueError("Image size has changed")
|
32
|
+
diff = ImageChops.difference(image1, image2)
|
33
|
+
if diff.getbbox():
|
34
|
+
# copy image1 to the reference directory to upload as artifact
|
35
|
+
os.makedirs(REFERENCE_DIR_FAILURES, exist_ok=True)
|
36
|
+
image_name = os.path.join(REFERENCE_DIR_FAILURES, os.path.basename(image1_path))
|
37
|
+
image1.save(image_name)
|
38
|
+
print(f"Image saved to {image_name}")
|
39
|
+
|
40
|
+
raise ValueError("Images are different")
|
41
|
+
|
42
|
+
|
43
|
+
def snap_and_compare(widget: any, output_directory: str, suffix: str = ""):
|
44
|
+
"""
|
45
|
+
Save a rendering of a widget and compare it to a reference image
|
46
|
+
|
47
|
+
Args:
|
48
|
+
widget(any): The widget to render
|
49
|
+
output_directory(str): The directory to save the image to
|
50
|
+
suffix(str): A suffix to append to the image name
|
51
|
+
|
52
|
+
Raises:
|
53
|
+
ValueError: If the images are different
|
54
|
+
|
55
|
+
Examples:
|
56
|
+
snap_and_compare(widget, tmpdir, suffix="started")
|
57
|
+
|
58
|
+
"""
|
59
|
+
|
60
|
+
if not isinstance(output_directory, str):
|
61
|
+
output_directory = str(output_directory)
|
62
|
+
|
63
|
+
os_suffix = sys.platform
|
64
|
+
|
65
|
+
name = (
|
66
|
+
f"{widget.__class__.__name__}_{suffix}_{os_suffix}.png"
|
67
|
+
if suffix
|
68
|
+
else f"{widget.__class__.__name__}_{os_suffix}.png"
|
69
|
+
)
|
70
|
+
|
71
|
+
# Save the widget to a pixmap
|
72
|
+
test_image_path = os.path.join(output_directory, name)
|
73
|
+
pixmap = QPixmap(widget.size())
|
74
|
+
widget.render(pixmap)
|
75
|
+
pixmap.save(test_image_path)
|
76
|
+
|
77
|
+
try:
|
78
|
+
reference_path = os.path.join(REFERENCE_DIR, f"{widget.__class__.__name__}")
|
79
|
+
reference_image_path = os.path.join(reference_path, name)
|
80
|
+
|
81
|
+
if not os.path.exists(reference_image_path):
|
82
|
+
raise ValueError(f"Reference image not found: {reference_image_path}")
|
83
|
+
|
84
|
+
compare_images(test_image_path, reference_image_path)
|
85
|
+
|
86
|
+
except ValueError:
|
87
|
+
image = Image.open(test_image_path)
|
88
|
+
os.makedirs(REFERENCE_DIR_FAILURES, exist_ok=True)
|
89
|
+
image_name = os.path.join(REFERENCE_DIR_FAILURES, name)
|
90
|
+
image.save(image_name)
|
91
|
+
print(f"Image saved to {image_name}")
|
92
|
+
raise
|
@@ -222,7 +222,7 @@ class _TerminalWidget(QtWidgets.QPlainTextEdit):
|
|
222
222
|
Start ``Backend`` process and render Pyte output as text.
|
223
223
|
"""
|
224
224
|
|
225
|
-
def __init__(self, parent, numColumns, numLines, **kwargs):
|
225
|
+
def __init__(self, parent, numColumns=125, numLines=50, **kwargs):
|
226
226
|
super().__init__(parent)
|
227
227
|
|
228
228
|
# file descriptor to communicate with the subprocess
|
File without changes
|
@@ -0,0 +1,197 @@
|
|
1
|
+
import os
|
2
|
+
import uuid
|
3
|
+
|
4
|
+
from bec_lib.endpoints import MessageEndpoints
|
5
|
+
from bec_lib.messages import ScanQueueMessage
|
6
|
+
from qtpy.QtCore import Property, Signal, Slot
|
7
|
+
from qtpy.QtGui import QDoubleValidator
|
8
|
+
from qtpy.QtWidgets import QDoubleSpinBox, QVBoxLayout, QWidget
|
9
|
+
|
10
|
+
from bec_widgets.utils import UILoader
|
11
|
+
from bec_widgets.utils.bec_connector import BECConnector
|
12
|
+
|
13
|
+
|
14
|
+
class DeviceBox(BECConnector, QWidget):
|
15
|
+
device_changed = Signal(str, str)
|
16
|
+
|
17
|
+
def __init__(self, parent=None, device=None, *args, **kwargs):
|
18
|
+
super().__init__(*args, **kwargs)
|
19
|
+
QWidget.__init__(self, parent=parent)
|
20
|
+
self.get_bec_shortcuts()
|
21
|
+
self._device = ""
|
22
|
+
self._limits = None
|
23
|
+
|
24
|
+
self.init_ui()
|
25
|
+
|
26
|
+
if device is not None:
|
27
|
+
self.device = device
|
28
|
+
self.init_device()
|
29
|
+
|
30
|
+
def init_ui(self):
|
31
|
+
self.device_changed.connect(self.on_device_change)
|
32
|
+
|
33
|
+
current_path = os.path.dirname(__file__)
|
34
|
+
self.ui = UILoader(self).loader(os.path.join(current_path, "device_box.ui"))
|
35
|
+
|
36
|
+
self.layout = QVBoxLayout(self)
|
37
|
+
self.layout.addWidget(self.ui)
|
38
|
+
self.layout.setSpacing(0)
|
39
|
+
self.layout.setContentsMargins(0, 0, 0, 0)
|
40
|
+
|
41
|
+
# fix the size of the device box
|
42
|
+
db = self.ui.device_box
|
43
|
+
db.setFixedHeight(234)
|
44
|
+
db.setFixedWidth(224)
|
45
|
+
|
46
|
+
self.ui.step_size.setStepType(QDoubleSpinBox.AdaptiveDecimalStepType)
|
47
|
+
self.ui.stop.clicked.connect(self.on_stop)
|
48
|
+
self.ui.tweak_right.clicked.connect(self.on_tweak_right)
|
49
|
+
self.ui.tweak_right.setToolTip("Tweak right")
|
50
|
+
self.ui.tweak_left.clicked.connect(self.on_tweak_left)
|
51
|
+
self.ui.tweak_left.setToolTip("Tweak left")
|
52
|
+
self.ui.setpoint.returnPressed.connect(self.on_setpoint_change)
|
53
|
+
|
54
|
+
self.setpoint_validator = QDoubleValidator()
|
55
|
+
self.ui.setpoint.setValidator(self.setpoint_validator)
|
56
|
+
self.ui.spinner_widget.start()
|
57
|
+
|
58
|
+
def init_device(self):
|
59
|
+
if self.device in self.dev:
|
60
|
+
data = self.dev[self.device].read()
|
61
|
+
self.on_device_readback({"signals": data}, {})
|
62
|
+
|
63
|
+
@Property(str)
|
64
|
+
def device(self):
|
65
|
+
return self._device
|
66
|
+
|
67
|
+
@device.setter
|
68
|
+
def device(self, value):
|
69
|
+
if not value or not isinstance(value, str):
|
70
|
+
return
|
71
|
+
old_device = self._device
|
72
|
+
self._device = value
|
73
|
+
self.device_changed.emit(old_device, value)
|
74
|
+
|
75
|
+
@Slot(str, str)
|
76
|
+
def on_device_change(self, old_device: str, new_device: str):
|
77
|
+
if new_device not in self.dev:
|
78
|
+
print(f"Device {new_device} not found in the device list")
|
79
|
+
return
|
80
|
+
print(f"Device changed from {old_device} to {new_device}")
|
81
|
+
self.init_device()
|
82
|
+
self.bec_dispatcher.disconnect_slot(
|
83
|
+
self.on_device_readback, MessageEndpoints.device_readback(old_device)
|
84
|
+
)
|
85
|
+
self.bec_dispatcher.connect_slot(
|
86
|
+
self.on_device_readback, MessageEndpoints.device_readback(new_device)
|
87
|
+
)
|
88
|
+
self.ui.device_box.setTitle(new_device)
|
89
|
+
self.ui.readback.setToolTip(f"{self.device} readback")
|
90
|
+
self.ui.setpoint.setToolTip(f"{self.device} setpoint")
|
91
|
+
self.ui.step_size.setToolTip(f"Step size for {new_device}")
|
92
|
+
|
93
|
+
precision = self.dev[new_device].precision
|
94
|
+
if precision is not None:
|
95
|
+
self.ui.step_size.setDecimals(precision)
|
96
|
+
self.ui.step_size.setValue(10**-precision * 10)
|
97
|
+
|
98
|
+
@Slot(dict, dict)
|
99
|
+
def on_device_readback(self, msg_content: dict, metadata: dict):
|
100
|
+
signals = msg_content.get("signals", {})
|
101
|
+
# pylint: disable=protected-access
|
102
|
+
hinted_signals = self.dev[self.device]._hints
|
103
|
+
precision = self.dev[self.device].precision
|
104
|
+
|
105
|
+
readback_val = None
|
106
|
+
setpoint_val = None
|
107
|
+
|
108
|
+
if len(hinted_signals) == 1:
|
109
|
+
signal = hinted_signals[0]
|
110
|
+
readback_val = signals.get(signal, {}).get("value")
|
111
|
+
|
112
|
+
if f"{self.device}_setpoint" in signals:
|
113
|
+
setpoint_val = signals.get(f"{self.device}_setpoint", {}).get("value")
|
114
|
+
|
115
|
+
if f"{self.device}_motor_is_moving" in signals:
|
116
|
+
is_moving = signals.get(f"{self.device}_motor_is_moving", {}).get("value")
|
117
|
+
if is_moving:
|
118
|
+
self.ui.spinner_widget.start()
|
119
|
+
self.ui.spinner_widget.setToolTip("Device is moving")
|
120
|
+
else:
|
121
|
+
self.ui.spinner_widget.stop()
|
122
|
+
self.ui.spinner_widget.setToolTip("Device is idle")
|
123
|
+
|
124
|
+
if readback_val is not None:
|
125
|
+
self.ui.readback.setText(f"{readback_val:.{precision}f}")
|
126
|
+
|
127
|
+
if setpoint_val is not None:
|
128
|
+
self.ui.setpoint.setText(f"{setpoint_val:.{precision}f}")
|
129
|
+
|
130
|
+
limits = self.dev[self.device].limits
|
131
|
+
self.update_limits(limits)
|
132
|
+
if limits is not None and readback_val is not None and limits[0] != limits[1]:
|
133
|
+
pos = (readback_val - limits[0]) / (limits[1] - limits[0])
|
134
|
+
self.ui.position_indicator.on_position_update(pos)
|
135
|
+
|
136
|
+
def update_limits(self, limits):
|
137
|
+
if limits == self._limits:
|
138
|
+
return
|
139
|
+
self._limits = limits
|
140
|
+
if limits is not None and limits[0] != limits[1]:
|
141
|
+
self.ui.position_indicator.setToolTip(f"Min: {limits[0]}, Max: {limits[1]}")
|
142
|
+
self.setpoint_validator.setRange(limits[0], limits[1])
|
143
|
+
else:
|
144
|
+
self.ui.position_indicator.setToolTip("No limits set")
|
145
|
+
self.setpoint_validator.setRange(float("-inf"), float("inf"))
|
146
|
+
|
147
|
+
@Slot()
|
148
|
+
def on_stop(self):
|
149
|
+
request_id = str(uuid.uuid4())
|
150
|
+
params = {
|
151
|
+
"device": self.device,
|
152
|
+
"rpc_id": request_id,
|
153
|
+
"func": "stop",
|
154
|
+
"args": [],
|
155
|
+
"kwargs": {},
|
156
|
+
}
|
157
|
+
msg = ScanQueueMessage(
|
158
|
+
scan_type="device_rpc",
|
159
|
+
parameter=params,
|
160
|
+
queue="emergency",
|
161
|
+
metadata={"RID": request_id, "response": False},
|
162
|
+
)
|
163
|
+
self.client.connector.send(MessageEndpoints.scan_queue_request(), msg)
|
164
|
+
|
165
|
+
@property
|
166
|
+
def step_size(self):
|
167
|
+
return self.ui.step_size.value()
|
168
|
+
|
169
|
+
@Slot()
|
170
|
+
def on_tweak_right(self):
|
171
|
+
self.dev[self.device].move(self.step_size, relative=True)
|
172
|
+
|
173
|
+
@Slot()
|
174
|
+
def on_tweak_left(self):
|
175
|
+
self.dev[self.device].move(-self.step_size, relative=True)
|
176
|
+
|
177
|
+
@Slot()
|
178
|
+
def on_setpoint_change(self):
|
179
|
+
self.ui.setpoint.clearFocus()
|
180
|
+
setpoint = self.ui.setpoint.text()
|
181
|
+
self.dev[self.device].move(float(setpoint), relative=False)
|
182
|
+
self.ui.tweak_left.setToolTip(f"Tweak left by {self.step_size}")
|
183
|
+
self.ui.tweak_right.setToolTip(f"Tweak right by {self.step_size}")
|
184
|
+
|
185
|
+
|
186
|
+
if __name__ == "__main__": # pragma: no cover
|
187
|
+
import sys
|
188
|
+
|
189
|
+
import qdarktheme
|
190
|
+
from qtpy.QtWidgets import QApplication
|
191
|
+
|
192
|
+
app = QApplication(sys.argv)
|
193
|
+
qdarktheme.setup_theme("light")
|
194
|
+
widget = DeviceBox(device="samx")
|
195
|
+
|
196
|
+
widget.show()
|
197
|
+
sys.exit(app.exec_())
|
@@ -0,0 +1 @@
|
|
1
|
+
{'files': ['device_box.py']}
|
@@ -0,0 +1,179 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<ui version="4.0">
|
3
|
+
<class>Form</class>
|
4
|
+
<widget class="QWidget" name="Form">
|
5
|
+
<property name="geometry">
|
6
|
+
<rect>
|
7
|
+
<x>0</x>
|
8
|
+
<y>0</y>
|
9
|
+
<width>251</width>
|
10
|
+
<height>289</height>
|
11
|
+
</rect>
|
12
|
+
</property>
|
13
|
+
<property name="minimumSize">
|
14
|
+
<size>
|
15
|
+
<width>0</width>
|
16
|
+
<height>192</height>
|
17
|
+
</size>
|
18
|
+
</property>
|
19
|
+
<property name="maximumSize">
|
20
|
+
<size>
|
21
|
+
<width>16777215</width>
|
22
|
+
<height>16777215</height>
|
23
|
+
</size>
|
24
|
+
</property>
|
25
|
+
<property name="windowTitle">
|
26
|
+
<string>Form</string>
|
27
|
+
</property>
|
28
|
+
<layout class="QVBoxLayout" name="verticalLayout">
|
29
|
+
<item>
|
30
|
+
<widget class="QGroupBox" name="device_box">
|
31
|
+
<property name="title">
|
32
|
+
<string>Device Name</string>
|
33
|
+
</property>
|
34
|
+
<layout class="QGridLayout" name="gridLayout" rowstretch="0,0,0,0,0">
|
35
|
+
<property name="topMargin">
|
36
|
+
<number>0</number>
|
37
|
+
</property>
|
38
|
+
<item row="3" column="1">
|
39
|
+
<widget class="QDoubleSpinBox" name="step_size"/>
|
40
|
+
</item>
|
41
|
+
<item row="3" column="2">
|
42
|
+
<widget class="QToolButton" name="tweak_right">
|
43
|
+
<property name="minimumSize">
|
44
|
+
<size>
|
45
|
+
<width>50</width>
|
46
|
+
<height>50</height>
|
47
|
+
</size>
|
48
|
+
</property>
|
49
|
+
<property name="maximumSize">
|
50
|
+
<size>
|
51
|
+
<width>50</width>
|
52
|
+
<height>50</height>
|
53
|
+
</size>
|
54
|
+
</property>
|
55
|
+
<property name="text">
|
56
|
+
<string>...</string>
|
57
|
+
</property>
|
58
|
+
<property name="iconSize">
|
59
|
+
<size>
|
60
|
+
<width>30</width>
|
61
|
+
<height>30</height>
|
62
|
+
</size>
|
63
|
+
</property>
|
64
|
+
<property name="arrowType">
|
65
|
+
<enum>Qt::ArrowType::RightArrow</enum>
|
66
|
+
</property>
|
67
|
+
</widget>
|
68
|
+
</item>
|
69
|
+
<item row="2" column="0" colspan="3">
|
70
|
+
<widget class="QLineEdit" name="setpoint"/>
|
71
|
+
</item>
|
72
|
+
<item row="3" column="0">
|
73
|
+
<widget class="QToolButton" name="tweak_left">
|
74
|
+
<property name="minimumSize">
|
75
|
+
<size>
|
76
|
+
<width>50</width>
|
77
|
+
<height>50</height>
|
78
|
+
</size>
|
79
|
+
</property>
|
80
|
+
<property name="maximumSize">
|
81
|
+
<size>
|
82
|
+
<width>50</width>
|
83
|
+
<height>50</height>
|
84
|
+
</size>
|
85
|
+
</property>
|
86
|
+
<property name="text">
|
87
|
+
<string>...</string>
|
88
|
+
</property>
|
89
|
+
<property name="iconSize">
|
90
|
+
<size>
|
91
|
+
<width>30</width>
|
92
|
+
<height>30</height>
|
93
|
+
</size>
|
94
|
+
</property>
|
95
|
+
<property name="arrowType">
|
96
|
+
<enum>Qt::ArrowType::LeftArrow</enum>
|
97
|
+
</property>
|
98
|
+
</widget>
|
99
|
+
</item>
|
100
|
+
<item row="4" column="0" colspan="3">
|
101
|
+
<widget class="QPushButton" name="stop">
|
102
|
+
<property name="text">
|
103
|
+
<string>Stop</string>
|
104
|
+
</property>
|
105
|
+
</widget>
|
106
|
+
</item>
|
107
|
+
<item row="0" column="0" colspan="3">
|
108
|
+
<layout class="QVBoxLayout" name="verticalLayout_2">
|
109
|
+
<item>
|
110
|
+
<layout class="QHBoxLayout" name="horizontalLayout">
|
111
|
+
<item>
|
112
|
+
<spacer name="horizontalSpacer">
|
113
|
+
<property name="orientation">
|
114
|
+
<enum>Qt::Orientation::Horizontal</enum>
|
115
|
+
</property>
|
116
|
+
<property name="sizeType">
|
117
|
+
<enum>QSizePolicy::Policy::Expanding</enum>
|
118
|
+
</property>
|
119
|
+
<property name="sizeHint" stdset="0">
|
120
|
+
<size>
|
121
|
+
<width>40</width>
|
122
|
+
<height>20</height>
|
123
|
+
</size>
|
124
|
+
</property>
|
125
|
+
</spacer>
|
126
|
+
</item>
|
127
|
+
<item>
|
128
|
+
<widget class="SpinnerWidget" name="spinner_widget">
|
129
|
+
<property name="minimumSize">
|
130
|
+
<size>
|
131
|
+
<width>25</width>
|
132
|
+
<height>25</height>
|
133
|
+
</size>
|
134
|
+
</property>
|
135
|
+
<property name="maximumSize">
|
136
|
+
<size>
|
137
|
+
<width>25</width>
|
138
|
+
<height>25</height>
|
139
|
+
</size>
|
140
|
+
</property>
|
141
|
+
</widget>
|
142
|
+
</item>
|
143
|
+
</layout>
|
144
|
+
</item>
|
145
|
+
<item>
|
146
|
+
<widget class="PositionIndicator" name="position_indicator"/>
|
147
|
+
</item>
|
148
|
+
<item>
|
149
|
+
<widget class="QLabel" name="readback">
|
150
|
+
<property name="text">
|
151
|
+
<string>Position</string>
|
152
|
+
</property>
|
153
|
+
<property name="alignment">
|
154
|
+
<set>Qt::AlignmentFlag::AlignCenter</set>
|
155
|
+
</property>
|
156
|
+
</widget>
|
157
|
+
</item>
|
158
|
+
</layout>
|
159
|
+
</item>
|
160
|
+
</layout>
|
161
|
+
</widget>
|
162
|
+
</item>
|
163
|
+
</layout>
|
164
|
+
</widget>
|
165
|
+
<customwidgets>
|
166
|
+
<customwidget>
|
167
|
+
<class>SpinnerWidget</class>
|
168
|
+
<extends>QWidget</extends>
|
169
|
+
<header>spinner_widget</header>
|
170
|
+
</customwidget>
|
171
|
+
<customwidget>
|
172
|
+
<class>PositionIndicator</class>
|
173
|
+
<extends>QWidget</extends>
|
174
|
+
<header>position_indicator</header>
|
175
|
+
</customwidget>
|
176
|
+
</customwidgets>
|
177
|
+
<resources/>
|
178
|
+
<connections/>
|
179
|
+
</ui>
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# Copyright (C) 2022 The Qt Company Ltd.
|
2
|
+
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
3
|
+
|
4
|
+
from qtpy.QtDesigner import QDesignerCustomWidgetInterface
|
5
|
+
from qtpy.QtGui import QIcon
|
6
|
+
|
7
|
+
from bec_widgets.widgets.device_box.device_box import DeviceBox
|
8
|
+
|
9
|
+
DOM_XML = """
|
10
|
+
<ui language='c++'>
|
11
|
+
<widget class='DeviceBox' name='device_box'>
|
12
|
+
</widget>
|
13
|
+
</ui>
|
14
|
+
"""
|
15
|
+
|
16
|
+
|
17
|
+
class DeviceBoxPlugin(QDesignerCustomWidgetInterface): # pragma: no cover
|
18
|
+
def __init__(self):
|
19
|
+
super().__init__()
|
20
|
+
self._form_editor = None
|
21
|
+
|
22
|
+
def createWidget(self, parent):
|
23
|
+
t = DeviceBox(parent)
|
24
|
+
return t
|
25
|
+
|
26
|
+
def domXml(self):
|
27
|
+
return DOM_XML
|
28
|
+
|
29
|
+
def group(self):
|
30
|
+
return "Device Control"
|
31
|
+
|
32
|
+
def icon(self):
|
33
|
+
return QIcon()
|
34
|
+
|
35
|
+
def includeFile(self):
|
36
|
+
return "device_box"
|
37
|
+
|
38
|
+
def initialize(self, form_editor):
|
39
|
+
self._form_editor = form_editor
|
40
|
+
|
41
|
+
def isContainer(self):
|
42
|
+
return False
|
43
|
+
|
44
|
+
def isInitialized(self):
|
45
|
+
return self._form_editor is not None
|
46
|
+
|
47
|
+
def name(self):
|
48
|
+
return "DeviceBox"
|
49
|
+
|
50
|
+
def toolTip(self):
|
51
|
+
return "A widget for controlling a single positioner. "
|
52
|
+
|
53
|
+
def whatsThis(self):
|
54
|
+
return self.toolTip()
|
@@ -0,0 +1,15 @@
|
|
1
|
+
def main(): # pragma: no cover
|
2
|
+
from qtpy import PYSIDE6
|
3
|
+
|
4
|
+
if not PYSIDE6:
|
5
|
+
print("PYSIDE6 is not available in the environment. Cannot patch designer.")
|
6
|
+
return
|
7
|
+
from PySide6.QtDesigner import QPyDesignerCustomWidgetCollection
|
8
|
+
|
9
|
+
from bec_widgets.widgets.device_box.device_box_plugin import DeviceBoxPlugin
|
10
|
+
|
11
|
+
QPyDesignerCustomWidgetCollection.addCustomWidget(DeviceBoxPlugin())
|
12
|
+
|
13
|
+
|
14
|
+
if __name__ == "__main__": # pragma: no cover
|
15
|
+
main()
|