bec-widgets 1.25.1__py3-none-any.whl → 2.0.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 (196) hide show
  1. .gitlab-ci.yml +3 -5
  2. CHANGELOG.md +631 -0
  3. PKG-INFO +3 -3
  4. bec_widgets/__init__.py +4 -0
  5. bec_widgets/applications/bw_launch.py +23 -0
  6. bec_widgets/applications/launch_window.py +430 -0
  7. bec_widgets/assets/app_icons/auto_update.png +0 -0
  8. bec_widgets/assets/app_icons/ui_loader_tile.png +0 -0
  9. bec_widgets/cli/__init__.py +0 -1
  10. bec_widgets/cli/client.py +1779 -2064
  11. bec_widgets/cli/client_utils.py +346 -174
  12. bec_widgets/cli/generate_cli.py +143 -37
  13. bec_widgets/cli/rpc/rpc_base.py +152 -21
  14. bec_widgets/cli/rpc/rpc_register.py +113 -6
  15. bec_widgets/cli/rpc/rpc_widget_handler.py +13 -11
  16. bec_widgets/cli/server.py +125 -239
  17. bec_widgets/examples/jupyter_console/jupyter_console_window.py +97 -145
  18. bec_widgets/examples/plugin_example_pyside/tictactoetaskmenu.py +1 -1
  19. bec_widgets/utils/bec_connector.py +190 -21
  20. bec_widgets/utils/bec_designer.py +7 -0
  21. bec_widgets/utils/bec_dispatcher.py +71 -4
  22. bec_widgets/utils/bec_plugin_helper.py +89 -0
  23. bec_widgets/utils/bec_signal_proxy.py +1 -1
  24. bec_widgets/utils/bec_widget.py +26 -10
  25. bec_widgets/utils/colors.py +1 -1
  26. bec_widgets/{qt_utils → utils}/compact_popup.py +2 -0
  27. bec_widgets/utils/container_utils.py +37 -12
  28. bec_widgets/utils/crosshair.py +25 -8
  29. bec_widgets/utils/entry_validator.py +3 -1
  30. bec_widgets/{qt_utils → utils}/error_popups.py +18 -0
  31. bec_widgets/{qt_utils → utils}/expandable_frame.py +2 -2
  32. bec_widgets/utils/forms_from_types/forms.py +182 -0
  33. bec_widgets/{widgets/editors/scan_metadata/_metadata_widgets.py → utils/forms_from_types/items.py} +41 -30
  34. bec_widgets/utils/generate_designer_plugin.py +40 -36
  35. bec_widgets/utils/linear_region_selector.py +2 -0
  36. bec_widgets/utils/name_utils.py +16 -0
  37. bec_widgets/{qt_utils → utils}/palette_viewer.py +2 -2
  38. bec_widgets/utils/plot_indicator_items.py +2 -5
  39. bec_widgets/utils/plugin_utils.py +47 -1
  40. bec_widgets/{qt_utils → utils}/round_frame.py +14 -14
  41. bec_widgets/utils/rpc_server.py +277 -0
  42. bec_widgets/utils/serialization.py +44 -0
  43. bec_widgets/{qt_utils → utils}/settings_dialog.py +26 -1
  44. bec_widgets/{qt_utils → utils}/side_panel.py +17 -10
  45. bec_widgets/{qt_utils → utils}/toolbar.py +69 -25
  46. bec_widgets/utils/ui_loader.py +8 -8
  47. bec_widgets/utils/widget_io.py +166 -25
  48. bec_widgets/widgets/containers/auto_update/auto_updates.py +364 -0
  49. bec_widgets/widgets/containers/dock/dock.py +157 -49
  50. bec_widgets/widgets/containers/dock/dock_area.py +186 -138
  51. bec_widgets/widgets/containers/layout_manager/layout_manager.py +2 -1
  52. bec_widgets/widgets/containers/main_window/addons/web_links.py +15 -0
  53. bec_widgets/widgets/containers/main_window/main_window.py +189 -41
  54. bec_widgets/widgets/control/buttons/button_abort/button_abort.py +3 -4
  55. bec_widgets/widgets/control/buttons/button_reset/button_reset.py +3 -4
  56. bec_widgets/widgets/control/buttons/button_resume/button_resume.py +3 -3
  57. bec_widgets/widgets/control/buttons/stop_button/stop_button.py +18 -7
  58. bec_widgets/widgets/control/device_control/position_indicator/position_indicator.py +22 -3
  59. bec_widgets/widgets/control/device_control/positioner_box/_base/positioner_box_base.py +31 -13
  60. bec_widgets/widgets/control/device_control/positioner_box/positioner_box/positioner_box.py +3 -1
  61. bec_widgets/widgets/control/device_control/positioner_box/positioner_box/positioner_box.ui +27 -4
  62. bec_widgets/widgets/control/device_control/positioner_box/positioner_box_2d/positioner_box_2d.py +5 -2
  63. bec_widgets/widgets/control/device_control/positioner_box/positioner_box_2d/positioner_box_2d.ui +97 -31
  64. bec_widgets/widgets/control/device_control/positioner_box/positioner_control_line/positioner_control_line.ui +11 -4
  65. bec_widgets/widgets/control/device_control/positioner_group/positioner_group.py +2 -3
  66. bec_widgets/widgets/control/device_input/base_classes/device_input_base.py +29 -4
  67. bec_widgets/widgets/control/device_input/base_classes/device_signal_input_base.py +1 -0
  68. bec_widgets/widgets/control/device_input/device_combobox/device_combobox.py +2 -2
  69. bec_widgets/widgets/control/device_input/device_line_edit/device_line_edit.py +2 -2
  70. bec_widgets/widgets/control/device_input/signal_combobox/signal_combobox.py +1 -2
  71. bec_widgets/widgets/control/device_input/signal_line_edit/signal_line_edit.py +1 -2
  72. bec_widgets/widgets/control/scan_control/scan_control.py +7 -5
  73. bec_widgets/widgets/control/scan_control/scan_group_box.py +28 -5
  74. bec_widgets/widgets/dap/dap_combo_box/dap_combo_box.py +1 -2
  75. bec_widgets/widgets/dap/lmfit_dialog/lmfit_dialog.py +3 -4
  76. bec_widgets/widgets/dap/lmfit_dialog/lmfit_dialog_vertical.ui +14 -8
  77. bec_widgets/widgets/editors/console/console.py +1 -1
  78. bec_widgets/widgets/editors/{scan_metadata/additional_metadata_table.py → dict_backed_table.py} +29 -6
  79. bec_widgets/widgets/editors/scan_metadata/__init__.py +0 -7
  80. bec_widgets/widgets/editors/scan_metadata/_util.py +1 -1
  81. bec_widgets/widgets/{plots/motor_map/register_bec_motor_map_widget.py → editors/scan_metadata/register_scan_metadata.py} +2 -4
  82. bec_widgets/widgets/editors/scan_metadata/scan_metadata.py +42 -136
  83. bec_widgets/widgets/editors/scan_metadata/scan_metadata.pyproject +1 -0
  84. bec_widgets/widgets/{plots/multi_waveform/bec_multi_waveform_widget_plugin.py → editors/scan_metadata/scan_metadata_plugin.py} +9 -9
  85. bec_widgets/widgets/editors/text_box/text_box.py +2 -3
  86. bec_widgets/widgets/editors/website/website.py +2 -2
  87. bec_widgets/widgets/games/minesweeper.py +3 -2
  88. bec_widgets/widgets/plots/image/image.py +960 -0
  89. bec_widgets/widgets/plots/image/image.pyproject +1 -0
  90. bec_widgets/widgets/plots/image/image_item.py +279 -0
  91. bec_widgets/widgets/plots/{motor_map/bec_motor_map_widget_plugin.py → image/image_plugin.py} +11 -13
  92. bec_widgets/widgets/{containers/figure/plots → plots}/image/image_processor.py +31 -64
  93. bec_widgets/widgets/plots/image/{register_bec_image_widget.py → register_image.py} +2 -2
  94. bec_widgets/widgets/plots/image/toolbar_bundles/image_selection.py +59 -0
  95. bec_widgets/widgets/plots/image/toolbar_bundles/processing.py +79 -0
  96. bec_widgets/widgets/plots/motor_map/motor_map.py +832 -0
  97. bec_widgets/widgets/plots/motor_map/motor_map.pyproject +1 -0
  98. bec_widgets/widgets/plots/motor_map/motor_map_plugin.py +54 -0
  99. bec_widgets/widgets/plots/{multi_waveform/register_bec_multi_waveform_widget.py → motor_map/register_motor_map.py} +2 -4
  100. bec_widgets/widgets/plots/motor_map/settings/motor_map_settings.py +129 -0
  101. bec_widgets/widgets/plots/motor_map/settings/motor_map_settings.ui +120 -0
  102. bec_widgets/widgets/plots/motor_map/toolbar_bundles/motor_selection.py +70 -0
  103. bec_widgets/widgets/plots/multi_waveform/multi_waveform.py +508 -0
  104. bec_widgets/widgets/plots/multi_waveform/multi_waveform.pyproject +1 -0
  105. bec_widgets/widgets/plots/multi_waveform/multi_waveform_plugin.py +54 -0
  106. bec_widgets/widgets/plots/multi_waveform/register_multi_waveform.py +15 -0
  107. bec_widgets/widgets/plots/multi_waveform/settings/control_panel.py +144 -0
  108. bec_widgets/widgets/plots/multi_waveform/settings/multi_waveform_controls.ui +164 -0
  109. bec_widgets/widgets/plots/multi_waveform/toolbar_bundles/monitor_selection.py +65 -0
  110. bec_widgets/widgets/{plots_next_gen → plots}/plot_base.py +321 -40
  111. bec_widgets/widgets/plots/{waveform/register_bec_waveform_widget.py → scatter_waveform/register_scatter_waveform.py} +3 -3
  112. bec_widgets/widgets/plots/scatter_waveform/scatter_curve.py +197 -0
  113. bec_widgets/widgets/plots/scatter_waveform/scatter_waveform.py +553 -0
  114. bec_widgets/widgets/plots/scatter_waveform/scatter_waveform.pyproject +1 -0
  115. bec_widgets/widgets/plots/{image/bec_image_widget_plugin.py → scatter_waveform/scatter_waveform_plugin.py} +9 -13
  116. bec_widgets/widgets/plots/scatter_waveform/settings/scatter_curve_setting.py +138 -0
  117. bec_widgets/widgets/plots/scatter_waveform/settings/scatter_curve_settings_horizontal.ui +195 -0
  118. bec_widgets/widgets/plots/scatter_waveform/settings/scatter_curve_settings_vertical.ui +204 -0
  119. bec_widgets/widgets/{plots_next_gen → plots}/setting_menus/axis_settings.py +8 -8
  120. bec_widgets/widgets/{plots_next_gen → plots}/toolbar_bundles/mouse_interactions.py +4 -18
  121. bec_widgets/widgets/{plots_next_gen → plots}/toolbar_bundles/plot_export.py +14 -3
  122. bec_widgets/widgets/{plots_next_gen → plots}/toolbar_bundles/roi_bundle.py +6 -1
  123. bec_widgets/widgets/{plots_next_gen → plots}/toolbar_bundles/save_state.py +2 -2
  124. bec_widgets/widgets/{containers/figure/plots/waveform/waveform_curve.py → plots/waveform/curve.py} +119 -49
  125. bec_widgets/widgets/plots/waveform/register_waveform.py +15 -0
  126. bec_widgets/widgets/plots/waveform/settings/curve_settings/curve_setting.py +125 -0
  127. bec_widgets/widgets/plots/waveform/settings/curve_settings/curve_tree.py +576 -0
  128. bec_widgets/widgets/plots/waveform/utils/__init__.py +0 -0
  129. bec_widgets/widgets/plots/waveform/utils/roi_manager.py +84 -0
  130. bec_widgets/widgets/plots/waveform/waveform.py +1794 -0
  131. bec_widgets/widgets/plots/waveform/waveform.pyproject +1 -0
  132. bec_widgets/widgets/plots/waveform/{bec_waveform_widget_plugin.py → waveform_plugin.py} +9 -13
  133. bec_widgets/widgets/progress/bec_progressbar/bec_progressbar.py +1 -2
  134. bec_widgets/widgets/progress/ring_progress_bar/ring.py +11 -10
  135. bec_widgets/widgets/progress/ring_progress_bar/ring_progress_bar.py +24 -14
  136. bec_widgets/widgets/services/bec_queue/bec_queue.py +13 -11
  137. bec_widgets/widgets/services/bec_status_box/bec_status_box.py +3 -4
  138. bec_widgets/widgets/services/device_browser/device_browser.py +5 -2
  139. bec_widgets/widgets/services/device_browser/device_item/device_item.py +1 -1
  140. bec_widgets/widgets/utility/logpanel/logpanel.py +36 -17
  141. bec_widgets/widgets/utility/spinbox/decimal_spinbox.py +3 -3
  142. bec_widgets/widgets/utility/visual/color_button/color_button.py +1 -1
  143. bec_widgets/widgets/utility/visual/colormap_widget/colormap_widget.py +4 -6
  144. bec_widgets/widgets/utility/visual/dark_mode_button/dark_mode_button.py +4 -8
  145. {bec_widgets-1.25.1.dist-info → bec_widgets-2.0.0.dist-info}/METADATA +3 -3
  146. {bec_widgets-1.25.1.dist-info → bec_widgets-2.0.0.dist-info}/RECORD +168 -153
  147. pyproject.toml +3 -3
  148. bec_widgets/applications/alignment/alignment_1d/alignment_1d.py +0 -198
  149. bec_widgets/applications/alignment/alignment_1d/alignment_1d.ui +0 -615
  150. bec_widgets/applications/bec_app.py +0 -84
  151. bec_widgets/cli/auto_updates.py +0 -168
  152. bec_widgets/widgets/containers/figure/__init__.py +0 -1
  153. bec_widgets/widgets/containers/figure/figure.py +0 -796
  154. bec_widgets/widgets/containers/figure/plots/axis_settings.py +0 -91
  155. bec_widgets/widgets/containers/figure/plots/axis_settings.ui +0 -256
  156. bec_widgets/widgets/containers/figure/plots/image/image.py +0 -772
  157. bec_widgets/widgets/containers/figure/plots/image/image_item.py +0 -337
  158. bec_widgets/widgets/containers/figure/plots/motor_map/motor_map.py +0 -525
  159. bec_widgets/widgets/containers/figure/plots/multi_waveform/multi_waveform.py +0 -340
  160. bec_widgets/widgets/containers/figure/plots/plot_base.py +0 -505
  161. bec_widgets/widgets/containers/figure/plots/waveform/waveform.py +0 -1563
  162. bec_widgets/widgets/plots/image/bec_image_widget.pyproject +0 -1
  163. bec_widgets/widgets/plots/image/image_widget.py +0 -515
  164. bec_widgets/widgets/plots/motor_map/bec_motor_map_widget.pyproject +0 -1
  165. bec_widgets/widgets/plots/motor_map/motor_map_dialog/motor_map_settings.py +0 -56
  166. bec_widgets/widgets/plots/motor_map/motor_map_dialog/motor_map_settings.ui +0 -108
  167. bec_widgets/widgets/plots/motor_map/motor_map_widget.py +0 -234
  168. bec_widgets/widgets/plots/multi_waveform/bec_multi_waveform_widget.pyproject +0 -1
  169. bec_widgets/widgets/plots/multi_waveform/multi_waveform_controls.ui +0 -99
  170. bec_widgets/widgets/plots/multi_waveform/multi_waveform_widget.py +0 -536
  171. bec_widgets/widgets/plots/waveform/bec_waveform_widget.pyproject +0 -1
  172. bec_widgets/widgets/plots/waveform/waveform_popups/curve_dialog/curve_dialog.py +0 -336
  173. bec_widgets/widgets/plots/waveform/waveform_popups/curve_dialog/curve_dialog.ui +0 -372
  174. bec_widgets/widgets/plots/waveform/waveform_popups/dap_summary_dialog/dap_summary_dialog.py +0 -25
  175. bec_widgets/widgets/plots/waveform/waveform_widget.py +0 -751
  176. /bec_widgets/{qt_utils → utils}/collapsible_panel_manager.py +0 -0
  177. /bec_widgets/{applications/alignment → utils/forms_from_types}/__init__.py +0 -0
  178. /bec_widgets/{qt_utils → utils}/redis_message_waiter.py +0 -0
  179. /bec_widgets/{applications/alignment/alignment_1d → widgets/containers/auto_update}/__init__.py +0 -0
  180. /bec_widgets/{qt_utils → widgets/containers/main_window/addons}/__init__.py +0 -0
  181. /bec_widgets/widgets/{containers/figure/plots → plots/image/toolbar_bundles}/__init__.py +0 -0
  182. /bec_widgets/widgets/{containers/figure/plots/image → plots/motor_map/settings}/__init__.py +0 -0
  183. /bec_widgets/widgets/{containers/figure/plots/motor_map → plots/motor_map/toolbar_bundles}/__init__.py +0 -0
  184. /bec_widgets/widgets/{containers/figure/plots/multi_waveform → plots/multi_waveform/settings}/__init__.py +0 -0
  185. /bec_widgets/widgets/{containers/figure/plots/waveform → plots/multi_waveform/toolbar_bundles}/__init__.py +0 -0
  186. /bec_widgets/widgets/plots/{motor_map/motor_map_dialog → scatter_waveform}/__init__.py +0 -0
  187. /bec_widgets/widgets/plots/{waveform/waveform_popups → scatter_waveform/settings}/__init__.py +0 -0
  188. /bec_widgets/widgets/plots/{waveform/waveform_popups/curve_dialog → setting_menus}/__init__.py +0 -0
  189. /bec_widgets/widgets/{plots_next_gen → plots}/setting_menus/axis_settings_horizontal.ui +0 -0
  190. /bec_widgets/widgets/{plots_next_gen → plots}/setting_menus/axis_settings_vertical.ui +0 -0
  191. /bec_widgets/widgets/plots/{waveform/waveform_popups/dap_summary_dialog → toolbar_bundles}/__init__.py +0 -0
  192. /bec_widgets/widgets/{plots_next_gen/setting_menus → plots/waveform/settings}/__init__.py +0 -0
  193. /bec_widgets/widgets/{plots_next_gen/toolbar_bundles → plots/waveform/settings/curve_settings}/__init__.py +0 -0
  194. {bec_widgets-1.25.1.dist-info → bec_widgets-2.0.0.dist-info}/WHEEL +0 -0
  195. {bec_widgets-1.25.1.dist-info → bec_widgets-2.0.0.dist-info}/entry_points.txt +0 -0
  196. {bec_widgets-1.25.1.dist-info → bec_widgets-2.0.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,41 +1,189 @@
1
- from qtpy.QtWidgets import QApplication, QMainWindow
2
-
3
- from bec_widgets.utils import BECConnector
4
- from bec_widgets.widgets.containers.dock.dock_area import BECDockArea
5
-
6
-
7
- class BECMainWindow(QMainWindow, BECConnector):
8
- def __init__(self, *args, **kwargs):
9
- BECConnector.__init__(self, **kwargs)
10
- QMainWindow.__init__(self, *args, **kwargs)
11
-
12
- def _dump(self):
13
- """Return a dictionary with informations about the application state, for use in tests"""
14
- # TODO: ModularToolBar and something else leak top-level widgets (3 or 4 QMenu + 2 QWidget);
15
- # so, a filtering based on title is applied here, but the solution is to not have those widgets
16
- # as top-level (so for now, a window with no title does not appear in _dump() result)
17
-
18
- # NOTE: the main window itself is excluded, since we want to dump dock areas
19
- info = {
20
- tlw.gui_id: {
21
- "title": tlw.windowTitle(),
22
- "visible": tlw.isVisible(),
23
- "class": str(type(tlw)),
24
- }
25
- for tlw in QApplication.instance().topLevelWidgets()
26
- if tlw is not self and tlw.windowTitle()
27
- }
28
- # Add the main window dock area
29
- info[self.centralWidget().gui_id] = {
30
- "title": self.windowTitle(),
31
- "visible": self.isVisible(),
32
- "class": str(type(self.centralWidget())),
33
- }
34
- return info
35
-
36
- def new_dock_area(self, name):
37
- dock_area = BECDockArea()
38
- dock_area.resize(dock_area.minimumSizeHint())
39
- dock_area.window().setWindowTitle(name)
40
- dock_area.show()
41
- return dock_area
1
+ import os
2
+
3
+ from qtpy.QtCore import QSize
4
+ from qtpy.QtGui import QAction, QActionGroup, QIcon
5
+ from qtpy.QtWidgets import QApplication, QMainWindow, QStyle
6
+
7
+ import bec_widgets
8
+ from bec_widgets.utils import UILoader
9
+ from bec_widgets.utils.bec_widget import BECWidget
10
+ from bec_widgets.utils.colors import apply_theme
11
+ from bec_widgets.utils.container_utils import WidgetContainerUtils
12
+ from bec_widgets.utils.error_popups import SafeSlot
13
+ from bec_widgets.utils.widget_io import WidgetHierarchy
14
+ from bec_widgets.widgets.containers.main_window.addons.web_links import BECWebLinksMixin
15
+
16
+ MODULE_PATH = os.path.dirname(bec_widgets.__file__)
17
+
18
+
19
+ class BECMainWindow(BECWidget, QMainWindow):
20
+ RPC = False
21
+ PLUGIN = False
22
+
23
+ def __init__(
24
+ self,
25
+ parent=None,
26
+ gui_id: str = None,
27
+ client=None,
28
+ window_title: str = "BEC",
29
+ *args,
30
+ **kwargs,
31
+ ):
32
+ super().__init__(parent=parent, gui_id=gui_id, **kwargs)
33
+
34
+ self.app = QApplication.instance()
35
+ self.setWindowTitle(window_title)
36
+ self._init_ui()
37
+ self._connect_to_theme_change()
38
+
39
+ def _init_ui(self):
40
+
41
+ # Set the icon
42
+ self._init_bec_icon()
43
+
44
+ # Set Menu and Status bar
45
+ self._setup_menu_bar()
46
+
47
+ # BEC Specific UI
48
+ self.display_app_id()
49
+
50
+ def _init_bec_icon(self):
51
+ icon = self.app.windowIcon()
52
+ if icon.isNull():
53
+ print("No icon is set, setting default icon")
54
+ icon = QIcon()
55
+ icon.addFile(
56
+ os.path.join(MODULE_PATH, "assets", "app_icons", "bec_widgets_icon.png"),
57
+ size=QSize(48, 48),
58
+ )
59
+ self.app.setWindowIcon(icon)
60
+ else:
61
+ print("An icon is set")
62
+
63
+ def load_ui(self, ui_file):
64
+ loader = UILoader(self)
65
+ self.ui = loader.loader(ui_file)
66
+ self.setCentralWidget(self.ui)
67
+
68
+ def display_app_id(self):
69
+ """
70
+ Display the app ID in the status bar.
71
+ """
72
+ if self.bec_dispatcher.cli_server is None:
73
+ status_message = "Not connected"
74
+ else:
75
+ # Get the server ID from the dispatcher
76
+ server_id = self.bec_dispatcher.cli_server.gui_id
77
+ status_message = f"App ID: {server_id}"
78
+ self.statusBar().showMessage(status_message)
79
+
80
+ def _fetch_theme(self) -> str:
81
+ return self.app.theme.theme
82
+
83
+ def _get_launcher_from_qapp(self):
84
+ """
85
+ Get the launcher from the QApplication instance.
86
+ """
87
+ from bec_widgets.applications.launch_window import LaunchWindow
88
+
89
+ qapp = QApplication.instance()
90
+ widgets = qapp.topLevelWidgets()
91
+ widgets = [w for w in widgets if isinstance(w, LaunchWindow)]
92
+ if widgets:
93
+ return widgets[0]
94
+ return None
95
+
96
+ def _show_launcher(self):
97
+ """
98
+ Show the launcher if it exists.
99
+ """
100
+ launcher = self._get_launcher_from_qapp()
101
+ if launcher:
102
+ launcher.show()
103
+ launcher.activateWindow()
104
+ launcher.raise_()
105
+
106
+ def _setup_menu_bar(self):
107
+ """
108
+ Setup the menu bar for the main window.
109
+ """
110
+ menu_bar = self.menuBar()
111
+
112
+ ##########################################
113
+ # Launch menu
114
+ launch_menu = menu_bar.addMenu("New")
115
+
116
+ open_launcher_action = QAction("Open Launcher", self)
117
+ launch_menu.addAction(open_launcher_action)
118
+ open_launcher_action.triggered.connect(self._show_launcher)
119
+
120
+ ########################################
121
+ # Theme menu
122
+ theme_menu = menu_bar.addMenu("Theme")
123
+
124
+ theme_group = QActionGroup(self)
125
+ light_theme_action = QAction("Light Theme", self, checkable=True)
126
+ dark_theme_action = QAction("Dark Theme", self, checkable=True)
127
+ theme_group.addAction(light_theme_action)
128
+ theme_group.addAction(dark_theme_action)
129
+ theme_group.setExclusive(True)
130
+
131
+ theme_menu.addAction(light_theme_action)
132
+ theme_menu.addAction(dark_theme_action)
133
+
134
+ # Connect theme actions
135
+ light_theme_action.triggered.connect(lambda: self.change_theme("light"))
136
+ dark_theme_action.triggered.connect(lambda: self.change_theme("dark"))
137
+
138
+ # Set the default theme
139
+ theme = self.app.theme.theme
140
+ if theme == "light":
141
+ light_theme_action.setChecked(True)
142
+ elif theme == "dark":
143
+ dark_theme_action.setChecked(True)
144
+
145
+ ########################################
146
+ # Help menu
147
+ help_menu = menu_bar.addMenu("Help")
148
+
149
+ help_icon = QApplication.style().standardIcon(QStyle.SP_MessageBoxQuestion)
150
+ bug_icon = QApplication.style().standardIcon(QStyle.SP_MessageBoxInformation)
151
+
152
+ bec_docs = QAction("BEC Docs", self)
153
+ bec_docs.setIcon(help_icon)
154
+ widgets_docs = QAction("BEC Widgets Docs", self)
155
+ widgets_docs.setIcon(help_icon)
156
+ bug_report = QAction("Bug Report", self)
157
+ bug_report.setIcon(bug_icon)
158
+
159
+ bec_docs.triggered.connect(BECWebLinksMixin.open_bec_docs)
160
+ widgets_docs.triggered.connect(BECWebLinksMixin.open_bec_widgets_docs)
161
+ bug_report.triggered.connect(BECWebLinksMixin.open_bec_bug_report)
162
+
163
+ help_menu.addAction(bec_docs)
164
+ help_menu.addAction(widgets_docs)
165
+ help_menu.addAction(bug_report)
166
+
167
+ @SafeSlot(str)
168
+ def change_theme(self, theme: str):
169
+ apply_theme(theme)
170
+
171
+ def cleanup(self):
172
+ central_widget = self.centralWidget()
173
+ central_widget.close()
174
+ central_widget.deleteLater()
175
+ if not isinstance(central_widget, BECWidget):
176
+ # if the central widget is not a BECWidget, we need to call the cleanup method
177
+ # of all widgets whose parent is the current BECMainWindow
178
+ children = self.findChildren(BECWidget)
179
+ for child in children:
180
+ ancestor = WidgetHierarchy._get_becwidget_ancestor(child)
181
+ if ancestor is self:
182
+ child.cleanup()
183
+ child.close()
184
+ child.deleteLater()
185
+ super().cleanup()
186
+
187
+
188
+ class UILaunchWindow(BECMainWindow):
189
+ RPC = True
@@ -2,8 +2,8 @@ from bec_qthemes import material_icon
2
2
  from qtpy.QtCore import Qt
3
3
  from qtpy.QtWidgets import QHBoxLayout, QPushButton, QToolButton, QWidget
4
4
 
5
- from bec_widgets.qt_utils.error_popups import SafeSlot
6
5
  from bec_widgets.utils.bec_widget import BECWidget
6
+ from bec_widgets.utils.error_popups import SafeSlot
7
7
 
8
8
 
9
9
  class AbortButton(BECWidget, QWidget):
@@ -11,6 +11,7 @@ class AbortButton(BECWidget, QWidget):
11
11
 
12
12
  PLUGIN = True
13
13
  ICON_NAME = "cancel"
14
+ RPC = True
14
15
 
15
16
  def __init__(
16
17
  self,
@@ -22,9 +23,7 @@ class AbortButton(BECWidget, QWidget):
22
23
  scan_id=None,
23
24
  **kwargs,
24
25
  ):
25
- super().__init__(client=client, config=config, gui_id=gui_id, **kwargs)
26
- QWidget.__init__(self, parent=parent)
27
-
26
+ super().__init__(parent=parent, client=client, gui_id=gui_id, config=config, **kwargs)
28
27
  self.get_bec_shortcuts()
29
28
 
30
29
  self.layout = QHBoxLayout(self)
@@ -2,8 +2,8 @@ from bec_qthemes import material_icon
2
2
  from qtpy.QtCore import Qt
3
3
  from qtpy.QtWidgets import QHBoxLayout, QMessageBox, QPushButton, QToolButton, QWidget
4
4
 
5
- from bec_widgets.qt_utils.error_popups import SafeSlot
6
5
  from bec_widgets.utils.bec_widget import BECWidget
6
+ from bec_widgets.utils.error_popups import SafeSlot
7
7
 
8
8
 
9
9
  class ResetButton(BECWidget, QWidget):
@@ -11,11 +11,10 @@ class ResetButton(BECWidget, QWidget):
11
11
 
12
12
  PLUGIN = True
13
13
  ICON_NAME = "restart_alt"
14
+ RPC = True
14
15
 
15
16
  def __init__(self, parent=None, client=None, config=None, gui_id=None, toolbar=False, **kwargs):
16
- super().__init__(client=client, config=config, gui_id=gui_id, **kwargs)
17
- QWidget.__init__(self, parent=parent)
18
-
17
+ super().__init__(parent=parent, client=client, gui_id=gui_id, config=config, **kwargs)
19
18
  self.get_bec_shortcuts()
20
19
 
21
20
  self.layout = QHBoxLayout(self)
@@ -2,8 +2,8 @@ from bec_qthemes import material_icon
2
2
  from qtpy.QtCore import Qt
3
3
  from qtpy.QtWidgets import QHBoxLayout, QPushButton, QToolButton, QWidget
4
4
 
5
- from bec_widgets.qt_utils.error_popups import SafeSlot
6
5
  from bec_widgets.utils.bec_widget import BECWidget
6
+ from bec_widgets.utils.error_popups import SafeSlot
7
7
 
8
8
 
9
9
  class ResumeButton(BECWidget, QWidget):
@@ -11,10 +11,10 @@ class ResumeButton(BECWidget, QWidget):
11
11
 
12
12
  PLUGIN = True
13
13
  ICON_NAME = "resume"
14
+ RPC = True
14
15
 
15
16
  def __init__(self, parent=None, client=None, config=None, gui_id=None, toolbar=False, **kwargs):
16
- super().__init__(client=client, config=config, gui_id=gui_id, **kwargs)
17
- QWidget.__init__(self, parent=parent)
17
+ super().__init__(parent=parent, client=client, gui_id=gui_id, config=config, **kwargs)
18
18
 
19
19
  self.get_bec_shortcuts()
20
20
 
@@ -2,8 +2,8 @@ from bec_qthemes import material_icon
2
2
  from qtpy.QtCore import Qt
3
3
  from qtpy.QtWidgets import QHBoxLayout, QPushButton, QSizePolicy, QToolButton, QWidget
4
4
 
5
- from bec_widgets.qt_utils.error_popups import SafeSlot
6
5
  from bec_widgets.utils.bec_widget import BECWidget
6
+ from bec_widgets.utils.error_popups import SafeSlot
7
7
 
8
8
 
9
9
  class StopButton(BECWidget, QWidget):
@@ -11,10 +11,10 @@ class StopButton(BECWidget, QWidget):
11
11
 
12
12
  PLUGIN = True
13
13
  ICON_NAME = "dangerous"
14
+ RPC = True
14
15
 
15
16
  def __init__(self, parent=None, client=None, config=None, gui_id=None, toolbar=False, **kwargs):
16
- super().__init__(client=client, config=config, gui_id=gui_id, **kwargs)
17
- QWidget.__init__(self, parent=parent)
17
+ super().__init__(parent=parent, client=client, gui_id=gui_id, config=config, **kwargs)
18
18
 
19
19
  self.get_bec_shortcuts()
20
20
 
@@ -54,9 +54,20 @@ class StopButton(BECWidget, QWidget):
54
54
  if __name__ == "__main__": # pragma: no cover
55
55
  import sys
56
56
 
57
- from qtpy.QtWidgets import QApplication
57
+ from qtpy.QtWidgets import QApplication, QVBoxLayout, QWidget
58
58
 
59
- app = QApplication(sys.argv)
60
- w = StopButton()
61
- w.show()
59
+ from bec_widgets.widgets.control.buttons.stop_button.stop_button import StopButton
60
+
61
+ class MyGui(QWidget):
62
+ def __init__(self):
63
+ super().__init__()
64
+ self.setLayout(QVBoxLayout())
65
+ # Create and add the StopButton to the layout
66
+ self.stop_button = StopButton()
67
+ self.layout().addWidget(self.stop_button)
68
+
69
+ # Example of how this custom GUI might be used:
70
+ app = QApplication([])
71
+ my_gui = MyGui()
72
+ my_gui.show()
62
73
  sys.exit(app.exec_())
@@ -8,13 +8,26 @@ from bec_widgets.utils.colors import get_accent_colors, get_theme_palette
8
8
 
9
9
 
10
10
  class PositionIndicator(BECWidget, QWidget):
11
- USER_ACCESS = ["set_value", "set_range", "vertical", "indicator_width", "rounded_corners"]
11
+ """
12
+ Display a position within a defined range, e.g. motor limits.
13
+ """
14
+
15
+ USER_ACCESS = [
16
+ "set_value",
17
+ "set_range",
18
+ "vertical",
19
+ "vertical.setter",
20
+ "indicator_width",
21
+ "indicator_width.setter",
22
+ "rounded_corners",
23
+ "rounded_corners.setter",
24
+ ]
12
25
  PLUGIN = True
13
26
  ICON_NAME = "horizontal_distribute"
14
27
 
15
28
  def __init__(self, parent=None, client=None, config=None, gui_id=None, **kwargs):
16
- super().__init__(client=client, config=config, gui_id=gui_id, **kwargs)
17
- QWidget.__init__(self, parent=parent)
29
+ super().__init__(parent=parent, client=client, gui_id=gui_id, config=config, **kwargs)
30
+
18
31
  self.position = 50
19
32
  self.min_value = 0
20
33
  self.max_value = 100
@@ -205,6 +218,12 @@ class PositionIndicator(BECWidget, QWidget):
205
218
  @Slot(int)
206
219
  @Slot(float)
207
220
  def set_value(self, position: float):
221
+ """
222
+ Set the position of the indicator
223
+
224
+ Args:
225
+ position: The new position of the indicator
226
+ """
208
227
  self.position = position
209
228
  self.update()
210
229
 
@@ -16,8 +16,8 @@ from qtpy.QtWidgets import (
16
16
  QVBoxLayout,
17
17
  )
18
18
 
19
- from bec_widgets.qt_utils.compact_popup import CompactPopupWidget
20
19
  from bec_widgets.utils.bec_widget import BECWidget
20
+ from bec_widgets.utils.compact_popup import CompactPopupWidget
21
21
  from bec_widgets.widgets.control.device_control.position_indicator.position_indicator import (
22
22
  PositionIndicator,
23
23
  )
@@ -40,6 +40,7 @@ class DeviceUpdateUIComponents(TypedDict):
40
40
  stop: QPushButton
41
41
  tweak_increase: QPushButton
42
42
  tweak_decrease: QPushButton
43
+ units: QLabel
43
44
 
44
45
 
45
46
  class PositionerBoxBase(BECWidget, CompactPopupWidget):
@@ -47,6 +48,7 @@ class PositionerBoxBase(BECWidget, CompactPopupWidget):
47
48
 
48
49
  current_path = ""
49
50
  ICON_NAME = "switch_right"
51
+ RPC = False
50
52
 
51
53
  def __init__(self, parent=None, **kwargs):
52
54
  """Initialize the PositionerBox widget.
@@ -55,8 +57,7 @@ class PositionerBoxBase(BECWidget, CompactPopupWidget):
55
57
  parent: The parent widget.
56
58
  device (Positioner): The device to control.
57
59
  """
58
- super().__init__(**kwargs)
59
- CompactPopupWidget.__init__(self, parent=parent, layout=QVBoxLayout)
60
+ super().__init__(parent=parent, layout=QVBoxLayout, **kwargs)
60
61
  self._dialog = None
61
62
  self.get_bec_shortcuts()
62
63
 
@@ -84,16 +85,33 @@ class PositionerBoxBase(BECWidget, CompactPopupWidget):
84
85
  limit_update: Callable[[tuple[float, float]], None],
85
86
  ):
