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
@@ -15,12 +15,16 @@ from qtpy.QtWidgets import (
15
15
  )
16
16
 
17
17
  from bec_widgets.utils import BECDispatcher
18
- from bec_widgets.utils.colors import apply_theme
18
+ from bec_widgets.utils.widget_io import WidgetHierarchy as wh
19
19
  from bec_widgets.widgets.containers.dock import BECDockArea
20
- from bec_widgets.widgets.containers.figure import BECFigure
21
20
  from bec_widgets.widgets.containers.layout_manager.layout_manager import LayoutManagerWidget
22
21
  from bec_widgets.widgets.editors.jupyter_console.jupyter_console import BECJupyterConsole
23
- from bec_widgets.widgets.plots_next_gen.plot_base import PlotBase
22
+ from bec_widgets.widgets.plots.image.image import Image
23
+ from bec_widgets.widgets.plots.motor_map.motor_map import MotorMap
24
+ from bec_widgets.widgets.plots.multi_waveform.multi_waveform import MultiWaveform
25
+ from bec_widgets.widgets.plots.plot_base import PlotBase
26
+ from bec_widgets.widgets.plots.scatter_waveform.scatter_waveform import ScatterWaveform
27
+ from bec_widgets.widgets.plots.waveform.waveform import Waveform
24
28
 
25
29
 
26
30
  class JupyterConsoleWindow(QWidget): # pragma: no cover:
@@ -37,34 +41,24 @@ class JupyterConsoleWindow(QWidget): # pragma: no cover:
37
41
  {
38
42
  "np": np,
39
43
  "pg": pg,
40
- "fig": self.figure,
44
+ "wh": wh,
41
45
  "dock": self.dock,
42
- "w1": self.w1,
43
- "w2": self.w2,
44
- "w3": self.w3,
45
- "w4": self.w4,
46
- "w5": self.w5,
47
- "w6": self.w6,
48
- "w7": self.w7,
49
- "w8": self.w8,
50
- "w9": self.w9,
51
- "w10": self.w10,
52
- "d0": self.d0,
53
- "d1": self.d1,
54
- "d2": self.d2,
55
- "wave": self.wf,
56
- "im": self.im,
57
- "mm": self.mm,
58
- "mw": self.mw,
59
- "lm": self.lm,
60
- "btn1": self.btn1,
61
- "btn2": self.btn2,
62
- "btn3": self.btn3,
63
- "btn4": self.btn4,
64
- "btn5": self.btn5,
65
- "btn6": self.btn6,
66
- "pb": self.pb,
67
- "pi": self.pi,
46
+ # "im": self.im,
47
+ # "mi": self.mi,
48
+ # "mm": self.mm,
49
+ # "lm": self.lm,
50
+ # "btn1": self.btn1,
51
+ # "btn2": self.btn2,
52
+ # "btn3": self.btn3,
53
+ # "btn4": self.btn4,
54
+ # "btn5": self.btn5,
55
+ # "btn6": self.btn6,
56
+ # "pb": self.pb,
57
+ # "pi": self.pi,
58
+ # "wf": self.wf,
59
+ # "scatter": self.scatter,
60
+ # "scatter_mi": self.scatter,
61
+ # "mwf": self.mwf,
68
62
  }
69
63
  )
70
64
 
@@ -83,126 +77,85 @@ class JupyterConsoleWindow(QWidget): # pragma: no cover:
83
77
  first_tab_layout.addWidget(self.dock)
84
78
  tab_widget.addTab(first_tab, "Dock Area")
85
79
 
