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.
Files changed (89) hide show
  1. .gitlab-ci.yml +0 -1
  2. .pylintrc +1 -1
  3. CHANGELOG.md +52 -62
  4. PKG-INFO +1 -4
  5. README.md +2 -2
  6. bec_widgets/cli/client.py +3 -2
  7. bec_widgets/cli/generate_cli.py +11 -10
  8. bec_widgets/cli/rpc_wigdet_handler.py +1 -1
  9. bec_widgets/examples/__init__.py +0 -9
  10. bec_widgets/utils/bec_connector.py +2 -1
  11. bec_widgets/utils/bec_dispatcher.py +3 -3
  12. bec_widgets/utils/bec_widget.py +2 -0
  13. bec_widgets/utils/entry_validator.py +1 -1
  14. bec_widgets/utils/plugin_utils.py +80 -10
  15. bec_widgets/utils/ui_loader.py +85 -26
  16. bec_widgets/widgets/{device_inputs → base_classes}/device_input_base.py +2 -0
  17. bec_widgets/widgets/color_button/color_button.py +36 -0
  18. bec_widgets/widgets/{buttons/color_button → color_button}/color_button_plugin.py +1 -1
  19. bec_widgets/widgets/{buttons/color_button → color_button}/register_color_button.py +1 -1
  20. bec_widgets/widgets/device_combobox/assets/device_combobox_icon.png +0 -0
  21. bec_widgets/widgets/{device_inputs/device_combobox → device_combobox}/device_combobox.py +2 -2
  22. bec_widgets/widgets/device_combobox/device_combobox.pyproject +3 -0
  23. bec_widgets/widgets/{device_inputs/device_combobox → device_combobox}/device_combobox_plugin.py +6 -3
  24. bec_widgets/widgets/{device_inputs/device_combobox → device_combobox}/register_device_combobox.py +1 -3
  25. bec_widgets/widgets/device_line_edit/assets/line_edit_icon.png +0 -0
  26. bec_widgets/widgets/{device_inputs/device_line_edit → device_line_edit}/device_line_edit.py +2 -2
  27. bec_widgets/widgets/device_line_edit/device_line_edit.pyproject +3 -0
  28. bec_widgets/widgets/{device_inputs/device_line_edit → device_line_edit}/device_line_edit_plugin.py +6 -3
  29. bec_widgets/widgets/{device_inputs/device_line_edit → device_line_edit}/register_device_line_edit.py +1 -3
  30. bec_widgets/widgets/figure/plots/waveform/waveform.py +8 -2
  31. bec_widgets/widgets/motor_map/motor_map_dialog/motor_map_settings.py +1 -1
  32. bec_widgets/widgets/motor_map/motor_map_dialog/motor_map_toolbar.py +1 -1
  33. bec_widgets/widgets/motor_map/motor_map_widget.py +0 -12
  34. bec_widgets/widgets/scan_control/scan_control.py +1 -1
  35. bec_widgets/widgets/scan_control/scan_group_box.py +1 -1
  36. bec_widgets/widgets/stop_button/assets/stop.png +0 -0
  37. bec_widgets/widgets/stop_button/register_stop_button.py +15 -0
  38. bec_widgets/widgets/{buttons/stop_button → stop_button}/stop_button.py +5 -12
  39. bec_widgets/widgets/stop_button/stop_button.pyproject +1 -0
  40. bec_widgets/widgets/stop_button/stop_button_plugin.py +57 -0
  41. bec_widgets/widgets/toggle/register_toggle_switch.py +15 -0
  42. bec_widgets/widgets/toggle/toggle.py +149 -0
  43. bec_widgets/widgets/toggle/toggle_switch.pyproject +1 -0
  44. bec_widgets/widgets/toggle/toggle_switch_plugin.py +54 -0
  45. {bec_widgets-0.79.3.dist-info → bec_widgets-0.82.0.dist-info}/METADATA +1 -4
  46. {bec_widgets-0.79.3.dist-info → bec_widgets-0.82.0.dist-info}/RECORD +66 -74
  47. docs/introduction/introduction.md +1 -1
  48. docs/user/getting_started/installation.md +2 -2
  49. pyproject.toml +1 -2
  50. tests/unit_tests/test_device_input_base.py +1 -1
  51. tests/unit_tests/test_device_input_widgets.py +2 -2
  52. tests/unit_tests/test_generate_cli_client.py +41 -17
  53. tests/unit_tests/test_plugin_utils.py +2 -3
  54. tests/unit_tests/test_stop_button.py +5 -2
  55. tests/unit_tests/test_toggle.py +38 -0
  56. bec_widgets/examples/motor_movement/__init__.py +0 -9
  57. bec_widgets/examples/motor_movement/motor_control_compilations.py +0 -250
  58. bec_widgets/examples/motor_movement/motor_controller.ui +0 -926
  59. bec_widgets/widgets/buttons/__init__.py +0 -1
  60. bec_widgets/widgets/buttons/color_button/color_button.py +0 -17
  61. bec_widgets/widgets/device_inputs/__init__.py +0 -2
  62. bec_widgets/widgets/device_inputs/device_combobox/device_combobox.pyproject +0 -4
  63. bec_widgets/widgets/device_inputs/device_combobox/launch_device_combobox.py +0 -11
  64. bec_widgets/widgets/device_inputs/device_line_edit/device_line_edit.pyproject +0 -4
  65. bec_widgets/widgets/device_inputs/device_line_edit/launch_device_line_edit.py +0 -11
  66. bec_widgets/widgets/motor_control/motor_control.py +0 -252
  67. bec_widgets/widgets/motor_control/motor_table/motor_table.py +0 -484
  68. bec_widgets/widgets/motor_control/motor_table/motor_table.ui +0 -113
  69. bec_widgets/widgets/motor_control/movement_absolute/__init__.py +0 -0
  70. bec_widgets/widgets/motor_control/movement_absolute/movement_absolute.py +0 -159
  71. bec_widgets/widgets/motor_control/movement_absolute/movement_absolute.ui +0 -149
  72. bec_widgets/widgets/motor_control/movement_relative/__init__.py +0 -0
  73. bec_widgets/widgets/motor_control/movement_relative/movement_relative.py +0 -230
  74. bec_widgets/widgets/motor_control/movement_relative/movement_relative.ui +0 -298
  75. bec_widgets/widgets/motor_control/selection/__init__.py +0 -0
  76. bec_widgets/widgets/motor_control/selection/selection.py +0 -110
  77. bec_widgets/widgets/motor_control/selection/selection.ui +0 -69
  78. tests/unit_tests/test_motor_control.py +0 -588
  79. /bec_widgets/widgets/{buttons/color_button → base_classes}/__init__.py +0 -0
  80. /bec_widgets/widgets/{buttons/stop_button → color_button}/__init__.py +0 -0
  81. /bec_widgets/widgets/{buttons/color_button → color_button}/assets/color_button.png +0 -0
  82. /bec_widgets/widgets/{buttons/color_button → color_button}/color_button.pyproject +0 -0
  83. /bec_widgets/widgets/{device_inputs/device_combobox → device_combobox}/__init__.py +0 -0
  84. /bec_widgets/widgets/{device_inputs/device_line_edit → device_line_edit}/__init__.py +0 -0
  85. /bec_widgets/widgets/{motor_control → stop_button}/__init__.py +0 -0
  86. /bec_widgets/widgets/{motor_control/motor_table → toggle}/__init__.py +0 -0
  87. {bec_widgets-0.79.3.dist-info → bec_widgets-0.82.0.dist-info}/WHEEL +0 -0
  88. {bec_widgets-0.79.3.dist-info → bec_widgets-0.82.0.dist-info}/entry_points.txt +0 -0
  89. {bec_widgets-0.79.3.dist-info → bec_widgets-0.82.0.dist-info}/licenses/LICENSE +0 -0