86
87
  """Init the device view and readback"""
87
- if self._check_device_is_valid(device):
88
- data = self.dev[device].read()
89
- self._on_device_readback(
90
- device,
91
- self._device_ui_components(device),
92
- {"signals": data},
93
- {},
94
- position_emit,
95
- limit_update,
96
- )
88
+ if not self._check_device_is_valid(device):
89
+ return
90
+
91
+ data = self.dev[device].read()
92
+ self._on_device_readback(
93
+ device,
94
+ self._device_ui_components(device),
95
+ {"signals": data},
96
+ {},
97
+ position_emit,
98
+ limit_update,
99
+ )
100
+
101
+ ui = self._device_ui_components(device)
102
+ if not ui.get("units"):
103
+ return
104
+
105
+ try:
106
+ egu = f"[{self.dev[device].egu()}]"
107
+ except Exception:
108
+ egu = ""
109
+
110
+ if egu:
111
+ ui["units"].setVisible(True)
112
+ ui["units"].setText(egu)
113
+ else:
114
+ ui["units"].setVisible(False)
97
115
 
98
116
  def _stop_device(self, device: str):
99
117
  """Stop call"""
@@ -11,9 +11,9 @@ from qtpy.QtCore import Signal
11
11
  from qtpy.QtGui import QDoubleValidator