86
- second_tab = QWidget()
87
- second_tab_layout = QVBoxLayout(second_tab)
88
- self.figure = BECFigure(parent=self, gui_id="figure")
89
- second_tab_layout.addWidget(self.figure)
90
- tab_widget.addTab(second_tab, "BEC Figure")
91
-
92
- third_tab = QWidget()
93
- third_tab_layout = QVBoxLayout(third_tab)
94
- self.lm = LayoutManagerWidget()
95
- third_tab_layout.addWidget(self.lm)
96
- tab_widget.addTab(third_tab, "Layout Manager Widget")
97
-
98
- fourth_tab = QWidget()
99
- fourth_tab_layout = QVBoxLayout(fourth_tab)
100
- self.pb = PlotBase()
101
- self.pi = self.pb.plot_item
102
- fourth_tab_layout.addWidget(self.pb)
103
- tab_widget.addTab(fourth_tab, "PltoBase")
104
-
105
- tab_widget.setCurrentIndex(3)
106
-
80
+ # third_tab = QWidget()
81
+ # third_tab_layout = QVBoxLayout(third_tab)
82
+ # self.lm = LayoutManagerWidget()
83
+ # third_tab_layout.addWidget(self.lm)
84
+ # tab_widget.addTab(third_tab, "Layout Manager Widget")
85
+ #
86
+ # fourth_tab = QWidget()
87
+ # fourth_tab_layout = QVBoxLayout(fourth_tab)
88
+ # self.pb = PlotBase()
89
+ # self.pi = self.pb.plot_item
90
+ # fourth_tab_layout.addWidget(self.pb)
91
+ # tab_widget.addTab(fourth_tab, "PlotBase")
92
+ #
93
+ # tab_widget.setCurrentIndex(3)
94
+ #
107
95
  group_box = QGroupBox("Jupyter Console", splitter)
108
96
  group_box_layout = QVBoxLayout(group_box)
109
97
  self.console = BECJupyterConsole(inprocess=True)
110
98
  group_box_layout.addWidget(self.console)
