bec-widgets 1.25.0__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 (197) hide show
  1. .gitlab-ci.yml +11 -6
  2. CHANGELOG.md +650 -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 +37 -18
  60. bec_widgets/widgets/control/device_control/positioner_box/positioner_box/positioner_box.py +28 -4
  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/spinner/spinner.py +2 -2
  143. bec_widgets/widgets/utility/visual/color_button/color_button.py +1 -1
  144. bec_widgets/widgets/utility/visual/colormap_widget/colormap_widget.py +4 -6
  145. bec_widgets/widgets/utility/visual/dark_mode_button/dark_mode_button.py +4 -8
  146. {bec_widgets-1.25.0.dist-info → bec_widgets-2.0.0.dist-info}/METADATA +3 -3
  147. {bec_widgets-1.25.0.dist-info → bec_widgets-2.0.0.dist-info}/RECORD +169 -154
  148. pyproject.toml +3 -3
  149. bec_widgets/applications/alignment/alignment_1d/alignment_1d.py +0 -198
  150. bec_widgets/applications/alignment/alignment_1d/alignment_1d.ui +0 -615
  151. bec_widgets/applications/bec_app.py +0 -84
  152. bec_widgets/cli/auto_updates.py +0 -168
  153. bec_widgets/widgets/containers/figure/__init__.py +0 -1
  154. bec_widgets/widgets/containers/figure/figure.py +0 -796
  155. bec_widgets/widgets/containers/figure/plots/axis_settings.py +0 -91
  156. bec_widgets/widgets/containers/figure/plots/axis_settings.ui +0 -256
  157. bec_widgets/widgets/containers/figure/plots/image/image.py +0 -772
  158. bec_widgets/widgets/containers/figure/plots/image/image_item.py +0 -337
  159. bec_widgets/widgets/containers/figure/plots/motor_map/motor_map.py +0 -525
  160. bec_widgets/widgets/containers/figure/plots/multi_waveform/multi_waveform.py +0 -340
  161. bec_widgets/widgets/containers/figure/plots/plot_base.py +0 -505
  162. bec_widgets/widgets/containers/figure/plots/waveform/waveform.py +0 -1563
  163. bec_widgets/widgets/plots/image/bec_image_widget.pyproject +0 -1
  164. bec_widgets/widgets/plots/image/image_widget.py +0 -515
  165. bec_widgets/widgets/plots/motor_map/bec_motor_map_widget.pyproject +0 -1
  166. bec_widgets/widgets/plots/motor_map/motor_map_dialog/motor_map_settings.py +0 -56
  167. bec_widgets/widgets/plots/motor_map/motor_map_dialog/motor_map_settings.ui +0 -108
  168. bec_widgets/widgets/plots/motor_map/motor_map_widget.py +0 -234
  169. bec_widgets/widgets/plots/multi_waveform/bec_multi_waveform_widget.pyproject +0 -1
  170. bec_widgets/widgets/plots/multi_waveform/multi_waveform_controls.ui +0 -99
  171. bec_widgets/widgets/plots/multi_waveform/multi_waveform_widget.py +0 -536
  172. bec_widgets/widgets/plots/waveform/bec_waveform_widget.pyproject +0 -1
  173. bec_widgets/widgets/plots/waveform/waveform_popups/curve_dialog/curve_dialog.py +0 -336
  174. bec_widgets/widgets/plots/waveform/waveform_popups/curve_dialog/curve_dialog.ui +0 -372
  175. bec_widgets/widgets/plots/waveform/waveform_popups/dap_summary_dialog/dap_summary_dialog.py +0 -25
  176. bec_widgets/widgets/plots/waveform/waveform_widget.py +0 -751
  177. /bec_widgets/{qt_utils → utils}/collapsible_panel_manager.py +0 -0
  178. /bec_widgets/{applications/alignment → utils/forms_from_types}/__init__.py +0 -0
  179. /bec_widgets/{qt_utils → utils}/redis_message_waiter.py +0 -0
  180. /bec_widgets/{applications/alignment/alignment_1d → widgets/containers/auto_update}/__init__.py +0 -0
  181. /bec_widgets/{qt_utils → widgets/containers/main_window/addons}/__init__.py +0 -0
  182. /bec_widgets/widgets/{containers/figure/plots → plots/image/toolbar_bundles}/__init__.py +0 -0
  183. /bec_widgets/widgets/{containers/figure/plots/image → plots/motor_map/settings}/__init__.py +0 -0
  184. /bec_widgets/widgets/{containers/figure/plots/motor_map → plots/motor_map/toolbar_bundles}/__init__.py +0 -0
  185. /bec_widgets/widgets/{containers/figure/plots/multi_waveform → plots/multi_waveform/settings}/__init__.py +0 -0
  186. /bec_widgets/widgets/{containers/figure/plots/waveform → plots/multi_waveform/toolbar_bundles}/__init__.py +0 -0
  187. /bec_widgets/widgets/plots/{motor_map/motor_map_dialog → scatter_waveform}/__init__.py +0 -0
  188. /bec_widgets/widgets/plots/{waveform/waveform_popups → scatter_waveform/settings}/__init__.py +0 -0
  189. /bec_widgets/widgets/plots/{waveform/waveform_popups/curve_dialog → setting_menus}/__init__.py +0 -0
  190. /bec_widgets/widgets/{plots_next_gen → plots}/setting_menus/axis_settings_horizontal.ui +0 -0
  191. /bec_widgets/widgets/{plots_next_gen → plots}/setting_menus/axis_settings_vertical.ui +0 -0
  192. /bec_widgets/widgets/plots/{waveform/waveform_popups/dap_summary_dialog → toolbar_bundles}/__init__.py +0 -0
  193. /bec_widgets/widgets/{plots_next_gen/setting_menus → plots/waveform/settings}/__init__.py +0 -0
  194. /bec_widgets/widgets/{plots_next_gen/toolbar_bundles → plots/waveform/settings/curve_settings}/__init__.py +0 -0
  195. {bec_widgets-1.25.0.dist-info → bec_widgets-2.0.0.dist-info}/WHEEL +0 -0
  196. {bec_widgets-1.25.0.dist-info → bec_widgets-2.0.0.dist-info}/entry_points.txt +0 -0
  197. {bec_widgets-1.25.0.dist-info → bec_widgets-2.0.0.dist-info}/licenses/LICENSE +0 -0