12
12
  from qtpy.QtWidgets import QDoubleSpinBox
13
13
 
14
- from bec_widgets.qt_utils.error_popups import SafeProperty, SafeSlot
15
14
  from bec_widgets.utils import UILoader
16
15
  from bec_widgets.utils.colors import get_accent_colors, set_theme
16
+ from bec_widgets.utils.error_popups import SafeProperty, SafeSlot
17
17
  from bec_widgets.widgets.control.device_control.positioner_box._base import PositionerBoxBase
18
18
  from bec_widgets.widgets.control.device_control.positioner_box._base.positioner_box_base import (
19
19
  DeviceUpdateUIComponents,
@@ -31,6 +31,7 @@ class PositionerBox(PositionerBoxBase):
31
31
  dimensions = (234, 224)
32
32
 
33
33
  PLUGIN = True
34
+ RPC = True
34
35
 
35
36
  USER_ACCESS = ["set_positioner"]
36
37
  device_changed = Signal(str, str)
@@ -170,6 +171,7 @@ class PositionerBox(PositionerBoxBase):
170
171
  "stop": self.ui.stop,
171
172
  "tweak_increase": self.ui.tweak_right,
172
173
  "tweak_decrease": self.ui.tweak_left,
174
+ "units": self.ui.units,
173
175
  }