111
-
112
- # Some buttons for layout testing
113
- self.btn1 = QPushButton("Button 1")
114
- self.btn2 = QPushButton("Button 2")
115
- self.btn3 = QPushButton("Button 3")
116
- self.btn4 = QPushButton("Button 4")
117
- self.btn5 = QPushButton("Button 5")
118
- self.btn6 = QPushButton("Button 6")
119
-
120
- # add stuff to figure
121
- self._init_figure()
122
-
123
- # init dock for testing
124
- self._init_dock()
125
-
126
- self.setWindowTitle("Jupyter Console Window")
127
-
128
- def _init_figure(self):
129
- self.w1 = self.figure.plot(x_name="samx", y_name="bpm4i", row=0, col=0)
130
- self.w1.set(
131
- title="Standard Plot with sync device, custom labels - w1",
132
- x_label="Motor Position",
133
- y_label="Intensity (A.U.)",
134
- )
135
- self.w2 = self.figure.motor_map("samx", "samy", row=0, col=1)
136
- self.w3 = self.figure.image(
137
- "eiger", color_map="viridis", vrange=(0, 100), title="Eiger Image - w3", row=0, col=2
138
- )
139
- self.w4 = self.figure.plot(
140
- x_name="samx",
141
- y_name="samy",
142
- z_name="bpm4i",
143
- color_map_z="magma",
144
- new=True,
145
- title="2D scatter plot - w4",
146
- row=0,
147
- col=3,
148
- )
149
- self.w5 = self.figure.plot(
150
- y_name="bpm4i",
151
- new=True,
152
- title="Best Effort Plot - w5",
153
- dap="GaussianModel",
154
- row=1,
155
- col=0,
156
- )
157
- self.w6 = self.figure.plot(
158
- x_name="timestamp", y_name="bpm4i", new=True, title="Timestamp Plot - w6", row=1, col=1
159
- )
160
- self.w7 = self.figure.plot(
161
- x_name="index", y_name="bpm4i", new=True, title="Index Plot - w7", row=1, col=2
162
- )
163
- self.w8 = self.figure.plot(
164
- y_name="monitor_async", new=True, title="Async Plot - Best Effort - w8", row=2, col=0
165
- )
166
- self.w9 = self.figure.plot(
167
- x_name="timestamp",
168
- y_name="monitor_async",
169
- new=True,
170
- title="Async Plot - timestamp - w9",
171
- row=2,
172
- col=1,
173
- )
174
- self.w10 = self.figure.plot(
175
- x_name="index",
176
- y_name="monitor_async",
177
- new=True,
178
- title="Async Plot - index - w10",
179
- row=2,
180
- col=2,
181
- )
182
-
183
- def _init_dock(self):
184
-
185
- self.d0 = self.dock.add_dock(name="dock_0")
186
- self.mm = self.d0.add_widget("BECMotorMapWidget")
187
- self.mm.change_motors("samx", "samy")
188
-
189
- self.d1 = self.dock.add_dock(name="dock_1", position="right")
190
- self.im = self.d1.add_widget("BECImageWidget")
191
- self.im.image("waveform", "1d")
192
-
193
- self.d2 = self.dock.add_dock(name="dock_2", position="bottom")
194
- self.wf = self.d2.add_widget("BECFigure", row=0, col=0)
195
-
196
- self.mw = self.wf.multi_waveform(monitor="waveform") # , config=config)
197
-
198
- self.dock.save_state()
99
+ #
100
+ # # Some buttons for layout testing
101
+ # self.btn1 = QPushButton("Button 1")
102
+ # self.btn2 = QPushButton("Button 2")
103
+ # self.btn3 = QPushButton("Button 3")
104
+ # self.btn4 = QPushButton("Button 4")
105
+ # self.btn5 = QPushButton("Button 5")
106
+ # self.btn6 = QPushButton("Button 6")
107
+ #
108
+ # fifth_tab = QWidget()
109
+ # fifth_tab_layout = QVBoxLayout(fifth_tab)
110
+ # self.wf = Waveform()
111
+ # fifth_tab_layout.addWidget(self.wf)
112
+ # tab_widget.addTab(fifth_tab, "Waveform Next Gen")
113
+ # tab_widget.setCurrentIndex(4)
114
+ #
115
+ # sixth_tab = QWidget()
116
+ # sixth_tab_layout = QVBoxLayout(sixth_tab)
117
+ # self.im = Image()
118
+ # self.mi = self.im.main_image
119
+ # sixth_tab_layout.addWidget(self.im)
120
+ # tab_widget.addTab(sixth_tab, "Image Next Gen")
121
+ # tab_widget.setCurrentIndex(5)
122
+ #
123
+ # seventh_tab = QWidget()
124
+ # seventh_tab_layout = QVBoxLayout(seventh_tab)
125
+ # self.scatter = ScatterWaveform()
126
+ # self.scatter_mi = self.scatter.main_curve
127
+ # self.scatter.plot("samx", "samy", "bpm4i")
128
+ # seventh_tab_layout.addWidget(self.scatter)
129
+ # tab_widget.addTab(seventh_tab, "Scatter Waveform")
130
+ # tab_widget.setCurrentIndex(6)
131
+ #
132
+ # eighth_tab = QWidget()
133
+ # eighth_tab_layout = QVBoxLayout(eighth_tab)
134
+ # self.mm = MotorMap()
135
+ # eighth_tab_layout.addWidget(self.mm)
136
+ # tab_widget.addTab(eighth_tab, "Motor Map")
137
+ # tab_widget.setCurrentIndex(7)
138
+ #
139
+ # ninth_tab = QWidget()
140
+ # ninth_tab_layout = QVBoxLayout(ninth_tab)
141
+ # self.mwf = MultiWaveform()
142
+ # ninth_tab_layout.addWidget(self.mwf)
143
+ # tab_widget.addTab(ninth_tab, "MultiWaveform")
144
+ # tab_widget.setCurrentIndex(8)
145
+ #
146
+ # # add stuff to the new Waveform widget
147
+ # self._init_waveform()
148
+ #
149
+ # self.setWindowTitle("Jupyter Console Window")
150
+
151
+ def _init_waveform(self):
152
+ self.wf.plot(y_name="bpm4i", y_entry="bpm4i", dap="GaussianModel")
153
+ self.wf.plot(y_name="bpm3a", y_entry="bpm3a", dap="GaussianModel")
199
154
 
200
155
  def closeEvent(self, event):
201
156
  """Override to handle things when main window is closed."""
202
157
  self.dock.cleanup()
203
158
  self.dock.close()
204
- self.figure.cleanup()
205
- self.figure.close()
206
159
  self.console.close()
207
160
 
208
161
  super().closeEvent(event)
@@ -218,17 +171,16 @@ if __name__ == "__main__": # pragma: no cover
218
171
  app = QApplication(sys.argv)
219
172
  app.setApplicationName("Jupyter Console")
220
173
  app.setApplicationDisplayName("Jupyter Console")
221
- apply_theme("dark")
222
- icon = material_icon("terminal", color="#434343", filled=True)
174
+ icon = material_icon("terminal", color=(255, 255, 255, 255), filled=True)
223
175
  app.setWindowIcon(icon)
