bec-widgets 0.79.3__py3-none-any.whl → 0.82.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 +0 -1
- .pylintrc +1 -1
- CHANGELOG.md +52 -62
- PKG-INFO +1 -4
- README.md +2 -2
- bec_widgets/cli/client.py +3 -2
- bec_widgets/cli/generate_cli.py +11 -10
- bec_widgets/cli/rpc_wigdet_handler.py +1 -1
- bec_widgets/examples/__init__.py +0 -9
- bec_widgets/utils/bec_connector.py +2 -1
- bec_widgets/utils/bec_dispatcher.py +3 -3
- bec_widgets/utils/bec_widget.py +2 -0
- bec_widgets/utils/entry_validator.py +1 -1
- bec_widgets/utils/plugin_utils.py +80 -10
- bec_widgets/utils/ui_loader.py +85 -26
- bec_widgets/widgets/{device_inputs → base_classes}/device_input_base.py +2 -0
- bec_widgets/widgets/color_button/color_button.py +36 -0
- bec_widgets/widgets/{buttons/color_button → color_button}/color_button_plugin.py +1 -1
- bec_widgets/widgets/{buttons/color_button → color_button}/register_color_button.py +1 -1
- bec_widgets/widgets/device_combobox/assets/device_combobox_icon.png +0 -0
- bec_widgets/widgets/{device_inputs/device_combobox → device_combobox}/device_combobox.py +2 -2
- bec_widgets/widgets/device_combobox/device_combobox.pyproject +3 -0
- bec_widgets/widgets/{device_inputs/device_combobox → device_combobox}/device_combobox_plugin.py +6 -3
- bec_widgets/widgets/{device_inputs/device_combobox → device_combobox}/register_device_combobox.py +1 -3
- bec_widgets/widgets/device_line_edit/assets/line_edit_icon.png +0 -0
- bec_widgets/widgets/{device_inputs/device_line_edit → device_line_edit}/device_line_edit.py +2 -2
- bec_widgets/widgets/device_line_edit/device_line_edit.pyproject +3 -0
- bec_widgets/widgets/{device_inputs/device_line_edit → device_line_edit}/device_line_edit_plugin.py +6 -3
- bec_widgets/widgets/{device_inputs/device_line_edit → device_line_edit}/register_device_line_edit.py +1 -3
- bec_widgets/widgets/figure/plots/waveform/waveform.py +8 -2
- bec_widgets/widgets/motor_map/motor_map_dialog/motor_map_settings.py +1 -1
- bec_widgets/widgets/motor_map/motor_map_dialog/motor_map_toolbar.py +1 -1
- bec_widgets/widgets/motor_map/motor_map_widget.py +0 -12
- bec_widgets/widgets/scan_control/scan_control.py +1 -1
- bec_widgets/widgets/scan_control/scan_group_box.py +1 -1
- bec_widgets/widgets/stop_button/assets/stop.png +0 -0
- bec_widgets/widgets/stop_button/register_stop_button.py +15 -0
- bec_widgets/widgets/{buttons/stop_button → stop_button}/stop_button.py +5 -12
- bec_widgets/widgets/stop_button/stop_button.pyproject +1 -0
- bec_widgets/widgets/stop_button/stop_button_plugin.py +57 -0
- bec_widgets/widgets/toggle/register_toggle_switch.py +15 -0
- bec_widgets/widgets/toggle/toggle.py +149 -0
- bec_widgets/widgets/toggle/toggle_switch.pyproject +1 -0
- bec_widgets/widgets/toggle/toggle_switch_plugin.py +54 -0
- {bec_widgets-0.79.3.dist-info → bec_widgets-0.82.0.dist-info}/METADATA +1 -4
- {bec_widgets-0.79.3.dist-info → bec_widgets-0.82.0.dist-info}/RECORD +66 -74
- docs/introduction/introduction.md +1 -1
- docs/user/getting_started/installation.md +2 -2
- pyproject.toml +1 -2
- tests/unit_tests/test_device_input_base.py +1 -1
- tests/unit_tests/test_device_input_widgets.py +2 -2
- tests/unit_tests/test_generate_cli_client.py +41 -17
- tests/unit_tests/test_plugin_utils.py +2 -3
- tests/unit_tests/test_stop_button.py +5 -2
- tests/unit_tests/test_toggle.py +38 -0
- bec_widgets/examples/motor_movement/__init__.py +0 -9
- bec_widgets/examples/motor_movement/motor_control_compilations.py +0 -250
- bec_widgets/examples/motor_movement/motor_controller.ui +0 -926
- bec_widgets/widgets/buttons/__init__.py +0 -1
- bec_widgets/widgets/buttons/color_button/color_button.py +0 -17
- bec_widgets/widgets/device_inputs/__init__.py +0 -2
- bec_widgets/widgets/device_inputs/device_combobox/device_combobox.pyproject +0 -4
- bec_widgets/widgets/device_inputs/device_combobox/launch_device_combobox.py +0 -11
- bec_widgets/widgets/device_inputs/device_line_edit/device_line_edit.pyproject +0 -4
- bec_widgets/widgets/device_inputs/device_line_edit/launch_device_line_edit.py +0 -11
- bec_widgets/widgets/motor_control/motor_control.py +0 -252
- bec_widgets/widgets/motor_control/motor_table/motor_table.py +0 -484
- bec_widgets/widgets/motor_control/motor_table/motor_table.ui +0 -113
- bec_widgets/widgets/motor_control/movement_absolute/__init__.py +0 -0
- bec_widgets/widgets/motor_control/movement_absolute/movement_absolute.py +0 -159
- bec_widgets/widgets/motor_control/movement_absolute/movement_absolute.ui +0 -149
- bec_widgets/widgets/motor_control/movement_relative/__init__.py +0 -0
- bec_widgets/widgets/motor_control/movement_relative/movement_relative.py +0 -230
- bec_widgets/widgets/motor_control/movement_relative/movement_relative.ui +0 -298
- bec_widgets/widgets/motor_control/selection/__init__.py +0 -0
- bec_widgets/widgets/motor_control/selection/selection.py +0 -110
- bec_widgets/widgets/motor_control/selection/selection.ui +0 -69
- tests/unit_tests/test_motor_control.py +0 -588
- /bec_widgets/widgets/{buttons/color_button → base_classes}/__init__.py +0 -0
- /bec_widgets/widgets/{buttons/stop_button → color_button}/__init__.py +0 -0
- /bec_widgets/widgets/{buttons/color_button → color_button}/assets/color_button.png +0 -0
- /bec_widgets/widgets/{buttons/color_button → color_button}/color_button.pyproject +0 -0
- /bec_widgets/widgets/{device_inputs/device_combobox → device_combobox}/__init__.py +0 -0
- /bec_widgets/widgets/{device_inputs/device_line_edit → device_line_edit}/__init__.py +0 -0
- /bec_widgets/widgets/{motor_control → stop_button}/__init__.py +0 -0
- /bec_widgets/widgets/{motor_control/motor_table → toggle}/__init__.py +0 -0
- {bec_widgets-0.79.3.dist-info → bec_widgets-0.82.0.dist-info}/WHEEL +0 -0
- {bec_widgets-0.79.3.dist-info → bec_widgets-0.82.0.dist-info}/entry_points.txt +0 -0
- {bec_widgets-0.79.3.dist-info → bec_widgets-0.82.0.dist-info}/licenses/LICENSE +0 -0
.gitlab-ci.yml
CHANGED
.pylintrc
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
# A comma-separated list of package or module names from where C extensions may
|
4
4
|
# be loaded. Extensions are loading into the active Python interpreter and may
|
5
5
|
# run arbitrary code.
|
6
|
-
extension-pkg-allow-list=
|
6
|
+
extension-pkg-allow-list=PyQt6, PySide6, pyqtgraph
|
7
7
|
|
8
8
|
# A comma-separated list of package or module names from where C extensions may
|
9
9
|
# be loaded. Extensions are loading into the active Python interpreter and may
|
CHANGELOG.md
CHANGED
@@ -1,5 +1,57 @@
|
|
1
1
|
# CHANGELOG
|
2
2
|
|
3
|
+
## v0.82.0 (2024-07-07)
|
4
|
+
|
5
|
+
### Feature
|
6
|
+
|
7
|
+
* feat(toggle): added angular component-like toggle ([`b9bff38`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/b9bff38b64b86f06b3bc047922ef9df0c7d32e71))
|
8
|
+
|
9
|
+
### Refactor
|
10
|
+
|
11
|
+
* refactor(device_input): DeviceComboBox and DeviceLineEdit moved to top layer of widgets ([`f048629`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/f04862933f049030554086adef3ec9e1aebd3eda))
|
12
|
+
|
13
|
+
* refactor(stop_button): moved to top layer, plugin added ([`f5b8375`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/f5b8375fd36e3bb681de571da86a6c0bdb3cb6f0))
|
14
|
+
|
15
|
+
* refactor(motor_map_widget): removed restriction of only PySide6 for widget ([`db1cdf4`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/db1cdf42806fef6d7c6d2db83528f32df3f9751d))
|
16
|
+
|
17
|
+
* refactor(color_button): ColorButton moved to top level of widgets ([`fa1e86f`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/fa1e86ff07b25d2c47c73117b00765b8e2f25da4))
|
18
|
+
|
19
|
+
## v0.81.2 (2024-07-07)
|
20
|
+
|
21
|
+
### Fix
|
22
|
+
|
23
|
+
* fix(waveform): scan_history error check for IndexError ([`dd1875e`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/dd1875ea5cc18bcef9aad743347a8accf144c08d))
|
24
|
+
|
25
|
+
## v0.81.1 (2024-07-07)
|
26
|
+
|
27
|
+
### Fix
|
28
|
+
|
29
|
+
* fix(motor_control): temporary remove of motor control widgets ([`99114f1`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/99114f14f62202e1fd8bf145616fa8c69937ada4))
|
30
|
+
|
31
|
+
## v0.81.0 (2024-07-06)
|
32
|
+
|
33
|
+
### Feature
|
34
|
+
|
35
|
+
* feat(color_button): can get colors in RGBA or HEX ([`9594be2`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/9594be260680d11c8550ff74ffb8d679e5a5b8f6))
|
36
|
+
|
37
|
+
## v0.80.1 (2024-07-06)
|
38
|
+
|
39
|
+
### Fix
|
40
|
+
|
41
|
+
* fix(entry_validator): check for entry == "" ([`61de7e9`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/61de7e9e221c766b9fb3ec23246da6a11c96a986))
|
42
|
+
|
43
|
+
## v0.80.0 (2024-07-06)
|
44
|
+
|
45
|
+
### Feature
|
46
|
+
|
47
|
+
* feat(qt5): dropped support for qt5; pyside2 and pyqt5 ([`fadbf77`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/fadbf77866903beff6580802bc203d53367fc7e7))
|
48
|
+
|
49
|
+
* feat(plugins): moved plugin dict to dataclass and container ([`03819a3`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/03819a3d902b4a51f3e882d52aedd971b2a8e127))
|
50
|
+
|
51
|
+
* feat(plugins): added support for pyqt6 ui files ([`d6d0777`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/d6d07771135335cb78dc648508ce573b8970261a))
|
52
|
+
|
53
|
+
* feat(plugins): added bec widgets base class ([`1aa83e0`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/1aa83e0ef1ffe45b01677b0b4590535cb0ca1cff))
|
54
|
+
|
3
55
|
## v0.79.3 (2024-07-05)
|
4
56
|
|
5
57
|
### Fix
|
@@ -85,65 +137,3 @@
|
|
85
137
|
* fix(image): image can be fully reconstructed from config ([`797f73c`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/797f73c39aa73e07d6311f3de4baea53f6c380e0))
|
86
138
|
|
87
139
|
* fix(image_item): vrange added int for pydantic model check ([`b8f796f`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/b8f796fd3fcc15641e8fc6a3ca75c344ce90fc45))
|
88
|
-
|
89
|
-
* fix(bec_figure): waveforms can be initialised from the config; widgets are deleteLater after removal ([`78673ea`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/78673ea11a47aad878128197ae6213925228ed59))
|
90
|
-
|
91
|
-
### Unknown
|
92
|
-
|
93
|
-
* Resolve "add VT100 console executing BEC as a widget" ([`c6a14c0`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/c6a14c0768a90695567a83a7895247ed0c64f3ce))
|
94
|
-
|
95
|
-
## v0.76.1 (2024-06-29)
|
96
|
-
|
97
|
-
### Fix
|
98
|
-
|
99
|
-
* fix(plugins): fixes and tests for auto-gen plugins ([`c42511d`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/c42511dd44cc13577e108a6cef3166376e594f54))
|
100
|
-
|
101
|
-
## v0.76.0 (2024-06-28)
|
102
|
-
|
103
|
-
### Feature
|
104
|
-
|
105
|
-
* feat(designer): added support for creating designer plugins automatically ([`c1dd0ee`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/c1dd0ee1906dba1f2e2ae9ce40a84d55c26a1cce))
|
106
|
-
|
107
|
-
### Fix
|
108
|
-
|
109
|
-
* fix: fixed qwidget inheritance for ring progress bar ([`0610d2f`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/0610d2f9f027f8659e7149f2dfbb316ff30e337d))
|
110
|
-
|
111
|
-
### Unknown
|
112
|
-
|
113
|
-
* fix:parent set as first kwarg TextBox and WebsiteWidget ([`a45c407`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/a45c4075684b93bfdcee03e5a416b84f61d3bc6f))
|
114
|
-
|
115
|
-
## v0.75.0 (2024-06-26)
|
116
|
-
|
117
|
-
### Feature
|
118
|
-
|
119
|
-
* feat(widgets): added simple bec queue widget ([`3faee98`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/3faee98ec80041a27e4c1f1156178de6f9dcdc63))
|
120
|
-
|
121
|
-
### Refactor
|
122
|
-
|
123
|
-
* refactor(dispatcher): cleanup ([`ca02132`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/ca02132c8d18535b37e9192e00459d2aca6ba5cf))
|
124
|
-
|
125
|
-
## v0.74.1 (2024-06-26)
|
126
|
-
|
127
|
-
### Build
|
128
|
-
|
129
|
-
* build: added missing pytest-bec-e2e dependency; closes #219 ([`56fdae4`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/56fdae42757bdb9fa301c1e425a77e98b6eaf92b))
|
130
|
-
|
131
|
-
* build: fixed dependency ranges; closes #135 ([`e6a06c9`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/e6a06c9f43e0ad6bbfcfa550a2f580d2a27aff66))
|
132
|
-
|
133
|
-
### Chore
|
134
|
-
|
135
|
-
* chore: sorted dependencies alphabetically ([`21c807f`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/21c807f35831fdd1ef2e488ab90edae4719f0cb7))
|
136
|
-
|
137
|
-
### Documentation
|
138
|
-
|
139
|
-
* docs: fixed doc string ([`f979a63`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/f979a63d3d1a008f80e500510909750878ff4303))
|
140
|
-
|
141
|
-
### Fix
|
142
|
-
|
143
|
-
* fix(rings): rings properties updated right after setting ([`c8b7367`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/c8b7367815b095f8e4aa8b819481efb701f2e542))
|
144
|
-
|
145
|
-
* fix(motor_map): motor map can be removed from BECFigure with .remove() ([`6b25abf`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/6b25abff70280271e2eeb70450553c05d4b7c99c))
|
146
|
-
|
147
|
-
### Test
|
148
|
-
|
149
|
-
* test(bec_figure): tests for removing widgets with rpc e2e ([`a268caa`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/a268caaa30711fcc7ece542d24578d74cbf65c77))
|
PKG-INFO
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: bec_widgets
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.82.0
|
4
4
|
Summary: BEC Widgets
|
5
5
|
Project-URL: Bug Tracker, https://gitlab.psi.ch/bec/bec_widgets/issues
|
6
6
|
Project-URL: Homepage, https://gitlab.psi.ch/bec/bec_widgets
|
@@ -28,9 +28,6 @@ Requires-Dist: pytest-random-order~=1.1; extra == 'dev'
|
|
28
28
|
Requires-Dist: pytest-timeout~=2.2; extra == 'dev'
|
29
29
|
Requires-Dist: pytest-xvfb~=3.0; extra == 'dev'
|
30
30
|
Requires-Dist: pytest~=8.0; extra == 'dev'
|
31
|
-
Provides-Extra: pyqt5
|
32
|
-
Requires-Dist: pyqt5>=5.9; extra == 'pyqt5'
|
33
|
-
Requires-Dist: pyqtwebengine>=5.9; extra == 'pyqt5'
|
34
31
|
Provides-Extra: pyqt6
|
35
32
|
Requires-Dist: pyqt6-webengine>=6.7; extra == 'pyqt6'
|
36
33
|
Requires-Dist: pyqt6>=6.7; extra == 'pyqt6'
|
README.md
CHANGED
@@ -17,7 +17,7 @@ cd bec_widgets
|
|
17
17
|
pip install -e .[dev,pyqt6]
|
18
18
|
```
|
19
19
|
|
20
|
-
BEC Widgets currently supports both
|
20
|
+
BEC Widgets currently supports both Pyside6 and PyQt6, however, no default distribution is specified. As a result, users must install one of the supported
|
21
21
|
Python Qt distributions manually.
|
22
22
|
|
23
23
|
To select a specific Python Qt distribution, install the package with an additional tag:
|
@@ -28,7 +28,7 @@ pip install bec_widgets[pyqt6]
|
|
28
28
|
or
|
29
29
|
|
30
30
|
```bash
|
31
|
-
pip install bec_widgets[
|
31
|
+
pip install bec_widgets[pyside6]
|
32
32
|
```
|
33
33
|
## Documentation
|
34
34
|
|
bec_widgets/cli/client.py
CHANGED
@@ -13,14 +13,15 @@ class Widgets(str, enum.Enum):
|
|
13
13
|
Enum for the available widgets.
|
14
14
|
"""
|
15
15
|
|
16
|
-
BECQueue = "BECQueue"
|
17
|
-
BECStatusBox = "BECStatusBox"
|
18
16
|
BECDock = "BECDock"
|
19
17
|
BECDockArea = "BECDockArea"
|
20
18
|
BECFigure = "BECFigure"
|
21
19
|
BECMotorMapWidget = "BECMotorMapWidget"
|
20
|
+
BECQueue = "BECQueue"
|
21
|
+
BECStatusBox = "BECStatusBox"
|
22
22
|
RingProgressBar = "RingProgressBar"
|
23
23
|
ScanControl = "ScanControl"
|
24
|
+
StopButton = "StopButton"
|
24
25
|
TextBox = "TextBox"
|
25
26
|
VSCodeEditor = "VSCodeEditor"
|
26
27
|
WebsiteWidget = "WebsiteWidget"
|
bec_widgets/cli/generate_cli.py
CHANGED
@@ -5,13 +5,12 @@ import argparse
|
|
5
5
|
import inspect
|
6
6
|
import os
|
7
7
|
import sys
|
8
|
-
from typing import Literal
|
9
8
|
|
10
9
|
import black
|
11
10
|
import isort
|
12
11
|
|
13
12
|
from bec_widgets.utils.generate_designer_plugin import DesignerPluginGenerator
|
14
|
-
from bec_widgets.utils.plugin_utils import get_rpc_classes
|
13
|
+
from bec_widgets.utils.plugin_utils import BECClassContainer, get_rpc_classes
|
15
14
|
|
16
15
|
if sys.version_info >= (3, 11):
|
17
16
|
from typing import get_overloads
|
@@ -40,17 +39,20 @@ from bec_widgets.cli.client_utils import RPCBase, rpc_call, BECGuiClientMixin
|
|
40
39
|
|
41
40
|
self.content = ""
|
42
41
|
|
43
|
-
def generate_client(
|
44
|
-
self, published_classes: dict[Literal["connector_classes", "top_level_classes"], list[type]]
|
45
|
-
):
|
42
|
+
def generate_client(self, class_container: BECClassContainer):
|
46
43
|
"""
|
47
44
|
Generate the client for the published classes.
|
48
45
|
|
49
46
|
Args:
|
50
|
-
|
47
|
+
class_container: The class container with the classes to generate the client for.
|
51
48
|
"""
|
52
|
-
|
53
|
-
|
49
|
+
rpc_top_level_classes = class_container.rpc_top_level_classes
|
50
|
+
rpc_top_level_classes.sort(key=lambda x: x.__name__)
|
51
|
+
connector_classes = class_container.connector_classes
|
52
|
+
connector_classes.sort(key=lambda x: x.__name__)
|
53
|
+
|
54
|
+
self.write_client_enum(rpc_top_level_classes)
|
55
|
+
for cls in connector_classes:
|
54
56
|
self.content += "\n\n"
|
55
57
|
self.generate_content_for_class(cls)
|
56
58
|
|
@@ -156,13 +158,12 @@ def main():
|
|
156
158
|
client_path = os.path.join(current_path, "client.py")
|
157
159
|
|
158
160
|
rpc_classes = get_rpc_classes("bec_widgets")
|
159
|
-
rpc_classes["connector_classes"].sort(key=lambda x: x.__name__)
|
160
161
|
|
161
162
|
generator = ClientGenerator()
|
162
163
|
generator.generate_client(rpc_classes)
|
163
164
|
generator.write(client_path)
|
164
165
|
|
165
|
-
for cls in rpc_classes
|
166
|
+
for cls in rpc_classes.plugins:
|
166
167
|
plugin = DesignerPluginGenerator(cls)
|
167
168
|
if not hasattr(plugin, "info"):
|
168
169
|
continue
|
@@ -29,7 +29,7 @@ class RPCWidgetHandler:
|
|
29
29
|
from bec_widgets.utils.plugin_utils import get_rpc_classes
|
30
30
|
|
31
31
|
clss = get_rpc_classes("bec_widgets")
|
32
|
-
self._widget_classes = {cls.__name__: cls for cls in clss
|
32
|
+
self._widget_classes = {cls.__name__: cls for cls in clss.top_level_classes}
|
33
33
|
|
34
34
|
def create_widget(self, widget_type, **kwargs) -> BECConnector:
|
35
35
|
"""
|
bec_widgets/examples/__init__.py
CHANGED
@@ -13,6 +13,7 @@ from qtpy.QtCore import QObject, QRunnable, QThreadPool, Signal
|
|
13
13
|
from qtpy.QtCore import Slot as pyqtSlot
|
14
14
|
|
15
15
|
from bec_widgets.cli.rpc_register import RPCRegister
|
16
|
+
from bec_widgets.utils.bec_widget import BECWidget
|
16
17
|
from bec_widgets.utils.yaml_dialog import load_yaml, load_yaml_gui, save_yaml, save_yaml_gui
|
17
18
|
|
18
19
|
BECDispatcher = lazy_import_from("bec_widgets.utils.bec_dispatcher", ("BECDispatcher",))
|
@@ -63,7 +64,7 @@ class Worker(QRunnable):
|
|
63
64
|
self.signals.completed.emit()
|
64
65
|
|
65
66
|
|
66
|
-
class BECConnector:
|
67
|
+
class BECConnector(BECWidget):
|
67
68
|
"""Connection mixin class for all BEC widgets, to handle BEC client and device manager"""
|
68
69
|
|
69
70
|
USER_ACCESS = ["_config_dict", "_get_all_rpc"]
|
@@ -8,7 +8,7 @@ import redis
|
|
8
8
|
from bec_lib.client import BECClient
|
9
9
|
from bec_lib.redis_connector import MessageObject, RedisConnector
|
10
10
|
from bec_lib.service_config import ServiceConfig
|
11
|
-
from qtpy.QtCore import
|
11
|
+
from qtpy.QtCore import PYQT6, PYSIDE6, QCoreApplication, QObject
|
12
12
|
from qtpy.QtCore import Signal as pyqtSignal
|
13
13
|
|
14
14
|
if TYPE_CHECKING:
|
@@ -127,9 +127,9 @@ class BECDispatcher:
|
|
127
127
|
return
|
128
128
|
|
129
129
|
# shutdown QCoreApp if it exists
|
130
|
-
if
|
130
|
+
if PYQT6:
|
131
131
|
cls.qapp.exit()
|
132
|
-
elif
|
132
|
+
elif PYSIDE6:
|
133
133
|
cls.qapp.shutdown()
|
134
134
|
cls.qapp = None
|
135
135
|
|
@@ -19,7 +19,7 @@ class EntryValidator:
|
|
19
19
|
device = self.devices[name]
|
20
20
|
description = device.describe()
|
21
21
|
|
22
|
-
if entry is None:
|
22
|
+
if entry is None or entry == "":
|
23
23
|
entry = next(iter(device._hints), name) if hasattr(device, "_hints") else name
|
24
24
|
if entry not in description:
|
25
25
|
raise ValueError(f"Entry '{entry}' not found in device '{name}' signals")
|
@@ -1,12 +1,13 @@
|
|
1
1
|
import importlib
|
2
2
|
import inspect
|
3
3
|
import os
|
4
|
-
from
|
4
|
+
from dataclasses import dataclass
|
5
5
|
|
6
6
|
from bec_lib.plugin_helper import _get_available_plugins
|
7
7
|
from qtpy.QtWidgets import QGraphicsWidget, QWidget
|
8
8
|
|
9
9
|
from bec_widgets.utils import BECConnector
|
10
|
+
from bec_widgets.utils.bec_widget import BECWidget
|
10
11
|
|
11
12
|
|
12
13
|
def get_plugin_widgets() -> dict[str, BECConnector]:
|
@@ -44,9 +45,74 @@ def _filter_plugins(obj):
|
|
44
45
|
return inspect.isclass(obj) and issubclass(obj, BECConnector)
|
45
46
|
|
46
47
|
|
47
|
-
|
48
|
-
|
49
|
-
|
48
|
+
@dataclass
|
49
|
+
class BECClassInfo:
|
50
|
+
name: str
|
51
|
+
module: str
|
52
|
+
file: str
|
53
|
+
obj: type
|
54
|
+
is_connector: bool = False
|
55
|
+
is_widget: bool = False
|
56
|
+
is_top_level: bool = False
|
57
|
+
|
58
|
+
|
59
|
+
class BECClassContainer:
|
60
|
+
def __init__(self):
|
61
|
+
self._collection = []
|
62
|
+
|
63
|
+
def add_class(self, class_info: BECClassInfo):
|
64
|
+
"""
|
65
|
+
Add a class to the collection.
|
66
|
+
|
67
|
+
Args:
|
68
|
+
class_info(BECClassInfo): The class information
|
69
|
+
"""
|
70
|
+
self.collection.append(class_info)
|
71
|
+
|
72
|
+
@property
|
73
|
+
def collection(self):
|
74
|
+
"""
|
75
|
+
Get the collection of classes.
|
76
|
+
"""
|
77
|
+
return self._collection
|
78
|
+
|
79
|
+
@property
|
80
|
+
def connector_classes(self):
|
81
|
+
"""
|
82
|
+
Get all connector classes.
|
83
|
+
"""
|
84
|
+
return [info.obj for info in self.collection if info.is_connector]
|
85
|
+
|
86
|
+
@property
|
87
|
+
def top_level_classes(self):
|
88
|
+
"""
|
89
|
+
Get all top-level classes.
|
90
|
+
"""
|
91
|
+
return [info.obj for info in self.collection if info.is_top_level]
|
92
|
+
|
93
|
+
@property
|
94
|
+
def plugins(self):
|
95
|
+
"""
|
96
|
+
Get all plugins. These are all classes that are on the top level and are widgets.
|
97
|
+
"""
|
98
|
+
return [info.obj for info in self.collection if info.is_widget and info.is_top_level]
|
99
|
+
|
100
|
+
@property
|
101
|
+
def widgets(self):
|
102
|
+
"""
|
103
|
+
Get all widgets. These are all classes inheriting from BECWidget.
|
104
|
+
"""
|
105
|
+
return [info.obj for info in self.collection if info.is_widget]
|
106
|
+
|
107
|
+
@property
|
108
|
+
def rpc_top_level_classes(self):
|
109
|
+
"""
|
110
|
+
Get all top-level classes that are RPC-enabled. These are all classes that users can choose from.
|
111
|
+
"""
|
112
|
+
return [info.obj for info in self.collection if info.is_top_level and info.is_connector]
|
113
|
+
|
114
|
+
|
115
|
+
def get_rpc_classes(repo_name: str) -> BECClassContainer:
|
50
116
|
"""
|
51
117
|
Get all RPC-enabled classes in the specified repository.
|
52
118
|
|
@@ -56,8 +122,7 @@ def get_rpc_classes(
|
|
56
122
|
Returns:
|
57
123
|
dict: A dictionary with keys "connector_classes" and "top_level_classes" and values as lists of classes.
|
58
124
|
"""
|
59
|
-
|
60
|
-
top_level_classes = []
|
125
|
+
collection = BECClassContainer()
|
61
126
|
anchor_module = importlib.import_module(f"{repo_name}.widgets")
|
62
127
|
directory = os.path.dirname(anchor_module.__file__)
|
63
128
|
for root, _, files in sorted(os.walk(directory)):
|
@@ -78,11 +143,16 @@ def get_rpc_classes(
|
|
78
143
|
obj = getattr(module, name)
|
79
144
|
if not hasattr(obj, "__module__") or obj.__module__ != module.__name__:
|
80
145
|
continue
|
81
|
-
if isinstance(obj, type)
|
82
|
-
|
146
|
+
if isinstance(obj, type):
|
147
|
+
class_info = BECClassInfo(name=name, module=module_name, file=path, obj=obj)
|
148
|
+
if issubclass(obj, BECConnector):
|
149
|
+
class_info.is_connector = True
|
150
|
+
if issubclass(obj, BECWidget):
|
151
|
+
class_info.is_widget = True
|
83
152
|
if len(subs) == 1 and (
|
84
153
|
issubclass(obj, QWidget) or issubclass(obj, QGraphicsWidget)
|
85
154
|
):
|
86
|
-
|
155
|
+
class_info.is_top_level = True
|
156
|
+
collection.add_class(class_info)
|
87
157
|
|
88
|
-
return
|
158
|
+
return collection
|
bec_widgets/utils/ui_loader.py
CHANGED
@@ -1,20 +1,18 @@
|
|
1
|
+
import os
|
2
|
+
|
1
3
|
from qtpy import PYQT6, PYSIDE6, QT_VERSION
|
2
4
|
from qtpy.QtCore import QFile, QIODevice
|
3
5
|
|
6
|
+
from bec_widgets.utils.generate_designer_plugin import DesignerPluginInfo
|
7
|
+
from bec_widgets.utils.plugin_utils import get_rpc_classes
|
8
|
+
|
4
9
|
if PYSIDE6:
|
5
10
|
from PySide6.QtUiTools import QUiLoader
|
6
11
|
|
7
|
-
from bec_widgets.utils.plugin_utils import get_rpc_classes
|
8
|
-
from bec_widgets.widgets.buttons.color_button.color_button import ColorButton
|
9
|
-
|
10
12
|
class CustomUiLoader(QUiLoader):
|
11
|
-
def __init__(self, baseinstance):
|
13
|
+
def __init__(self, baseinstance, custom_widgets: dict = None):
|
12
14
|
super().__init__(baseinstance)
|
13
|
-
|
14
|
-
|
15
|
-
widgets.append(ColorButton)
|
16
|
-
|
17
|
-
self.custom_widgets = {widget.__name__: widget for widget in widgets}
|
15
|
+
self.custom_widgets = custom_widgets or {}
|
18
16
|
|
19
17
|
self.baseinstance = baseinstance
|
20
18
|
|
@@ -27,25 +25,21 @@ if PYSIDE6:
|
|
27
25
|
|
28
26
|
|
29
27
|
class UILoader:
|
30
|
-
"""Universal UI loader for
|
28
|
+
"""Universal UI loader for PyQt6 and PySide6."""
|
31
29
|
|
32
30
|
def __init__(self, parent=None):
|
33
31
|
self.parent = parent
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
self.loader = loadUi
|
47
|
-
else:
|
48
|
-
raise ImportError("No compatible Qt bindings found.")
|
32
|
+
|
33
|
+
widgets = get_rpc_classes("bec_widgets").top_level_classes
|
34
|
+
|
35
|
+
self.custom_widgets = {widget.__name__: widget for widget in widgets}
|
36
|
+
|
37
|
+
if PYSIDE6:
|
38
|
+
self.loader = self.load_ui_pyside6
|
39
|
+
elif PYQT6:
|
40
|
+
self.loader = self.load_ui_pyqt6
|
41
|
+
else:
|
42
|
+
raise ImportError("No compatible Qt bindings found.")
|
49
43
|
|
50
44
|
def load_ui_pyside6(self, ui_file, parent=None):
|
51
45
|
"""
|
@@ -58,7 +52,7 @@ class UILoader:
|
|
58
52
|
QWidget: The loaded widget.
|
59
53
|
"""
|
60
54
|
|
61
|
-
loader = CustomUiLoader(parent)
|
55
|
+
loader = CustomUiLoader(parent, self.custom_widgets)
|
62
56
|
file = QFile(ui_file)
|
63
57
|
if not file.open(QIODevice.ReadOnly):
|
64
58
|
raise IOError(f"Cannot open file: {ui_file}")
|
@@ -66,6 +60,71 @@ class UILoader:
|
|
66
60
|
file.close()
|
67
61
|
return widget
|
68
62
|
|
63
|
+
def load_ui_pyqt6(self, ui_file, parent=None):
|
64
|
+
"""
|
65
|
+
Specific loader for PyQt6 using loadUi.
|
66
|
+
Args:
|
67
|
+
ui_file(str): Path to the .ui file.
|
68
|
+
parent(QWidget): Parent widget.
|
69
|
+
|
70
|
+
Returns:
|
71
|
+
QWidget: The loaded widget.
|
72
|
+
"""
|
73
|
+
from PyQt6.uic.Loader.loader import DynamicUILoader
|
74
|
+
|
75
|
+
class CustomDynamicUILoader(DynamicUILoader):
|
76
|
+
def __init__(self, package, custom_widgets: dict = None):
|
77
|
+
super().__init__(package)
|
78
|
+
self.custom_widgets = custom_widgets or {}
|
79
|
+
|
80
|
+
def _handle_custom_widgets(self, el):
|
81
|
+
"""Handle the <customwidgets> element."""
|
82
|
+
|
83
|
+
def header2module(header):
|
84
|
+
"""header2module(header) -> string
|
85
|
+
|
86
|
+
Convert paths to C++ header files to according Python modules
|
87
|
+
>>> header2module("foo/bar/baz.h")
|
88
|
+
'foo.bar.baz'
|
89
|
+
"""
|
90
|
+
|
91
|
+
if header.endswith(".h"):
|
92
|
+
header = header[:-2]
|
93
|
+
|
94
|
+
mpath = []
|
95
|
+
for part in header.split("/"):
|
96
|
+
# Ignore any empty parts or those that refer to the current
|
97
|
+
# directory.
|
98
|
+
if part not in ("", "."):
|
99
|
+
if part == "..":
|
100
|
+
# We should allow this for Python3.
|
101
|
+
raise SyntaxError(
|
102
|
+
"custom widget header file name may not contain '..'."
|
103
|
+
)
|
104
|
+
|
105
|
+
mpath.append(part)
|
106
|
+
|
107
|
+
return ".".join(mpath)
|
108
|
+
|
109
|
+
for custom_widget in el:
|
110
|
+
classname = custom_widget.findtext("class")
|
111
|
+
header = custom_widget.findtext("header")
|
112
|
+
if header:
|
113
|
+
header = self._translate_bec_widgets_header(header)
|
114
|
+
self.factory.addCustomWidget(
|
115
|
+
classname,
|
116
|
+
custom_widget.findtext("extends") or "QWidget",
|
117
|
+
header2module(header),
|
118
|
+
)
|
119
|
+
|
120
|
+
def _translate_bec_widgets_header(self, header):
|
121
|
+
for name, value in self.custom_widgets.items():
|
122
|
+
if header == DesignerPluginInfo.pascal_to_snake(name):
|
123
|
+
return value.__module__
|
124
|
+
return header
|
125
|
+
|
126
|
+
return CustomDynamicUILoader("", self.custom_widgets).loadUi(ui_file, parent)
|
127
|
+
|
69
128
|
def load_ui(self, ui_file, parent=None):
|
70
129
|
"""
|
71
130
|
Universal UI loader method.
|
@@ -25,6 +25,7 @@ class DeviceInputBase(BECConnector):
|
|
25
25
|
super().__init__(client=client, config=config, gui_id=gui_id)
|
26
26
|
|
27
27
|
self.get_bec_shortcuts()
|
28
|
+
self._device_filter = None
|
28
29
|
self._devices = []
|
29
30
|
|
30
31
|
@property
|
@@ -56,6 +57,7 @@ class DeviceInputBase(BECConnector):
|
|
56
57
|
"""
|
57
58
|
self.validate_device_filter(device_filter)
|
58
59
|
self.config.device_filter = device_filter
|
60
|
+
self._device_filter = device_filter
|
59
61
|
|
60
62
|
def set_default_device(self, default_device: str):
|
61
63
|
"""
|
@@ -0,0 +1,36 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from typing import Literal
|
4
|
+
|
5
|
+
import pyqtgraph as pg
|
6
|
+
|
7
|
+
|
8
|
+
class ColorButton(pg.ColorButton):
|
9
|
+
"""
|
10
|
+
A ColorButton that opens a dialog to select a color. Inherits from pyqtgraph.ColorButton.
|
11
|
+
Patches event loop of the ColorDialog, if opened in another QDialog.
|
12
|
+
"""
|
13
|
+
|
14
|
+
def __init__(self, *args, **kwargs):
|
15
|
+
super().__init__(*args, **kwargs)
|
16
|
+
|
17
|
+
def selectColor(self):
|
18
|
+
self.origColor = self.color()
|
19
|
+
self.colorDialog.setCurrentColor(self.color())
|
20
|
+
self.colorDialog.open()
|
21
|
+
self.colorDialog.exec()
|
22
|
+
|
23
|
+
def get_color(self, format: Literal["RGBA", "HEX"] = "RGBA") -> tuple | str:
|
24
|
+
"""
|
25
|
+
Get the color of the button in the specified format.
|
26
|
+
|
27
|
+
Args:
|
28
|
+
format(Literal["RGBA", "HEX"]): The format of the returned color.
|
29
|
+
|
30
|
+
Returns:
|
31
|
+
tuple|str: The color in the specified format.
|
32
|
+
"""
|
33
|
+
if format == "RGBA":
|
34
|
+
return self.color().getRgb()
|
35
|
+
if format == "HEX":
|
36
|
+
return self.color().name()
|
@@ -3,7 +3,7 @@ import os
|
|
3
3
|
from qtpy.QtDesigner import QDesignerCustomWidgetInterface
|
4
4
|
from qtpy.QtGui import QIcon
|
5
5
|
|
6
|
-
from bec_widgets.widgets.
|
6
|
+
from bec_widgets.widgets.color_button.color_button import ColorButton
|
7
7
|
|
8
8
|
DOM_XML = """
|
9
9
|
<ui language='c++'>
|
@@ -6,7 +6,7 @@ def main(): # pragma: no cover
|
|
6
6
|
return
|
7
7
|
from PySide6.QtDesigner import QPyDesignerCustomWidgetCollection
|
8
8
|
|
9
|
-
from bec_widgets.widgets.
|
9
|
+
from bec_widgets.widgets.color_button.color_button_plugin import ColorButtonPlugin
|
10
10
|
|
11
11
|
QPyDesignerCustomWidgetCollection.addCustomWidget(ColorButtonPlugin())
|
12
12
|
|