174
176
 
175
177
  @SafeSlot(dict, dict)
@@ -135,6 +135,29 @@
135
135
  </property>
136
136
  </widget>
137
137
  </item>
138
+ <item>
139
+ <spacer name="horizontalSpacer_2">
140
+ <property name="orientation">
141
+ <enum>Qt::Orientation::Horizontal</enum>
142
+ </property>
143
+ <property name="sizeHint" stdset="0">
144
+ <size>
145
+ <width>40</width>
146
+ <height>20</height>
147
+ </size>
148
+ </property>
149
+ </spacer>
150
+ </item>
151
+ <item>
152
+ <widget class="QLabel" name="units">
153
+ <property name="toolTip">
154
+ <string>Motor units</string>
155
+ </property>
156
+ <property name="text">
157
+ <string></string>
158
+ </property>
159
+ </widget>
160
+ </item>
138
161
  <item>
139
162
  <spacer name="horizontalSpacer">
140
163
  <property name="orientation">
@@ -204,14 +227,14 @@
204
227
  </widget>
205
228
  <customwidgets>
206
229
  <customwidget>
207
- <class>SpinnerWidget</class>
230
+ <class>PositionIndicator</class>
208
231
  <extends>QWidget</extends>
209
- <header>spinner_widget</header>
232
+ <header>position_indicator</header>
210
233
  </customwidget>