224
176
 
225
- bec_dispatcher = BECDispatcher()
177
+ bec_dispatcher = BECDispatcher(gui_id="jupyter_console")
226
178
  client = bec_dispatcher.client
227
179
  client.start()
228
180
 
229
181
  win = JupyterConsoleWindow()
230
182
  win.show()
231
- win.resize(1200, 800)
183
+ win.resize(1500, 800)
232
184
 
233
185
  app.aboutToQuit.connect(win.close)
234
186
  sys.exit(app.exec_())
@@ -6,7 +6,7 @@ from qtpy.QtGui import QAction
6
6
  from qtpy.QtWidgets import QDialog, QDialogButtonBox, QVBoxLayout
7
7
 
8
8
  from bec_widgets.examples.plugin_example_pyside.tictactoe import TicTacToe
9
- from bec_widgets.qt_utils.error_popups import SafeSlot as Slot
9
+ from bec_widgets.utils.error_popups import SafeSlot as Slot
10
10
 
11
11
 
12
12
  class TicTacToeDialog(QDialog): # pragma: no cover
@@ -3,25 +3,29 @@ from __future__ import annotations
3
3
 
4
4
  import os
5
5
  import time
6
+ import traceback
6
7
  import uuid
8
+ from datetime import datetime
7
9
  from typing import TYPE_CHECKING, Optional
8
10
 
9
11
  from bec_lib.logger import bec_logger
10
12
  from bec_lib.utils.import_utils import lazy_import_from
11
13
  from pydantic import BaseModel, Field, field_validator
12
- from qtpy.QtCore import QObject, QRunnable, QThreadPool, Signal
14
+ from qtpy.QtCore import QObject, QRunnable, QThreadPool, QTimer, Signal
13
15
  from qtpy.QtWidgets import QApplication
14
16
 
15
17
  from bec_widgets.cli.rpc.rpc_register import RPCRegister
16
- from bec_widgets.qt_utils.error_popups import ErrorPopupUtility
17
- from bec_widgets.qt_utils.error_popups import SafeSlot as pyqtSlot
18
+ from bec_widgets.utils.error_popups import ErrorPopupUtility, SafeSlot
19
+ from bec_widgets.utils.widget_io import WidgetHierarchy
18
20
  from bec_widgets.utils.yaml_dialog import load_yaml, load_yaml_gui, save_yaml, save_yaml_gui
19
21
 
20
- if TYPE_CHECKING:
22
+ if TYPE_CHECKING: # pragma: no cover
21
23
  from bec_widgets.utils.bec_dispatcher import BECDispatcher
24
+ from bec_widgets.widgets.containers.dock import BECDock
25
+ else:
26
+ BECDispatcher = lazy_import_from("bec_widgets.utils.bec_dispatcher", ("BECDispatcher",))
22
27
 
23
28
  logger = bec_logger.logger
24
- BECDispatcher = lazy_import_from("bec_widgets.utils.bec_dispatcher", ("BECDispatcher",))
25
29
 
26
30
 
27
31
  class ConnectionConfig(BaseModel):
@@ -39,8 +43,7 @@ class ConnectionConfig(BaseModel):
39
43
  """Generate a GUI ID if none is provided."""
40
44
  if v is None:
41
45
  widget_class = values.data["widget_class"]
42
- v = f"{widget_class}_{str(time.time())}"
43
- return v
46
+ v = f"{widget_class}_{datetime.now().strftime('%Y_%m_%d_%H_%M_%S_%f')}"
44
47
  return v
45
48
 
46
49
 
@@ -75,15 +78,52 @@ class BECConnector:
75
78
  USER_ACCESS = ["_config_dict", "_get_all_rpc", "_rpc_id"]
76
79
  EXIT_HANDLERS = {}
77
80
 