@@ -3,36 +3,43 @@ from __future__ import annotations
3
3
  from typing import Literal, Optional
4
4
  from weakref import WeakValueDictionary
5
5
 
6
- from bec_lib.endpoints import MessageEndpoints
6
+ from bec_lib.logger import bec_logger
7
7
  from pydantic import Field
8
8
  from pyqtgraph.dockarea.DockArea import DockArea
9
9
  from qtpy.QtCore import QSize, Qt
10
10
  from qtpy.QtGui import QPainter, QPaintEvent
11
11
  from qtpy.QtWidgets import QApplication, QSizePolicy, QVBoxLayout, QWidget
12
12
 
13
- from bec_widgets.qt_utils.error_popups import SafeSlot
14
- from bec_widgets.qt_utils.toolbar import (
13
+ from bec_widgets.cli.rpc.rpc_register import RPCRegister
14
+ from bec_widgets.utils import ConnectionConfig, WidgetContainerUtils
15
+ from bec_widgets.utils.bec_widget import BECWidget
16
+ from bec_widgets.utils.error_popups import SafeSlot
17
+ from bec_widgets.utils.name_utils import pascal_to_snake
18
+ from bec_widgets.utils.toolbar import (
15
19
  ExpandableMenuAction,
16
20
  MaterialIconAction,
17
21
  ModularToolBar,
18
22
  SeparatorAction,
19
23
  )
20
- from bec_widgets.utils import ConnectionConfig, WidgetContainerUtils
21
- from bec_widgets.utils.bec_widget import BECWidget
24
+ from bec_widgets.utils.widget_io import WidgetHierarchy
22
25
  from bec_widgets.widgets.containers.dock.dock import BECDock, DockConfig
26
+ from bec_widgets.widgets.containers.main_window.main_window import BECMainWindow
23
27
  from bec_widgets.widgets.control.device_control.positioner_box import PositionerBox
24
28
  from bec_widgets.widgets.control.scan_control.scan_control import ScanControl
25
29
  from bec_widgets.widgets.editors.vscode.vscode import VSCodeEditor
26
- from bec_widgets.widgets.plots.image.image_widget import BECImageWidget
27
- from bec_widgets.widgets.plots.motor_map.motor_map_widget import BECMotorMapWidget
28
- from bec_widgets.widgets.plots.multi_waveform.multi_waveform_widget import BECMultiWaveformWidget
29
- from bec_widgets.widgets.plots.waveform.waveform_widget import BECWaveformWidget
30
+ from bec_widgets.widgets.plots.image.image import Image
31
+ from bec_widgets.widgets.plots.motor_map.motor_map import MotorMap
32
+ from bec_widgets.widgets.plots.multi_waveform.multi_waveform import MultiWaveform
33
+ from bec_widgets.widgets.plots.scatter_waveform.scatter_waveform import ScatterWaveform
34
+ from bec_widgets.widgets.plots.waveform.waveform import Waveform
30
35
  from bec_widgets.widgets.progress.ring_progress_bar.ring_progress_bar import RingProgressBar
31
36
  from bec_widgets.widgets.services.bec_queue.bec_queue import BECQueue
32
37
  from bec_widgets.widgets.services.bec_status_box.bec_status_box import BECStatusBox
33
38
  from bec_widgets.widgets.utility.logpanel.logpanel import LogPanel
34
39
  from bec_widgets.widgets.utility.visual.dark_mode_button.dark_mode_button import DarkModeButton
35
40
 
41
+ logger = bec_logger.logger
42
+
36
43
 
37
44
  class DockAreaConfig(ConnectionConfig):
38
45
  docks: dict[str, DockConfig] = Field({}, description="The docks in the dock area.")
@@ -42,23 +49,27 @@ class DockAreaConfig(ConnectionConfig):
42
49
 
43
50
 
44
51
  class BECDockArea(BECWidget, QWidget):
52
+ """
53
+ Container for other widgets. Widgets can be added to the dock area and arranged in a grid layout.
54
+ """
55
+
45
56
  PLUGIN = True
46
57
  USER_ACCESS = [
58
+ "_rpc_id",
47
59
  "_config_dict",
48
- "selected_device",
49
- "panels",
50
- "save_state",
51
- "remove_dock",
52
- "restore_state",
53
- "add_dock",
54
- "clear_all",
55
- "detach_dock",
56
- "attach_all",
57
60
  "_get_all_rpc",
58
- "temp_areas",
61
+ "new",
59
62
  "show",
60
63
  "hide",
64
+ "panels",
65
+ "panel_list",
61
66
  "delete",
67
+ "delete_all",
68
+ "remove",
69
+ "detach_dock",
70
+ "attach_all",
71
+ "save_state",
72
+ "restore_state",
62
73
  ]
63
74
 
64
75
  def __init__(
@@ -67,6 +78,8 @@ class BECDockArea(BECWidget, QWidget):
67
78
  config: DockAreaConfig | None = None,
68
79
  client=None,
69
80
  gui_id: str = None,
81
+ object_name: str = None,
82
+ **kwargs,
70
83
  ) -> None:
71
84
  if config is None:
72
85
  config = DockAreaConfig(widget_class=self.__class__.__name__)
@@ -74,37 +87,47 @@ class BECDockArea(BECWidget, QWidget):
74
87
  if isinstance(config, dict):
75
88
  config = DockAreaConfig(**config)
76
89
  self.config = config
77
- super().__init__(client=client, config=config, gui_id=gui_id)
78
- QWidget.__init__(self, parent=parent)
90
+ super().__init__(
91
+ parent=parent,
92
+ object_name=object_name,
93
+ client=client,
94
+ gui_id=gui_id,
95
+ config=config,
96
+ **kwargs,
97
+ )
98
+ self._parent = parent # TODO probably not needed
79
99
  self.layout = QVBoxLayout(self)
80
100
  self.layout.setSpacing(5)
81
101
  self.layout.setContentsMargins(0, 0, 0, 0)
82
102
 
83
103
  self._instructions_visible = True
84
104
 
85
- self.dock_area = DockArea()
105
+ self.dark_mode_button = DarkModeButton(parent=self, toolbar=True)
106
+ self.dock_area = DockArea(parent=self)
86
107
  self.toolbar = ModularToolBar(
108
+ parent=self,
87
109
  actions={
88
110
  "menu_plots": ExpandableMenuAction(
89
111
  label="Add Plot ",
90
112
  actions={
91
113
  "waveform": MaterialIconAction(
92
- icon_name=BECWaveformWidget.ICON_NAME,
93
- tooltip="Add Waveform",
114
+ icon_name=Waveform.ICON_NAME, tooltip="Add Waveform", filled=True
115
+ ),
116
+ "scatter_waveform": MaterialIconAction(
117
+ icon_name=ScatterWaveform.ICON_NAME,
118
+ tooltip="Add Scatter Waveform",
94
119
  filled=True,
95
120
  ),
96
121
  "multi_waveform": MaterialIconAction(
97
- icon_name=BECMultiWaveformWidget.ICON_NAME,
122
+ icon_name=MultiWaveform.ICON_NAME,
98
123
  tooltip="Add Multi Waveform",
99
124
  filled=True,
100
125
  ),
101
126
  "image": MaterialIconAction(
102
- icon_name=BECImageWidget.ICON_NAME, tooltip="Add Image", filled=True
127
+ icon_name=Image.ICON_NAME, tooltip="Add Image", filled=True
103
128
  ),
104
129
  "motor_map": MaterialIconAction(
105
- icon_name=BECMotorMapWidget.ICON_NAME,
106
- tooltip="Add Motor Map",
107
- filled=True,
130
+ icon_name=MotorMap.ICON_NAME, tooltip="Add Motor Map", filled=True
108
131
  ),
109
132
  },
110
133
  ),
@@ -159,10 +182,10 @@ class BECDockArea(BECWidget, QWidget):
159
182
 
160
183
  self.layout.addWidget(self.toolbar)
161
184
  self.layout.addWidget(self.dock_area)
162
- self.spacer = QWidget()
185
+ self.spacer = QWidget(parent=self)
163
186
  self.spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
164
187
  self.toolbar.addWidget(self.spacer)
165
- self.toolbar.addWidget(DarkModeButton(toolbar=True))
188
+ self.toolbar.addWidget(self.dark_mode_button)
166
189
  self._hook_toolbar()
167
190
 
168
191
  def minimumSizeHint(self):
@@ -171,41 +194,44 @@ class BECDockArea(BECWidget, QWidget):
171
194
  def _hook_toolbar(self):
172
195
  # Menu Plot
173
196
  self.toolbar.widgets["menu_plots"].widgets["waveform"].triggered.connect(
174
- lambda: self.add_dock(widget="BECWaveformWidget", prefix="waveform")
197
+ lambda: self._create_widget_from_toolbar(widget_name="Waveform")
198
+ )
199
+ self.toolbar.widgets["menu_plots"].widgets["scatter_waveform"].triggered.connect(
200
+ lambda: self._create_widget_from_toolbar(widget_name="ScatterWaveform")
175
201
  )
176
202
  self.toolbar.widgets["menu_plots"].widgets["multi_waveform"].triggered.connect(
177
- lambda: self.add_dock(widget="BECMultiWaveformWidget", prefix="multi_waveform")
203
+ lambda: self._create_widget_from_toolbar(widget_name="MultiWaveform")
178
204
  )
179
205
  self.toolbar.widgets["menu_plots"].widgets["image"].triggered.connect(
180
- lambda: self.add_dock(widget="BECImageWidget", prefix="image")
206
+ lambda: self._create_widget_from_toolbar(widget_name="Image")
181
207
  )
182
208
  self.toolbar.widgets["menu_plots"].widgets["motor_map"].triggered.connect(
183
- lambda: self.add_dock(widget="BECMotorMapWidget", prefix="motor_map")
209
+ lambda: self._create_widget_from_toolbar(widget_name="MotorMap")
184
210
  )
185
211
 
186
212
  # Menu Devices
187
213
  self.toolbar.widgets["menu_devices"].widgets["scan_control"].triggered.connect(
188
- lambda: self.add_dock(widget="ScanControl", prefix="scan_control")
214
+ lambda: self._create_widget_from_toolbar(widget_name="ScanControl")
189
215
  )
190
216
  self.toolbar.widgets["menu_devices"].widgets["positioner_box"].triggered.connect(
191
- lambda: self.add_dock(widget="PositionerBox", prefix="positioner_box")
217
+ lambda: self._create_widget_from_toolbar(widget_name="PositionerBox")
192
218
  )
193
219
 
194
220
  # Menu Utils
195
221
  self.toolbar.widgets["menu_utils"].widgets["queue"].triggered.connect(
196
- lambda: self.add_dock(widget="BECQueue", prefix="queue")
222
+ lambda: self._create_widget_from_toolbar(widget_name="BECQueue")
197
223
  )
198
224
  self.toolbar.widgets["menu_utils"].widgets["status"].triggered.connect(
199
- lambda: self.add_dock(widget="BECStatusBox", prefix="status")
225
+ lambda: self._create_widget_from_toolbar(widget_name="BECStatusBox")
200
226
  )
201
227
  self.toolbar.widgets["menu_utils"].widgets["vs_code"].triggered.connect(
202
- lambda: self.add_dock(widget="VSCodeEditor", prefix="vs_code")
228
+ lambda: self._create_widget_from_toolbar(widget_name="VSCodeEditor")
203
229
  )
204
230
  self.toolbar.widgets["menu_utils"].widgets["progress_bar"].triggered.connect(
205
- lambda: self.add_dock(widget="RingProgressBar", prefix="progress_bar")
231
+ lambda: self._create_widget_from_toolbar(widget_name="RingProgressBar")
206
232
  )
207
233
  self.toolbar.widgets["menu_utils"].widgets["log_panel"].triggered.connect(
208
- lambda: self.add_dock(widget="LogPanel", prefix="log_panel")
234
+ lambda: self._create_widget_from_toolbar(widget_name="LogPanel")
209
235
  )
210
236
 
211
237
  # Icons
@@ -213,6 +239,14 @@ class BECDockArea(BECWidget, QWidget):
213
239
  self.toolbar.widgets["save_state"].action.triggered.connect(self.save_state)
214
240
  self.toolbar.widgets["restore_state"].action.triggered.connect(self.restore_state)
215
241
 
242
+ @SafeSlot()
243
+ def _create_widget_from_toolbar(self, widget_name: str) -> None:
244
+ # Run with RPC broadcast to namespace of all widgets
245
+ with RPCRegister.delayed_broadcast():
246
+ name = pascal_to_snake(widget_name)
247
+ dock_name = WidgetContainerUtils.generate_unique_name(name, self.panels.keys())
248
+ self.new(name=dock_name, widget=widget_name)
249
+
216
250
  def paintEvent(self, event: QPaintEvent): # TODO decide if we want any default instructions
217
251
  super().paintEvent(event)
218
252
  if self._instructions_visible:
@@ -220,20 +254,9 @@ class BECDockArea(BECWidget, QWidget):
220
254
  painter.drawText(
221
255
  self.rect(),
222
256
  Qt.AlignCenter,
223
- "Add docks using 'add_dock' method from CLI\n or \n Add widget docks using the toolbar",
257
+ "Add docks using 'new' method from CLI\n or \n Add widget docks using the toolbar",
224
258
  )
225
259
 
226
- @property
227
- def selected_device(self) -> str:
228
- gui_id = QApplication.instance().gui_id
229
- auto_update_config = self.client.connector.get(
230
- MessageEndpoints.gui_auto_update_config(gui_id)
231
- )
232
- try:
233
- return auto_update_config.selected_device
234
- except AttributeError:
235
- return None
236
-
237
260
  @property
238
261
  def panels(self) -> dict[str, BECDock]:
239
262
  """
@@ -245,7 +268,17 @@ class BECDockArea(BECWidget, QWidget):
245
268
 
246
269
  @panels.setter
247
270
  def panels(self, value: dict[str, BECDock]):
248
- self.dock_area.docks = WeakValueDictionary(value)
271
+ self.dock_area.docks = WeakValueDictionary(value) # This can not work can it?
272
+
273
+ @property
274
+ def panel_list(self) -> list[BECDock]:
275
+ """
276
+ Get the docks in the dock area.
277
+
278
+ Returns:
279
+ list: The docks in the dock area.
280
+ """
281
+ return list(self.dock_area.docks.values())
249
282
 
250
283
  @property
251
284
  def temp_areas(self) -> list:
@@ -289,36 +322,17 @@ class BECDockArea(BECWidget, QWidget):
289
322
  self.config.docks_state = last_state
290
323
  return last_state
291
324
 
292
- def remove_dock(self, name: str):
293
- """
294
- Remove a dock by name and ensure it is properly closed and cleaned up.
295
-
296
- Args:
297
- name(str): The name of the dock to remove.
298
- """
299
- dock = self.dock_area.docks.pop(name, None)
300
- self.config.docks.pop(name, None)
301
- if dock:
302
- dock.close()
303
- dock.deleteLater()
304
- if len(self.dock_area.docks) <= 1:
305
- for dock in self.dock_area.docks.values():
306
- dock.hide_title_bar()
307
-
308
- else:
309
- raise ValueError(f"Dock with name {name} does not exist.")
310
-
311
325
  @SafeSlot(popup_error=True)
312
- def add_dock(
326
+ def new(
313
327
  self,
314
- name: str = None,
315
- position: Literal["bottom", "top", "left", "right", "above", "below"] = None,
328
+ name: str | None = None,
329
+ widget: str | QWidget | None = None,
330
+ widget_name: str | None = None,
331
+ position: Literal["bottom", "top", "left", "right", "above", "below"] = "bottom",
316
332
  relative_to: BECDock | None = None,
317
333
  closable: bool = True,
318
334
  floating: bool = False,
319
- prefix: str = "dock",
320
- widget: str | QWidget | None = None,
321
- row: int = None,
335
+ row: int | None = None,
322
336
  col: int = 0,
323
337
  rowspan: int = 1,
324
338
  colspan: int = 1,
@@ -328,12 +342,11 @@ class BECDockArea(BECWidget, QWidget):
328
342
 
329
343
  Args:
330
344
  name(str): The name of the dock to be displayed and for further references. Has to be unique.
345
+ widget(str|QWidget|None): The widget to be added to the dock. While using RPC, only BEC RPC widgets from RPCWidgetHandler are allowed.
331
346
  position(Literal["bottom", "top", "left", "right", "above", "below"]): The position of the dock.
332
347
  relative_to(BECDock): The dock to which the new dock should be added relative to.
333
348
  closable(bool): Whether the dock is closable.
334
349
  floating(bool): Whether the dock is detached after creating.
335
- prefix(str): The prefix for the dock name if no name is provided.
336
- widget(str|QWidget|None): The widget to be added to the dock. While using RPC, only BEC RPC widgets from RPCWidgetHandler are allowed.
337
350
  row(int): The row of the added widget.
338
351
  col(int): The column of the added widget.
339
352
  rowspan(int): The rowspan of the added widget.
@@ -342,21 +355,33 @@ class BECDockArea(BECWidget, QWidget):
342
355
  Returns:
343
356
  BECDock: The created dock.
344
357
  """
345
- if name is None:
346
- name = WidgetContainerUtils.generate_unique_widget_id(
347
- container=self.dock_area.docks, prefix=prefix
348
- )
349
-
350
- if name in set(self.dock_area.docks.keys()):
351
- raise ValueError(f"Dock with name {name} already exists.")
352
-
353
- if position is None:
354
- position = "bottom"
355
-
356
- dock = BECDock(name=name, parent_dock_area=self, closable=closable)
358
+ dock_names = [
359
+ dock.object_name for dock in self.panel_list
360
+ ] # pylint: disable=protected-access
361
+ if name is not None: # Name is provided
362
+ if name in dock_names:
363
+ raise ValueError(
364
+ f"Name {name} must be unique for docks, but already exists in DockArea "
365
+ f"with name: {self.object_name} and id {self.gui_id}."
366
+ )
367
+ if not WidgetContainerUtils.has_name_valid_chars(name):
368
+ raise ValueError(
369
+ f"Name {name} contains invalid characters. "
370
+ f"Only alphanumeric characters and underscores are allowed."
371
+ )
372
+ else: # Name is not provided
373
+ name = WidgetContainerUtils.generate_unique_name(name="dock", list_of_names=dock_names)
374
+
375
+ dock = BECDock(
376
+ parent=self,
377
+ name=name, # this is dock name pyqtgraph property, this is displayed on label
378
+ object_name=name, # this is a real qt object name passed to BECConnector
379
+ parent_dock_area=self,
380
+ closable=closable,
381
+ )
357
382
  dock.config.position = position
358
- self.config.docks[name] = dock.config
359
-
383
+ self.config.docks[dock.name()] = dock.config
384
+ # The dock.name is equal to the name passed to BECDock
360
385
  self.dock_area.addDock(dock=dock, position=position, relativeTo=relative_to)
361
386
 
362
387
  if len(self.dock_area.docks) <= 1:
@@ -365,10 +390,11 @@ class BECDockArea(BECWidget, QWidget):
365
390
  for dock in self.dock_area.docks.values():
366
391
  dock.show_title_bar()
367
392
 
368
- if widget is not None and isinstance(widget, str):
369
- dock.add_widget(widget=widget, row=row, col=col, rowspan=rowspan, colspan=colspan)
370
- elif widget is not None and isinstance(widget, QWidget):
371
- dock.addWidget(widget, row=row, col=col, rowspan=rowspan, colspan=colspan)
393
+ if widget is not None:
394
+ # Check if widget name exists.
395
+ dock.new(
396
+ widget=widget, name=widget_name, row=row, col=col, rowspan=rowspan, colspan=colspan
397
+ )
372
398
  if (
373
399
  self._instructions_visible
374
400
  ): # TODO still decide how initial instructions should be handled
@@ -406,49 +432,24 @@ class BECDockArea(BECWidget, QWidget):
406
432
  Remove a temporary area from the dock area.
407
433
  This is a patched method of pyqtgraph's removeTempArea
408
434
  """
435
+ if area not in self.dock_area.tempAreas:
436
+ # FIXME add some context for the logging, I am not sure which object is passed.
437
+ # It looks like a pyqtgraph.DockArea
438
+ logger.info(f"Attempted to remove dock_area, but was not floating.")
439
+ return
409
440
  self.dock_area.tempAreas.remove(area)
410
441
  area.window().close()
411
442
  area.window().deleteLater()
412
443
 
413
- def clear_all(self):
414
- """
415
- Close all docks and remove all temp areas.
416
- """
417
- self.attach_all()
418
- for dock in dict(self.dock_area.docks).values():
419
- dock.remove()
420
- self.dock_area.docks.clear()
421
-
422
444
  def cleanup(self):
423
445
  """
424
446
  Cleanup the dock area.
425
447
  """
426
- self.clear_all()
427
- self.toolbar.close()
428
- self.toolbar.deleteLater()
429
- self.dock_area.close()
430
- self.dock_area.deleteLater()
448
+ self.delete_all()
449
+ self.dark_mode_button.close()
450
+ self.dark_mode_button.deleteLater()
431
451
  super().cleanup()
432
452
 
433
- def closeEvent(self, event):
434
- if self.parent() is None:
435
- # we are at top-level (independent window)
436
- if self.isVisible():
437
- # we are visible => user clicked on [X]
438
- # (when closeEvent is called from shutdown procedure,
439
- # everything is hidden first)
440
- # so, let's ignore "close", and do hide instead
441
- event.ignore()
442
- self.setVisible(False)
443
-
444
- def close(self):
445
- """
446
- Close the dock area and cleanup.
447
- Has to be implemented to overwrite pyqtgraph event accept in Container close.
448
- """
449
- self.cleanup()
450
- super().close()
451
-
452
453
  def show(self):
453
454
  """Show all windows including floating docks."""
454
455
  super().show()
@@ -467,18 +468,65 @@ class BECDockArea(BECWidget, QWidget):
467
468
  continue
468
469
  docks.window().hide()
469
470
 
470
- def delete(self):
471
- self.hide()
472
- self.deleteLater()
471
+ def delete_all(self) -> None:
472
+ """
473
+ Delete all docks.
474
+ """
475
+ self.attach_all()
476
+ for dock_name in self.panels.keys():
477
+ self.delete(dock_name)
478
+
479
+ def delete(self, dock_name: str):
480
+ """
481
+ Delete a dock by name.
482
+
483
+ Args:
484
+ dock_name(str): The name of the dock to delete.
485
+ """
486
+ dock = self.dock_area.docks.pop(dock_name, None)
487
+ self.config.docks.pop(dock_name, None)
488
+ if dock:
489
+ dock.close()
490
+ dock.deleteLater()
491
+ if len(self.dock_area.docks) <= 1:
492
+ for dock in self.dock_area.docks.values():
493
+ dock.hide_title_bar()
494
+ else:
495
+ raise ValueError(f"Dock with name {dock_name} does not exist.")
496
+ # self._broadcast_update()
497
+
498
+ def remove(self) -> None:
499
+ """
500
+ Remove the dock area. If the dock area is embedded in a BECMainWindow and
501
+ is set as the central widget, the main window will be closed.
502
+ """
503
+ parent = self.parent()
504
+ if isinstance(parent, BECMainWindow):
505
+ central_widget = parent.centralWidget()
506
+ if central_widget is self:
507
+ # Closing the parent will also close the dock area
508
+ parent.close()
509
+ return
510
+
511
+ self.close()
512
+
473
513
 
514
+ if __name__ == "__main__": # pragma: no cover
474
515
 
475
- if __name__ == "__main__":
476
- from qtpy.QtWidgets import QApplication
516
+ import sys
477
517
 
478
518
  from bec_widgets.utils.colors import set_theme
479
519
 
480
520
  app = QApplication([])
481
521
  set_theme("auto")
482
522
  dock_area = BECDockArea()
523
+ dock_1 = dock_area.new(name="dock_0", widget="DarkModeButton")
524
+ dock_1.new(widget="DarkModeButton")
525
+ # dock_1 = dock_area.new(name="dock_0", widget="Waveform")
526
+ dock_area.new(widget="DarkModeButton")
483
527
  dock_area.show()
528
+ dock_area.setGeometry(100, 100, 800, 600)
529
+ app.topLevelWidgets()
530
+ WidgetHierarchy.print_becconnector_hierarchy_from_app()
484
531
  app.exec_()
532
+ sys.exit(app.exec_())
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  import math
2
4
  import sys
3
5
  from typing import Dict, Literal, Optional, Set, Tuple, Union
@@ -34,7 +36,6 @@ class LayoutManagerWidget(QWidget):
34
36
 
35
37
  def __init__(self, parent=None, auto_reindex=True):
36
38
  super().__init__(parent)
37
- self.setObjectName("LayoutManagerWidget")
38
39
  self.layout = QGridLayout(self)
39
40
  self.auto_reindex = auto_reindex
40
41
 
@@ -0,0 +1,15 @@
1
+ import webbrowser
2
+
3
+
4
+ class BECWebLinksMixin:
5
+ @staticmethod
6
+ def open_bec_docs():
7
+ webbrowser.open("https://beamline-experiment-control.readthedocs.io/en/latest/")
8
+
9
+ @staticmethod
10
+ def open_bec_widgets_docs():
11
+ webbrowser.open("https://bec.readthedocs.io/projects/bec-widgets/en/latest/")
12
+
13
+ @staticmethod
14
+ def open_bec_bug_report():
15
+ webbrowser.open("https://gitlab.psi.ch/groups/bec/-/issues/")