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
PKG-INFO CHANGED
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bec_widgets
3
- Version: 1.25.0
3
+ Version: 2.0.0
4
4
  Summary: BEC Widgets
5
5
  Project-URL: Bug Tracker, https://gitlab.psi.ch/bec/bec_widgets/issues
6
6
  Project-URL: Homepage, https://gitlab.psi.ch/bec/bec_widgets
@@ -10,13 +10,13 @@ Classifier: Programming Language :: Python :: 3
10
10
  Classifier: Topic :: Scientific/Engineering
11
11
  Requires-Python: >=3.10
12
12
  Requires-Dist: bec-ipython-client<=4.0,>=2.21.4
13
- Requires-Dist: bec-lib<=4.0,>=2.21.4
13
+ Requires-Dist: bec-lib<=4.0,>=3.29
14
14
  Requires-Dist: bec-qthemes>=0.7,~=0.7
15
15
  Requires-Dist: black~=24.0
16
16
  Requires-Dist: isort>=5.13.2,~=5.13
17
17
  Requires-Dist: pydantic~=2.0
18
18
  Requires-Dist: pyqtgraph~=0.13
19
- Requires-Dist: pyside6>=6.8
19
+ Requires-Dist: pyside6~=6.8.2
20
20
  Requires-Dist: pyte
21
21
  Requires-Dist: qtconsole>=5.5.1,~=5.5
22
22
  Requires-Dist: qtpy~=2.4