78
- def __init__(self, client=None, config: ConnectionConfig = None, gui_id: str = None):
81
+ def __init__(
82
+ self,
83
+ client=None,
84
+ config: ConnectionConfig | None = None,
85
+ gui_id: str | None = None,
86
+ object_name: str | None = None,
87
+ parent_dock: BECDock | None = None, # TODO should go away -> issue created #473
88
+ root_widget: bool = False,
89
+ **kwargs,
90
+ ):
91
+ """
92
+ BECConnector mixin class to handle BEC client and device manager.
93
+
94
+ Args:
95
+ client(BECClient, optional): The BEC client.
96
+ config(ConnectionConfig, optional): The connection configuration with specific gui id.
97
+ gui_id(str, optional): The GUI ID.
98
+ object_name(str, optional): The object name.
99
+ parent_dock(BECDock, optional): The parent dock.# TODO should go away -> issue created #473
100
+ root_widget(bool, optional): If set to True, the parent_id will be always set to None, thus enforcing that the widget is accessible as a root widget of the BECGuiClient object.
101
+ **kwargs:
102
+ """
103
+ # Extract object_name from kwargs to not pass it to Qt class
104
+ object_name = object_name or kwargs.pop("objectName", None)
105
+ # Ensure the parent is always the first argument for QObject
106
+ parent = kwargs.pop("parent", None)
107
+ # This initializes the QObject or any qt related class BECConnector has to be used from this line down with QObject, otherwise hierarchy logic will not work
108
+ super().__init__(parent=parent, **kwargs)
109
+
110
+ assert isinstance(
111
+ self, QObject
112
+ ), "BECConnector must be used with a QObject or any qt related class."
113
+
114
+ # flag to check if the object was destroyed and its cleanup was called
115
+ self._destroyed = False
116
+
79
117
  # BEC related connections
80
118
  self.bec_dispatcher = BECDispatcher(client=client)
81
119
  self.client = self.bec_dispatcher.client if client is None else client
120
+ self._parent_dock = parent_dock # TODO also remove at some point -> issue created #473
121
+ self.rpc_register = RPCRegister()
82
122
 
83
123
  if not self.client in BECConnector.EXIT_HANDLERS:
84
124
  # register function to clean connections at exit;
85
125
  # the function depends on BECClient, and BECDispatcher
86
- @pyqtSlot()
126
+ @SafeSlot()
87
127
  def terminate(client=self.client, dispatcher=self.bec_dispatcher):
88
128
  logger.info("Disconnecting", repr(dispatcher))
89
129
  dispatcher.disconnect_all()
@@ -103,17 +143,26 @@ class BECConnector:
103
143
  )
104
144
  self.config = ConnectionConfig(widget_class=self.__class__.__name__)
105
145
 
146
+ # If the gui_id is passed, it should be respected. However, this should be revisted since
147
+ # the gui_id has to be unique, and may no longer be.
106
148
  if gui_id:
107
149
  self.config.gui_id = gui_id
108
- self.gui_id = gui_id
150
+ self.gui_id: str = gui_id # Keep namespace in sync
109
151
  else:
110
- self.gui_id = self.config.gui_id
152
+ self.gui_id: str = self.config.gui_id # type: ignore
111
153
 
112
- # register widget to rpc register
113
- # be careful: when registering, and the object is not a BECWidget,
114
- # cleanup has to be called manually since there is no 'closeEvent'
115
- self.rpc_register = RPCRegister()
116
- self.rpc_register.add_rpc(self)
154
+ if object_name is not None:
155
+ self.setObjectName(object_name)
156
+
157
+ # 1) If no objectName is set, set the initial name
158
+ if not self.objectName():
159
+ self.setObjectName(self.__class__.__name__)
160
+ self.object_name = self.objectName()
161
+
162
+ # 2) Enforce unique objectName among siblings with the same BECConnector parent
163
+ self.setParent(parent)
164
+ if isinstance(self.parent(), QObject) and hasattr(self, "cleanup"):
165
+ self.parent().destroyed.connect(self._run_cleanup_on_deleted_parent)
117
166
 
118
167
  # Error popups
119
168
  self.error_utility = ErrorPopupUtility()
@@ -122,7 +171,108 @@ class BECConnector:
122
171
  # Store references to running workers so they're not garbage collected prematurely.
123
172
  self._workers = []
124
173
 