211
234
  <customwidget>
212
- <class>PositionIndicator</class>
235
+ <class>SpinnerWidget</class>
213
236
  <extends>QWidget</extends>
214
- <header>position_indicator</header>
237
+ <header>spinner_widget</header>
215
238
  </customwidget>
216
239
  </customwidgets>
217
240
  <resources/>
@@ -1,4 +1,4 @@
1
- """ Module for a PositionerBox2D widget to control two positioner devices."""
1
+ """Module for a PositionerBox2D widget to control two positioner devices."""
2
2
 
3
3
  from __future__ import annotations
4
4
 
@@ -12,9 +12,9 @@ from qtpy.QtCore import Signal
12
12
  from qtpy.QtGui import QDoubleValidator
13
13
  from qtpy.QtWidgets import QDoubleSpinBox
14
14
 
15
- from bec_widgets.qt_utils.error_popups import SafeProperty, SafeSlot
16
15
  from bec_widgets.utils import UILoader
17
16
  from bec_widgets.utils.colors import set_theme
17
+ from bec_widgets.utils.error_popups import SafeProperty, SafeSlot
18
18
  from bec_widgets.widgets.control.device_control.positioner_box._base import PositionerBoxBase
19
19
  from bec_widgets.widgets.control.device_control.positioner_box._base.positioner_box_base import (
20
20
  DeviceUpdateUIComponents,
@@ -33,6 +33,7 @@ class PositionerBox2D(PositionerBoxBase):
33
33
  ui_file = "positioner_box_2d.ui"
34
34
 
35
35
  PLUGIN = True
36
+ RPC = True
36
37
  USER_ACCESS = ["set_positioner_hor", "set_positioner_ver"]
37
38
 
38
39
  device_changed_hor = Signal(str, str)
@@ -312,6 +313,7 @@ class PositionerBox2D(PositionerBoxBase):
312
313
  "stop": self.ui.stop_button,
313
314
  "tweak_increase": self.ui.tweak_increase_hor,
314
315
  "tweak_decrease": self.ui.tweak_decrease_hor,
316
+ "units": self.ui.units_hor,
315
317
  }
316
318
  elif device == "vertical":
317
319
  return {
@@ -324,6 +326,7 @@ class PositionerBox2D(PositionerBoxBase):
324
326
  "stop": self.ui.stop_button,
325
327
  "tweak_increase": self.ui.tweak_increase_ver,
326
328
  "tweak_decrease": self.ui.tweak_decrease_ver,
329
+ "units": self.ui.units_ver,
327
330
  }
328
331
  else:
329
332
  raise ValueError(f"Device {device} is not represented by this UI")