bec_widgets/__init__.py CHANGED
@@ -0,0 +1,4 @@
1
+ from bec_widgets.utils.bec_widget import BECWidget
2
+ from bec_widgets.utils.error_popups import SafeProperty, SafeSlot
3
+
4
+ __all__ = ["BECWidget", "SafeSlot", "SafeProperty"]
@@ -0,0 +1,23 @@
1
+ from __future__ import annotations
2
+
3
+ from bec_widgets.widgets.containers.auto_update.auto_updates import AutoUpdates
4
+ from bec_widgets.widgets.containers.dock.dock_area import BECDockArea
5
+
6
+
7
+ def dock_area(object_name: str | None = None) -> BECDockArea:
8
+ _dock_area = BECDockArea(object_name=object_name, root_widget=True)
9
+ return _dock_area
10
+
11
+
12
+ def auto_update_dock_area(object_name: str | None = None) -> AutoUpdates:
13
+ """
14
+ Create a dock area with auto update enabled.
15
+
16
+ Args:
17
+ object_name(str): The name of the dock area.
18
+
19
+ Returns:
20
+ BECDockArea: The created dock area.
21
+ """
22
+ _auto_update = AutoUpdates(object_name=object_name)
23
+ return _auto_update
@@ -0,0 +1,430 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ import xml.etree.ElementTree as ET
5
+ from typing import TYPE_CHECKING
6
+
7
+ from bec_lib.logger import bec_logger
8
+ from qtpy.QtCore import Qt, Signal
9
+ from qtpy.QtGui import QPainter, QPainterPath, QPixmap
10
+ from qtpy.QtWidgets import (
11
+ QApplication,
12
+ QComboBox,
13
+ QFileDialog,
14
+ QHBoxLayout,
15
+ QLabel,
16
+ QPushButton,
17
+ QSizePolicy,
18
+ QSpacerItem,
19
+ QWidget,
20
+ )
21
+
22
+ import bec_widgets
23
+ from bec_widgets.cli.rpc.rpc_register import RPCRegister
24
+ from bec_widgets.utils.container_utils import WidgetContainerUtils
25
+ from bec_widgets.utils.error_popups import SafeSlot
26
+ from bec_widgets.utils.plugin_utils import get_plugin_auto_updates
27
+ from bec_widgets.utils.round_frame import RoundedFrame
28
+ from bec_widgets.utils.toolbar import ModularToolBar
29
+ from bec_widgets.utils.ui_loader import UILoader
30
+ from bec_widgets.widgets.containers.auto_update.auto_updates import AutoUpdates
31
+ from bec_widgets.widgets.containers.dock.dock_area import BECDockArea
32
+ from bec_widgets.widgets.containers.main_window.main_window import BECMainWindow, UILaunchWindow
33
+ from bec_widgets.widgets.utility.visual.dark_mode_button.dark_mode_button import DarkModeButton
34
+
35
+ if TYPE_CHECKING: # pragma: no cover
36
+ from qtpy.QtCore import QObject
37
+
38
+ logger = bec_logger.logger
39
+ MODULE_PATH = os.path.dirname(bec_widgets.__file__)
40
+
41
+
42
+ class LaunchTile(RoundedFrame):
43
+ open_signal = Signal()
44
+
45
+ def __init__(
46
+ self,
47
+ parent: QObject | None = None,
48
+ icon_path: str | None = None,
49
+ top_label: str | None = None,
50
+ main_label: str | None = None,
51
+ description: str | None = None,
52
+ show_selector: bool = False,
53
+ ):
54
+ super().__init__(parent=parent, orientation="vertical")
55
+
56
+ self.icon_label = QLabel(parent=self)
57
+ self.icon_label.setFixedSize(100, 100)
58
+ self.icon_label.setScaledContents(True)
59
+ pixmap = QPixmap(icon_path)
60
+ if not pixmap.isNull():
61
+ size = 100
62
+ circular_pixmap = QPixmap(size, size)
63
+ circular_pixmap.fill(Qt.transparent)
64
+
65
+ painter = QPainter(circular_pixmap)
66
+ painter.setRenderHints(QPainter.Antialiasing, True)
67
+ path = QPainterPath()
68
+ path.addEllipse(0, 0, size, size)
69
+ painter.setClipPath(path)
70
+ pixmap = pixmap.scaled(size, size, Qt.KeepAspectRatio, Qt.SmoothTransformation)
71
+ painter.drawPixmap(0, 0, pixmap)
72
+ painter.end()
73
+
74
+ self.icon_label.setPixmap(circular_pixmap)
75
+ self.layout.addWidget(self.icon_label, alignment=Qt.AlignCenter)
76
+
77
+ # Top label
78
+ self.top_label = QLabel(top_label.upper())
79
+ font_top = self.top_label.font()
80
+ font_top.setPointSize(10)
81
+ self.top_label.setFont(font_top)
82
+ self.layout.addWidget(self.top_label, alignment=Qt.AlignCenter)
83
+
84
+ # Main label
85
+ self.main_label = QLabel(main_label)
86
+ font_main = self.main_label.font()
87
+ font_main.setPointSize(14)
88
+ font_main.setBold(True)
89
+ self.main_label.setFont(font_main)
90
+ self.main_label.setWordWrap(True)
91
+ self.main_label.setAlignment(Qt.AlignCenter)
92
+ self.layout.addWidget(self.main_label)
93
+
94
+ self.spacer_top = QSpacerItem(0, 10, QSizePolicy.Fixed, QSizePolicy.Fixed)
95
+ self.layout.addItem(self.spacer_top)
96
+
97
+ # Description
98
+ self.description_label = QLabel(description)
99
+ self.description_label.setWordWrap(True)
100
+ self.description_label.setAlignment(Qt.AlignCenter)
101
+ self.layout.addWidget(self.description_label)
102
+
103
+ # Selector
104
+ if show_selector:
105
+ self.selector = QComboBox(self)
106
+ self.layout.addWidget(self.selector)
107
+ else:
108
+ self.selector = None
109
+
110
+ self.spacer_bottom = QSpacerItem(0, 0, QSizePolicy.Fixed, QSizePolicy.Expanding)
111
+ self.layout.addItem(self.spacer_bottom)
112
+
113
+ # Action button
114
+ self.action_button = QPushButton("Open")
115
+ self.action_button.setStyleSheet(
116
+ """
117
+ QPushButton {
118
+ background-color: #007AFF;
119
+ border: none;
120
+ padding: 8px 16px;
121
+ color: white;
122
+ border-radius: 6px;
123
+ font-weight: bold;
124
+ }
125
+ QPushButton:hover {
126
+ background-color: #005BB5;
127
+ }
128
+ """
129
+ )
130
+ self.layout.addWidget(self.action_button, alignment=Qt.AlignCenter)
131
+
132
+
133
+ class LaunchWindow(BECMainWindow):
134
+ RPC = True
135
+ TILE_SIZE = (250, 300)
136
+ USER_ACCESS = ["show_launcher", "hide_launcher"]
137
+
138
+ def __init__(
139
+ self, parent=None, gui_id: str = None, window_title="BEC Launcher", *args, **kwargs
140
+ ):
141
+ super().__init__(parent=parent, gui_id=gui_id, window_title=window_title, **kwargs)
142
+
143
+ self.app = QApplication.instance()
144
+
145
+ # Toolbar
146
+ self.dark_mode_button = DarkModeButton(parent=self, toolbar=True)
147
+ self.toolbar = ModularToolBar(parent=self)
148
+ self.addToolBar(Qt.TopToolBarArea, self.toolbar)
149
+ self.spacer = QWidget(self)
150
+ self.spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
151
+ self.toolbar.addWidget(self.spacer)
152
+ self.toolbar.addWidget(self.dark_mode_button)
153
+
154
+ # Main Widget
155
+ self.central_widget = QWidget(self)
156
+ self.central_widget.layout = QHBoxLayout(self.central_widget)
157
+ self.setCentralWidget(self.central_widget)
158
+
159
+ self.tile_dock_area = LaunchTile(
160
+ icon_path=os.path.join(MODULE_PATH, "assets", "app_icons", "bec_widgets_icon.png"),
161
+ top_label="Get started",
162
+ main_label="BEC Dock Area",
163
+ description="Highly flexible and customizable dock area application with modular widgets.",
164
+ )
165
+ self.tile_dock_area.setFixedSize(*self.TILE_SIZE)
166
+
167
+ self.tile_auto_update = LaunchTile(
168
+ icon_path=os.path.join(MODULE_PATH, "assets", "app_icons", "auto_update.png"),
169
+ top_label="Get automated",
170
+ main_label="BEC Auto Update Dock Area",
171
+ description="Dock area with auto update functionality for BEC widgets plotting.",
172
+ show_selector=True,
173
+ )
174
+ self.tile_auto_update.setFixedSize(*self.TILE_SIZE)
175
+
176
+ self.tile_ui_file = LaunchTile(
177
+ icon_path=os.path.join(MODULE_PATH, "assets", "app_icons", "ui_loader_tile.png"),
178
+ top_label="Get customized",
179
+ main_label="Launch Custom UI File",
180
+ description="GUI application with custom UI file.",
181
+ )
182
+ self.tile_ui_file.setFixedSize(*self.TILE_SIZE)
183
+
184
+ # Add tiles to the main layout
185
+ self.central_widget.layout.addWidget(self.tile_dock_area)
186
+ self.central_widget.layout.addWidget(self.tile_auto_update)
187
+ self.central_widget.layout.addWidget(self.tile_ui_file)
188
+
189
+ # hacky solution no time to waste
190
+ self.tiles = [self.tile_dock_area, self.tile_auto_update, self.tile_ui_file]
191
+
192
+ # Connect signals
193
+ self.tile_dock_area.action_button.clicked.connect(lambda: self.launch("dock_area"))
194
+ self.tile_auto_update.action_button.clicked.connect(self._open_auto_update)
195
+ self.tile_ui_file.action_button.clicked.connect(self._open_custom_ui_file)
196
+ self._update_theme()
197
+
198
+ # Auto updates
199
+ self.available_auto_updates: dict[str, type[AutoUpdates]] = (
200
+ self._update_available_auto_updates()
201
+ )
202
+ if self.tile_auto_update.selector is not None:
203
+ self.tile_auto_update.selector.addItems(
204
+ list(self.available_auto_updates.keys()) + ["Default"]
205
+ )
206
+
207
+ self.register = RPCRegister()
208
+ self.register.callbacks.append(self._turn_off_the_lights)
209
+ self.register.broadcast()
210
+
211
+ def launch(
212
+ self,
213
+ launch_script: str,
214
+ name: str | None = None,
215
+ geometry: tuple[int, int, int, int] | None = None,
216
+ **kwargs,
217
+ ) -> QWidget | None:
218
+ """Launch the specified script. If the launch script creates a QWidget, it will be
219
+ embedded in a BECMainWindow. If the launch script creates a BECMainWindow, it will be shown
220
+ as a separate window.
221
+
222
+ Args:
223
+ launch_script(str): The name of the script to be launched.
224
+ name(str): The name of the dock area.
225
+ geometry(tuple): The geometry parameters to be passed to the dock area.
226
+ Returns:
227
+ QWidget: The created dock area.
228
+ """
229
+ from bec_widgets.applications import bw_launch
230
+
231
+ with RPCRegister.delayed_broadcast() as rpc_register:
232
+ existing_dock_areas = rpc_register.get_names_of_rpc_by_class_type(BECDockArea)
233
+ if name is not None:
234
+ if name in existing_dock_areas:
235
+ raise ValueError(
236
+ f"Name {name} must be unique for dock areas, but already exists: {existing_dock_areas}."
237
+ )
238
+ if not WidgetContainerUtils.has_name_valid_chars(name):
239
+ raise ValueError(
240
+ f"Name {name} contains invalid characters. Only alphanumeric characters, underscores, and dashes are allowed."
241
+ )
242
+ else:
243
+ name = "dock_area"
244
+ name = WidgetContainerUtils.generate_unique_name(name, existing_dock_areas)
245
+
246
+ if launch_script is None:
247
+ launch_script = "dock_area"
248
+ if not isinstance(launch_script, str):
249
+ raise ValueError(f"Launch script must be a string, but got {type(launch_script)}.")
250
+
251
+ if launch_script == "custom_ui_file":
252
+ ui_file = kwargs.pop("ui_file", None)
253
+ if not ui_file:
254
+ return None
255
+ return self._launch_custom_ui_file(ui_file)
256
+
257
+ if launch_script == "auto_update":
258
+ auto_update = kwargs.pop("auto_update", None)
259
+ return self._launch_auto_update(auto_update)
260
+
261
+ launch = getattr(bw_launch, launch_script, None)
262
+ if launch is None:
263
+ raise ValueError(f"Launch script {launch_script} not found.")
264
+
265
+ result_widget = launch(name)
266
+ result_widget.resize(result_widget.minimumSizeHint())
267
+ # TODO Should we simply use the specified name as title here?
268
+ result_widget.window().setWindowTitle(f"BEC - {name}")
269
+ logger.info(f"Created new dock area: {name}")
270
+
271
+ if geometry is not None:
272
+ result_widget.setGeometry(*geometry)
273
+ if isinstance(result_widget, BECMainWindow):
274
+ result_widget.show()
275
+ else:
276
+ window = BECMainWindow()
277
+ window.setCentralWidget(result_widget)
278
+ window.show()
279
+ return result_widget
280
+
281
+ def _launch_custom_ui_file(self, ui_file: str | None) -> BECMainWindow:
282
+ # Load the custom UI file
283
+ if ui_file is None:
284
+ raise ValueError("UI file must be provided for custom UI file launch.")
285
+ filename = os.path.basename(ui_file).split(".")[0]
286
+
287
+ tree = ET.parse(ui_file)
288
+ root = tree.getroot()
289
+ # Check if the top-level widget is a QMainWindow
290
+ widget = root.find("widget")
291
+ if widget is None:
292
+ raise ValueError("No widget found in the UI file.")
293
+
294
+ if widget.attrib.get("class") == "QMainWindow":
295
+ raise ValueError(
296
+ "Loading a QMainWindow from a UI file is currently not supported. "
297
+ "If you need this, please contact the BEC team or create a ticket on gitlab.psi.ch/bec/bec_widgets."
298
+ )
299
+
300
+ window = UILaunchWindow(object_name=filename)
301
+ QApplication.processEvents()
302
+ result_widget = UILoader(window).loader(ui_file)
303
+ window.setCentralWidget(result_widget)
304
+ window.setWindowTitle(f"BEC - {window.object_name}")
305
+ window.show()
306
+ logger.info(f"Object name of new instance: {result_widget.objectName()}, {window.gui_id}")
307
+ return window
308
+
309
+ def _launch_auto_update(self, auto_update: str) -> AutoUpdates:
310
+ if auto_update in self.available_auto_updates:
311
+ auto_update_cls = self.available_auto_updates[auto_update]
312
+ window = auto_update_cls()
313
+ else:
314
+
315
+ auto_update = "auto_updates"
316
+ window = AutoUpdates()
317
+
318
+ window.resize(window.minimumSizeHint())
319
+ QApplication.processEvents()
320
+ window.setWindowTitle(f"BEC - {window.objectName()}")
321
+ window.show()
322
+ return window
323
+
324
+ def apply_theme(self, theme: str):
325
+ """
326
+ Change the theme of the application.
327
+ """
328
+ for tile in self.tiles:
329
+ tile.apply_theme(theme)
330
+
331
+ super().apply_theme(theme)
332
+
333
+ def _open_auto_update(self):
334
+ """
335
+ Open the auto update window.
336
+ """
337
+ if self.tile_auto_update.selector is None:
338
+ auto_update = None
339
+ else:
340
+ auto_update = self.tile_auto_update.selector.currentText()
341
+ if auto_update == "Default":
342
+ auto_update = None
343
+ return self.launch("auto_update", auto_update=auto_update)
344
+
345
+ @SafeSlot(popup_error=True)
346
+ def _open_custom_ui_file(self):
347
+ """
348
+ Open a file dialog to select a custom UI file and launch it.
349
+ """
350
+ ui_file, _ = QFileDialog.getOpenFileName(
351
+ self, "Select UI File", "", "UI Files (*.ui);;All Files (*)"
352
+ )
353
+ self.launch("custom_ui_file", ui_file=ui_file)
354
+
355
+ @staticmethod
356
+ def _update_available_auto_updates() -> dict[str, type[AutoUpdates]]:
357
+ """
358
+ Load all available auto updates from the plugin repository.
359
+ """
360
+ try:
361
+ auto_updates = get_plugin_auto_updates()
362
+ logger.info(f"Available auto updates: {auto_updates.keys()}")
363
+ except Exception as exc:
364
+ logger.error(f"Failed to load auto updates: {exc}")
365
+ return {}
366
+ return auto_updates
367
+
368
+ def show_launcher(self):
369
+ """
370
+ Show the launcher window.
371
+ """
372
+ self.show()
373
+
374
+ def hide_launcher(self):
375
+ """
376
+ Hide the launcher window.
377
+ """
378
+ self.hide()
379
+
380
+ def showEvent(self, event):
381
+ super().showEvent(event)
382
+ self.setFixedSize(self.size())
383
+
384
+ def _launcher_is_last_widget(self, connections: dict) -> bool:
385
+ """
386
+ Check if the launcher is the last widget in the application.
387
+ """
388
+
389
+ remaining_connections = [
390
+ connection for connection in connections.values() if connection.parent_id != self.gui_id
391
+ ]
392
+ return len(remaining_connections) <= 1
393
+
394
+ def _turn_off_the_lights(self, connections: dict):
395
+ """
396
+ If there is only one connection remaining, it is the launcher, so we show it.
397
+ Once the launcher is closed as the last window, we quit the application.
398
+ """
399
+ if self._launcher_is_last_widget(connections):
400
+ self.show()
401
+ self.activateWindow()
402
+ self.raise_()
403
+ if self.app:
404
+ self.app.setQuitOnLastWindowClosed(True) # type: ignore
405
+ return
406
+
407
+ self.hide()
408
+ if self.app:
409
+ self.app.setQuitOnLastWindowClosed(False) # type: ignore
410
+
411
+ def closeEvent(self, event):
412
+ """
413
+ Close the launcher window.
414
+ """
415
+ connections = self.register.list_all_connections()
416
+ if self._launcher_is_last_widget(connections):
417
+ event.accept()
418
+ return
419
+
420
+ event.ignore()
421
+ self.hide()
422
+
423
+
424
+ if __name__ == "__main__":
425
+ import sys
426
+
427
+ app = QApplication(sys.argv)
428
+ launcher = LaunchWindow()
429
+ launcher.show()
430
+ sys.exit(app.exec())
@@ -1 +0,0 @@
1
- from .client import *