125
- def submit_task(self, fn, *args, on_complete: pyqtSlot = None, **kwargs) -> Worker:
174
+ # If set to True, the parent_id will be always set to None, thus enforcing that the widget is accessible as a root widget of the BECGuiClient object.
175
+ self.root_widget = root_widget
176
+
177
+ QTimer.singleShot(0, self._update_object_name)
178
+
179
+ @property
180
+ def parent_id(self) -> str | None:
181
+ try:
182
+ if self.root_widget:
183
+ return None
184
+ connector_parent = WidgetHierarchy._get_becwidget_ancestor(self)
185
+ return connector_parent.gui_id if connector_parent else None
186
+ except:
187
+ logger.error(f"Error getting parent_id for {self.__class__.__name__}")
188
+
189
+ @SafeSlot()
190
+ def _run_cleanup_on_deleted_parent(self) -> None:
191
+ """
192
+ Run cleanup on the deleted parent.
193
+ This method is called when the parent is deleted.
194
+ """
195
+ if not hasattr(self, "cleanup"):
196
+ return
197
+ try:
198
+ if not self._destroyed:
199
+ self.cleanup()
200
+ self._destroyed = True
201
+ except Exception:
202
+ content = traceback.format_exc()
203
+ logger.info(
204
+ "Failed to run cleanup on deleted parent. "
205
+ f"This is not necessarily an error as the parent may be deleted before the child and includes already a cleanup. The following exception was raised:\n{content}"
206
+ )
207
+
208
+ def _update_object_name(self) -> None:
209
+ """
210
+ Enforce a unique object name among siblings and register the object for RPC.
211
+ This method is called through a single shot timer kicked off in the constructor.
212
+ """
213
+ # 1) Enforce unique objectName among siblings with the same BECConnector parent
214
+ self._enforce_unique_sibling_name()
215
+ # 2) Register the object for RPC
216
+ self.rpc_register.add_rpc(self)
217
+
218
+ def _enforce_unique_sibling_name(self):
219
+ """
220
+ Enforce that this BECConnector has a unique objectName among its siblings.
221
+
222
+ Sibling logic:
223
+ - If there's a nearest BECConnector parent, only compare with children of that parent.
224
+ - If parent is None (i.e., top-level object), compare with all other top-level BECConnectors.
225
+ """
226
+ QApplication.processEvents()
227
+ parent_bec = WidgetHierarchy._get_becwidget_ancestor(self)
228
+
229
+ if parent_bec:
230
+ # We have a parent => only compare with siblings under that parent
231
+ siblings = parent_bec.findChildren(BECConnector)
232
+ else:
233
+ # No parent => treat all top-level BECConnectors as siblings
234
+ # 1) Gather all BECConnectors from QApplication
235
+ all_widgets = QApplication.allWidgets()
236
+ all_bec = [w for w in all_widgets if isinstance(w, BECConnector)]
237
+ # 2) "Top-level" means closest BECConnector parent is None
238
+ top_level_bec = [
239
+ w for w in all_bec if WidgetHierarchy._get_becwidget_ancestor(w) is None
240
+ ]
241
+ # 3) We are among these top-level siblings
242
+ siblings = top_level_bec
243
+
244
+ # Collect used names among siblings
245
+ used_names = {sib.objectName() for sib in siblings if sib is not self}
246
+
247
+ base_name = self.object_name
248
+ if base_name not in used_names:
249
+ # Name is already unique among siblings
250
+ return
251
+
252
+ # Need a suffix to avoid collision
253
+ counter = 0
254
+ while True:
255
+ trial_name = f"{base_name}_{counter}"
256
+ if trial_name not in used_names:
257
+ self.setObjectName(trial_name)
258
+ self.object_name = trial_name
259
+ break
260
+ counter += 1
261
+
262
+ # pylint: disable=invalid-name
263
+ def setObjectName(self, name: str) -> None:
264
+ """
265
+ Set the object name of the widget.
266
+
267
+ Args:
268
+ name (str): The new object name.
269
+ """
270
+ super().setObjectName(name)
271
+ self.object_name = name
272
+ if self.rpc_register.object_is_registered(self):
273
+ self.rpc_register.broadcast()
274
+
275
+ def submit_task(self, fn, *args, on_complete: SafeSlot = None, **kwargs) -> Worker:
126
276
  """
127
277
  Submit a task to run in a separate thread. The task will run the specified
128
278
  function with the provided arguments and emit the completed signal when done.
@@ -195,6 +345,7 @@ class BECConnector:
195
345
  """
196
346
  self.config = config
197
347
 