.gitlab-ci.yml CHANGED
@@ -154,7 +154,6 @@ test-matrix:
154
154
  - "3.12"
155
155
  QT_PCKG:
156
156
  - "pyside6"
157
- - "pyqt5"
158
157
  - "pyqt6"
159
158
 
160
159
  stage: AdditionalTests
.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=PyQt5, pyqtgraph
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.79.3
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 PyQt5 and PyQt6, however, no default distribution is specified. As a result, users must install one of the supported
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[pyqt5]
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"
@@ -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
- published_classes(dict): A dictionary with keys "connector_classes" and "top_level_classes" and values as lists of classes.
47
+ class_container: The class container with the classes to generate the client for.
51
48
  """
52
- self.write_client_enum(published_classes["top_level_classes"])
53
- for cls in published_classes["connector_classes"]:
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["top_level_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["top_level_classes"]}
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
  """
@@ -1,9 +0,0 @@
1
- from .motor_movement import (
2
- MotorControlApp,
3
- MotorControlMap,
4
- MotorControlPanel,
5
- MotorControlPanelAbsolute,
6
- MotorControlPanelRelative,
7
- MotorCoordinateTable,
8
- MotorThread,
9
- )
@@ -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 PYQT5, PYQT6, PYSIDE2, PYSIDE6, QCoreApplication, QObject
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 PYQT5 or PYQT6:
130
+ if PYQT6:
131
131
  cls.qapp.exit()
132
- elif PYSIDE2 or PYSIDE6:
132
+ elif PYSIDE6:
133
133
  cls.qapp.shutdown()
134
134
  cls.qapp = None
135
135
 
@@ -0,0 +1,2 @@
1
+ class BECWidget:
2
+ """Base class for all BEC widgets."""
@@ -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 typing import Literal
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
- def get_rpc_classes(
48
- repo_name: str,
49
- ) -> dict[Literal["connector_classes", "top_level_classes"], list[type]]:
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
- connector_classes = []
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) and issubclass(obj, BECConnector):
82
- connector_classes.append(obj)
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
- top_level_classes.append(obj)
155
+ class_info.is_top_level = True
156
+ collection.add_class(class_info)
87
157
 
88
- return {"connector_classes": connector_classes, "top_level_classes": top_level_classes}
158
+ return collection
@@ -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
- widgets = get_rpc_classes("bec_widgets").get("top_level_classes", [])
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 PyQt5, PyQt6, PySide2, and PySide6."""
28
+ """Universal UI loader for PyQt6 and PySide6."""
31
29
 
32
30
  def __init__(self, parent=None):
33
31
  self.parent = parent
34
- if QT_VERSION.startswith("5"):
35
- # PyQt5 or PySide2
36
- from qtpy import uic
37
-
38
- self.loader = uic.loadUi
39
- elif QT_VERSION.startswith("6"):
40
- # PyQt6 or PySide6
41
- if PYSIDE6:
42
- self.loader = self.load_ui_pyside6
43
- elif PYQT6:
44
- from PyQt6.uic import loadUi
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.buttons.color_button.color_button import ColorButton
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.buttons.color_button.color_button_plugin import ColorButtonPlugin
9
+ from bec_widgets.widgets.color_button.color_button_plugin import ColorButtonPlugin
10
10
 
11
11
  QPyDesignerCustomWidgetCollection.addCustomWidget(ColorButtonPlugin())
12
12