bec-widgets 1.25.1__py3-none-any.whl → 2.0.1__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 +639 -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 +188 -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.1.dist-info}/METADATA +3 -3
  146. {bec_widgets-1.25.1.dist-info → bec_widgets-2.0.1.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.1.dist-info}/WHEEL +0 -0
  195. {bec_widgets-1.25.1.dist-info → bec_widgets-2.0.1.dist-info}/entry_points.txt +0 -0
  196. {bec_widgets-1.25.1.dist-info → bec_widgets-2.0.1.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:
@@ -275,6 +308,8 @@ class BECDockArea(BECWidget, QWidget):
275
308
  """
276
309
  if state is None:
277
310
  state = self.config.docks_state
311
+ if state is None:
312
+ return
278
313
  self.dock_area.restoreState(state, missing=missing, extra=extra)
279
314
 
280
315
  @SafeSlot()
@@ -289,36 +324,17 @@ class BECDockArea(BECWidget, QWidget):
289
324
  self.config.docks_state = last_state
290
325
  return last_state
291
326
 
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
327
  @SafeSlot(popup_error=True)
312
- def add_dock(
328
+ def new(
313
329
  self,
314
- name: str = None,
315
- position: Literal["bottom", "top", "left", "right", "above", "below"] = None,
330
+ name: str | None = None,
331
+ widget: str | QWidget | None = None,
332
+ widget_name: str | None = None,
333
+ position: Literal["bottom", "top", "left", "right", "above", "below"] = "bottom",
316
334
  relative_to: BECDock | None = None,
317
335
  closable: bool = True,
318
336
  floating: bool = False,
319
- prefix: str = "dock",
320
- widget: str | QWidget | None = None,
321
- row: int = None,
337
+ row: int | None = None,
322
338
  col: int = 0,
323
339
  rowspan: int = 1,
324
340
  colspan: int = 1,
@@ -328,12 +344,11 @@ class BECDockArea(BECWidget, QWidget):
328
344
 
329
345
  Args:
330
346
  name(str): The name of the dock to be displayed and for further references. Has to be unique.
347
+ widget(str|QWidget|None): The widget to be added to the dock. While using RPC, only BEC RPC widgets from RPCWidgetHandler are allowed.
331
348
  position(Literal["bottom", "top", "left", "right", "above", "below"]): The position of the dock.
332
349
  relative_to(BECDock): The dock to which the new dock should be added relative to.
333
350
  closable(bool): Whether the dock is closable.
334
351
  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
352
  row(int): The row of the added widget.
338
353
  col(int): The column of the added widget.
339
354
  rowspan(int): The rowspan of the added widget.
@@ -342,21 +357,33 @@ class BECDockArea(BECWidget, QWidget):
342
357
  Returns:
343
358
  BECDock: The created dock.
344
359
  """
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)
360
+ dock_names = [
361
+ dock.object_name for dock in self.panel_list
362
+ ] # pylint: disable=protected-access
363
+ if name is not None: # Name is provided
364
+ if name in dock_names:
365
+ raise ValueError(
366
+ f"Name {name} must be unique for docks, but already exists in DockArea "
367
+ f"with name: {self.object_name} and id {self.gui_id}."
368
+ )
369
+ if not WidgetContainerUtils.has_name_valid_chars(name):
370
+ raise ValueError(
371
+ f"Name {name} contains invalid characters. "
372
+ f"Only alphanumeric characters and underscores are allowed."
373
+ )
374
+ else: # Name is not provided
375
+ name = WidgetContainerUtils.generate_unique_name(name="dock", list_of_names=dock_names)
376
+
377
+ dock = BECDock(
378
+ parent=self,
379
+ name=name, # this is dock name pyqtgraph property, this is displayed on label
380
+ object_name=name, # this is a real qt object name passed to BECConnector
381
+ parent_dock_area=self,
382
+ closable=closable,
383
+ )
357
384
  dock.config.position = position
358
- self.config.docks[name] = dock.config
359
-
385
+ self.config.docks[dock.name()] = dock.config
386
+ # The dock.name is equal to the name passed to BECDock
360
387
  self.dock_area.addDock(dock=dock, position=position, relativeTo=relative_to)
361
388
 
362
389
  if len(self.dock_area.docks) <= 1:
@@ -365,10 +392,11 @@ class BECDockArea(BECWidget, QWidget):
365
392
  for dock in self.dock_area.docks.values():
366
393
  dock.show_title_bar()
367
394
 
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)
395
+ if widget is not None:
396
+ # Check if widget name exists.
397
+ dock.new(
398
+ widget=widget, name=widget_name, row=row, col=col, rowspan=rowspan, colspan=colspan
399
+ )
372
400
  if (
373
401
  self._instructions_visible
374
402
  ): # TODO still decide how initial instructions should be handled
@@ -406,49 +434,24 @@ class BECDockArea(BECWidget, QWidget):
406
434
  Remove a temporary area from the dock area.
407
435
  This is a patched method of pyqtgraph's removeTempArea
408
436
  """
437
+ if area not in self.dock_area.tempAreas:
438
+ # FIXME add some context for the logging, I am not sure which object is passed.
439
+ # It looks like a pyqtgraph.DockArea
440
+ logger.info(f"Attempted to remove dock_area, but was not floating.")
441
+ return
409
442
  self.dock_area.tempAreas.remove(area)
410
443
  area.window().close()
411
444
  area.window().deleteLater()
412
445
 
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
446
  def cleanup(self):
423
447
  """
424
448
  Cleanup the dock area.
425
449
  """
426
- self.clear_all()
427
- self.toolbar.close()
428
- self.toolbar.deleteLater()
429
- self.dock_area.close()
430
- self.dock_area.deleteLater()
450
+ self.delete_all()
451
+ self.dark_mode_button.close()
452
+ self.dark_mode_button.deleteLater()
431
453
  super().cleanup()
432
454
 
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
455
  def show(self):
453
456
  """Show all windows including floating docks."""
454
457
  super().show()
@@ -467,18 +470,65 @@ class BECDockArea(BECWidget, QWidget):
467
470
  continue
468
471
  docks.window().hide()
469
472
 
470
- def delete(self):
471
- self.hide()
472
- self.deleteLater()
473
+ def delete_all(self) -> None:
474
+ """
475
+ Delete all docks.
476
+ """
477
+ self.attach_all()
478
+ for dock_name in self.panels.keys():
479
+ self.delete(dock_name)
480
+
481
+ def delete(self, dock_name: str):
482
+ """
483
+ Delete a dock by name.
484
+
485
+ Args:
486
+ dock_name(str): The name of the dock to delete.
487
+ """
488
+ dock = self.dock_area.docks.pop(dock_name, None)
489
+ self.config.docks.pop(dock_name, None)
490
+ if dock:
491
+ dock.close()
492
+ dock.deleteLater()
493
+ if len(self.dock_area.docks) <= 1:
494
+ for dock in self.dock_area.docks.values():
495
+ dock.hide_title_bar()
496
+ else:
497
+ raise ValueError(f"Dock with name {dock_name} does not exist.")
498
+ # self._broadcast_update()
499
+
500
+ def remove(self) -> None:
501
+ """
502
+ Remove the dock area. If the dock area is embedded in a BECMainWindow and
503
+ is set as the central widget, the main window will be closed.
504
+ """
505
+ parent = self.parent()
506
+ if isinstance(parent, BECMainWindow):
507
+ central_widget = parent.centralWidget()
508
+ if central_widget is self:
509
+ # Closing the parent will also close the dock area
510
+ parent.close()
511
+ return
512
+
513
+ self.close()
514
+
473
515
 
516
+ if __name__ == "__main__": # pragma: no cover
474
517
 
475
- if __name__ == "__main__":
476
- from qtpy.QtWidgets import QApplication
518
+ import sys
477
519
 
478
520
  from bec_widgets.utils.colors import set_theme
479
521
 
480
522
  app = QApplication([])
481
523
  set_theme("auto")
482
524
  dock_area = BECDockArea()
525
+ dock_1 = dock_area.new(name="dock_0", widget="DarkModeButton")
526
+ dock_1.new(widget="DarkModeButton")
527
+ # dock_1 = dock_area.new(name="dock_0", widget="Waveform")
528
+ dock_area.new(widget="DarkModeButton")
483
529
  dock_area.show()
530
+ dock_area.setGeometry(100, 100, 800, 600)
531
+ app.topLevelWidgets()
532
+ WidgetHierarchy.print_becconnector_hierarchy_from_app()
484
533
  app.exec_()
534
+ 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/")