348
+ # FIXME some thoughts are required to decide how thhis should work with rpc registry
198
349
  def apply_config(self, config: dict, generate_new_id: bool = True) -> None:
199
350
  """
200
351
  Apply the configuration to the widget.
@@ -207,11 +358,12 @@ class BECConnector:
207
358
  if generate_new_id is True:
208
359
  gui_id = str(uuid.uuid4())
209
360
  self.rpc_register.remove_rpc(self)
210
- self.set_gui_id(gui_id)
361
+ self._set_gui_id(gui_id)
211
362
  self.rpc_register.add_rpc(self)
212
363
  else:
213
364
  self.gui_id = self.config.gui_id
214
365
 
366
+ # FIXME some thoughts are required to decide how thhis should work with rpc registry
215
367
  def load_config(self, path: str | None = None, gui: bool = False):
216
368
  """
217
369
  Load the configuration of the widget from YAML.
@@ -248,8 +400,8 @@ class BECConnector:
248
400
  file_path = os.path.join(path, f"{self.__class__.__name__}_config.yaml")
249
401
  save_yaml(file_path, self._config_dict)
250
402
 
251
- @pyqtSlot(str)
252
- def set_gui_id(self, gui_id: str) -> None:
403
+ # @SafeSlot(str)
404
+ def _set_gui_id(self, gui_id: str) -> None:
253
405
  """
254
406
  Set the GUI ID for the widget.
255
407
 
@@ -280,7 +432,7 @@ class BECConnector:
280
432
  self.client = client
281
433
  self.get_bec_shortcuts()
282
434
 
283
- @pyqtSlot(ConnectionConfig) # TODO can be also dict
435
+ @SafeSlot(ConnectionConfig) # TODO can be also dict
284
436
  def on_config_update(self, config: ConnectionConfig | dict) -> None:
285
437
  """
286
438
  Update the configuration for the widget.
@@ -288,9 +440,26 @@ class BECConnector:
288
440
  Args:
289
441
  config (ConnectionConfig | dict): Configuration settings.
290
442
  """
443
+ gui_id = getattr(config, "gui_id", None)
291
444
  if isinstance(config, dict):
292
445
  config = ConnectionConfig(**config)
293
446
  self.config = config
447
+ if gui_id and config.gui_id != gui_id: # Recreating config should not overwrite the gui_id
448
+ self.config.gui_id = gui_id
449
+
450
+ def remove(self):
451
+ """Cleanup the BECConnector"""
452
+ # If the widget is attached to a dock, remove it from the dock.
453
+ # TODO this should be handled by dock and dock are not by BECConnector -> issue created #473
454
+ if self._parent_dock is not None:
455
+ self._parent_dock.delete(self.object_name)
456
+ # If the widget is from Qt, trigger its close method.
457
+ elif hasattr(self, "close"):
458
+ self.close()
459
+ # If the widget is neither from a Dock nor from Qt, remove it from the RPC registry.
460
+ # i.e. Curve Item from Waveform
461
+ else:
462
+ self.rpc_register.remove_rpc(self)
294
463
 
295
464
  def get_config(self, dict_output: bool = True) -> dict | BaseModel:
296
465
  """
@@ -10,6 +10,8 @@ from bec_qthemes import material_icon
10
10
  from qtpy import PYSIDE6
11
11
  from qtpy.QtGui import QIcon
12
12
 
13
+ from bec_widgets.utils.bec_plugin_helper import user_widget_plugin
14
+
13
15
  if PYSIDE6:
14
16
  from PySide6.scripts.pyside_tool import (
15
17
  _extend_path_var,
@@ -150,7 +152,12 @@ def main(): # pragma: no cover
150
152
  print("PYSIDE6 is not available in the environment. Exiting...")
151
153
  return
152
154
  base_dir = Path(os.path.dirname(bec_widgets.__file__)).resolve()
155
+
153
156
  plugin_paths = find_plugin_paths(base_dir)
157
+ if (plugin_repo := user_widget_plugin()) and isinstance(plugin_repo.__file__, str):
158
+ plugin_repo_dir = Path(os.path.dirname(plugin_repo.__file__)).resolve()
159
+ plugin_paths.extend(find_plugin_paths(plugin_repo_dir))
160
+
154
161
  set_plugin_environment_variable(plugin_paths)
155
162
 
156
163
  patch_designer()