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
@@ -13,15 +13,23 @@ from qtpy.QtWidgets import (
13
13
  QWidget,
14
14
  )
15
15
 
16
- from bec_widgets.qt_utils.error_popups import SafeSlot
16
+ from bec_widgets.utils.error_popups import SafeSlot
17
17
 
18
18
 
19
- class AdditionalMetadataTableModel(QAbstractTableModel):
19
+ class DictBackedTableModel(QAbstractTableModel):
20
20
  def __init__(self, data):
21
+ """A model to go with DictBackedTable, which represents key-value pairs
22
+ to be displayed in a TreeWidget.
23
+
24
+ Args:
25
+ data (list[list[str]]): list of key-value pairs to initialise with"""
21
26
  super().__init__()
22
27
  self._data: list[list[str]] = data
23
28
  self._disallowed_keys: list[str] = []
24
29
 
30
+ # pylint: disable=missing-function-docstring
31
+ # see QAbstractTableModel documentation for these methods
32
+
25
33
  def headerData(
26
34
  self, section: int, orientation: Qt.Orientation, role: int = Qt.ItemDataRole()
27
35
  ) -> Any:
@@ -49,6 +57,10 @@ class AdditionalMetadataTableModel(QAbstractTableModel):
49
57
  return False
50
58
 
51
59
  def update_disallowed_keys(self, keys: list[str]):
60
+ """Set the list of keys which may not be used.
61
+
62
+ Args:
63
+ keys (list[str]): list of keys which are forbidden."""
52
64
  self._disallowed_keys = keys
53
65
  for i, item in enumerate(self._data):
54
66
  if item[0] in self._disallowed_keys:
@@ -95,16 +107,21 @@ class AdditionalMetadataTableModel(QAbstractTableModel):
95
107
  return dict(self._data)
96
108
 
97
109
 
98
- class AdditionalMetadataTable(QWidget):
99
-
110
+ class DictBackedTable(QWidget):
100
111
  delete_rows = Signal(list)
101
112
 
102
113
  def __init__(self, initial_data: list[list[str]]):
114
+ """Widget which uses a DictBackedTableModel to display an editable table
115
+ which can be extracted as a dict.
116
+
117
+ Args:
118
+ initial_data (list[list[str]]): list of key-value pairs to initialise with
119
+ """
103
120
  super().__init__()
104
121
 
105
122
  self._layout = QHBoxLayout()
106
123
  self.setLayout(self._layout)
107
- self._table_model = AdditionalMetadataTableModel(initial_data)
124
+ self._table_model = DictBackedTableModel(initial_data)
108
125
  self._table_view = QTreeView()
109
126
  self._table_view.setModel(self._table_model)
110
127
  self._table_view.setSizePolicy(
@@ -126,15 +143,21 @@ class AdditionalMetadataTable(QWidget):
126
143
  self.delete_rows.connect(self._table_model.delete_rows)
127
144
 
128
145
  def delete_selected_rows(self):
146
+ """Delete rows which are part of the selection model"""
129
147
  cells: list[QModelIndex] = self._table_view.selectionModel().selectedIndexes()
130
148
  row_indices = list({r.row() for r in cells})
131
149
  if row_indices:
132
150
  self.delete_rows.emit(row_indices)
133
151
 
134
152
  def dump_dict(self):
153
+ """Get the current content of the table as a dict"""
135
154
  return self._table_model.dump_dict()
136
155
 
137
156
  def update_disallowed_keys(self, keys: list[str]):
157
+ """Set the list of keys which may not be used.
158
+
159
+ Args:
160
+ keys (list[str]): list of keys which are forbidden."""
138
161
  self._table_model.update_disallowed_keys(keys)
139
162
 
140
163
 
@@ -144,6 +167,6 @@ if __name__ == "__main__": # pragma: no cover
144
167
  app = QApplication([])
145
168
  set_theme("dark")
146
169
 
147
- window = AdditionalMetadataTable([["key1", "value1"], ["key2", "value2"], ["key3", "value3"]])
170
+ window = DictBackedTable([["key1", "value1"], ["key2", "value2"], ["key3", "value3"]])
148
171
  window.show()
149
172
  app.exec()
@@ -1,7 +0,0 @@
1
- from bec_widgets.widgets.editors.scan_metadata.additional_metadata_table import (
2
- AdditionalMetadataTable,
3
- AdditionalMetadataTableModel,
4
- )
5
- from bec_widgets.widgets.editors.scan_metadata.scan_metadata import ScanMetadata
6
-
7
- __all__ = ["ScanMetadata", "AdditionalMetadataTable", "AdditionalMetadataTableModel"]
@@ -9,7 +9,7 @@ from annotated_types import Ge, Gt, Le, Lt
9
9
  from bec_lib.logger import bec_logger
10
10
  from pydantic_core import PydanticUndefined
11
11
 
12
- if TYPE_CHECKING:
12
+ if TYPE_CHECKING: # pragma: no cover
13
13
  from pydantic.fields import FieldInfo
14
14
 
15
15
  logger = bec_logger.logger
@@ -6,11 +6,9 @@ def main(): # pragma: no cover
6
6
  return
7
7
  from PySide6.QtDesigner import QPyDesignerCustomWidgetCollection
8
8
 
9
- from bec_widgets.widgets.plots.motor_map.bec_motor_map_widget_plugin import (
10
- BECMotorMapWidgetPlugin,
11
- )
9
+ from bec_widgets.widgets.editors.scan_metadata.scan_metadata_plugin import ScanMetadataPlugin
12
10
 
13
- QPyDesignerCustomWidgetCollection.addCustomWidget(BECMotorMapWidgetPlugin())
11
+ QPyDesignerCustomWidgetCollection.addCustomWidget(ScanMetadataPlugin())
14
12
 
15
13
 
16
14
  if __name__ == "__main__": # pragma: no cover
@@ -1,48 +1,21 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from decimal import Decimal
4
- from types import NoneType
5
- from typing import TYPE_CHECKING
6
4
 
7
5
  from bec_lib.logger import bec_logger
8
6
  from bec_lib.metadata_schema import get_metadata_schema_for_scan
9
- from bec_qthemes import material_icon
10
- from pydantic import Field, ValidationError
11
- from qtpy.QtCore import Signal # type: ignore
12
- from qtpy.QtWidgets import (
13
- QApplication,
14
- QComboBox,
15
- QGridLayout,
16
- QHBoxLayout,
17
- QLabel,
18
- QLayout,
19
- QVBoxLayout,
20
- QWidget,
21
- )
22
-
23
- from bec_widgets.qt_utils.compact_popup import CompactPopupWidget
24
- from bec_widgets.qt_utils.error_popups import SafeProperty, SafeSlot
25
- from bec_widgets.qt_utils.expandable_frame import ExpandableGroupFrame
26
- from bec_widgets.utils.bec_widget import BECWidget
27
- from bec_widgets.widgets.editors.scan_metadata._metadata_widgets import widget_from_type
28
- from bec_widgets.widgets.editors.scan_metadata.additional_metadata_table import (
29
- AdditionalMetadataTable,
30
- )
31
-
32
- if TYPE_CHECKING:
33
- from pydantic.fields import FieldInfo
34
-
35
- logger = bec_logger.logger
7
+ from pydantic import Field
8
+ from qtpy.QtWidgets import QApplication, QComboBox, QHBoxLayout, QVBoxLayout, QWidget
36
9
 
10
+ from bec_widgets.utils.error_popups import SafeProperty, SafeSlot
11
+ from bec_widgets.utils.expandable_frame import ExpandableGroupFrame
12
+ from bec_widgets.utils.forms_from_types.forms import PydanticModelForm
13
+ from bec_widgets.widgets.editors.dict_backed_table import DictBackedTable
37
14
 
38
- class ScanMetadata(BECWidget, QWidget):
39
- """Dynamically generates a form for inclusion of metadata for a scan. Uses the
40
- metadata schema registry supplied in the plugin repo to find pydantic models
41
- associated with the scan type. Sets limits for numerical values if specified."""
15
+ logger = bec_logger.logger
42
16
 
43
- metadata_updated = Signal(dict)
44
- metadata_cleared = Signal(NoneType)
45
17
 
18
+ class ScanMetadata(PydanticModelForm):
46
19
  def __init__(
47
20
  self,
48
21
  parent=None,
@@ -51,119 +24,36 @@ class ScanMetadata(BECWidget, QWidget):
51
24
  initial_extras: list[list[str]] | None = None,
52
25
  **kwargs,
53
26
  ):
54
- super().__init__(client=client, **kwargs)
55
- QWidget.__init__(self, parent=parent)
56
-
57
- self.set_schema(scan_name)
58
-
59
- self._layout = QVBoxLayout()
60
- self._layout.setContentsMargins(0, 0, 0, 0)
61
- self.setLayout(self._layout)
27
+ """Dynamically generates a form for inclusion of metadata for a scan. Uses the
28
+ metadata schema registry supplied in the plugin repo to find pydantic models
29
+ associated with the scan type. Sets limits for numerical values if specified.
62
30
 
63
- self._required_md_box = ExpandableGroupFrame("Scan schema metadata")
64
- self._layout.addWidget(self._required_md_box)
65
- self._required_md_box_layout = QHBoxLayout()
66
- self._required_md_box.set_layout(self._required_md_box_layout)
67
-
68
- self._md_grid = QWidget()
69
- self._required_md_box_layout.addWidget(self._md_grid)
70
- self._grid_container = QVBoxLayout()
71
- self._md_grid.setLayout(self._grid_container)
72
- self._new_grid_layout()
73
- self._grid_container.addLayout(self._md_grid_layout)
31
+ Args:
32
+ scan_name (str): The scan for which to generate a metadata form
33
+ Initial_extras (list[list[str]]): Initial data with which to populate the additional
34
+ metadata table - inner lists should be key-value pairs
35
+ """
74
36
 
37
+ # self.populate() gets called in super().__init__
38
+ # so make sure self._additional_metadata exists
75
39
  self._additional_md_box = ExpandableGroupFrame("Additional metadata", expanded=False)
76
- self._layout.addWidget(self._additional_md_box)
77
40
  self._additional_md_box_layout = QHBoxLayout()
78
41
  self._additional_md_box.set_layout(self._additional_md_box_layout)
79
42
 
80
- self._additional_metadata = AdditionalMetadataTable(initial_extras or [])
81
- self._additional_md_box_layout.addWidget(self._additional_metadata)
43
+ self._additional_metadata = DictBackedTable(initial_extras or [])
44
+ self._scan_name = scan_name or ""
45
+ self._md_schema = get_metadata_schema_for_scan(self._scan_name)
82
46
 
83
- self._validity = CompactPopupWidget()
84
- self._validity.compact_view = True # type: ignore
85
- self._validity.label = "Metadata validity" # type: ignore
86
- self._validity.compact_show_popup.setIcon(
87
- material_icon(icon_name="info", size=(10, 10), convert_to_pixmap=False)
88
- )
89
- self._validity_message = QLabel("Not yet validated")
90
- self._validity.addWidget(self._validity_message)
91
- self._layout.addWidget(self._validity)
47
+ super().__init__(parent=parent, metadata_model=self._md_schema, client=client, **kwargs)
92
48
 
93
- self.populate()
49
+ self._layout.addWidget(self._additional_md_box)
50
+ self._additional_md_box_layout.addWidget(self._additional_metadata)
94
51
 
95
52
  @SafeSlot(str)
96
53
  def update_with_new_scan(self, scan_name: str):
97
- self.set_schema(scan_name)
98
- self.populate()
54
+ self.set_schema_from_scan(scan_name)
99
55
  self.validate_form()
100
56
 
101
- def validate_form(self, *_) -> bool:
102
- """validate the currently entered metadata against the pydantic schema.
103
- If successful, returns on metadata_emitted and returns true.
104
- Otherwise, emits on metadata_cleared and returns false."""
105
- try:
106
- metadata_dict = self.get_full_model_dict()
107
- self._md_schema.model_validate(metadata_dict)
108
- self._validity.set_global_state("success")
109
- self._validity_message.setText("No errors!")
110
- self.metadata_updated.emit(metadata_dict)
111
- except ValidationError as e:
112
- self._validity.set_global_state("emergency")
113
- self._validity_message.setText(str(e))
114
- self.metadata_cleared.emit(None)
115
-
116
- def get_full_model_dict(self):
117
- """Get the entered metadata as a dict"""
118
- return self._additional_metadata.dump_dict() | self._dict_from_grid()
119
-
120
- def set_schema(self, scan_name: str | None = None):
121
- self._scan_name = scan_name or ""
122
- self._md_schema = get_metadata_schema_for_scan(self._scan_name)
123
-
124
- def populate(self):
125
- self._clear_grid()
126
- self._populate()
127
-
128
- def _populate(self):
129
- self._additional_metadata.update_disallowed_keys(list(self._md_schema.model_fields.keys()))
130
- for i, (field_name, info) in enumerate(self._md_schema.model_fields.items()):
131
- self._add_griditem(field_name, info, i)
132
-
133
- def _add_griditem(self, field_name: str, info: FieldInfo, row: int):
134
- grid = self._md_grid_layout
135
- label = QLabel(info.title or field_name)
136
- label.setProperty("_model_field_name", field_name)
137
- label.setToolTip(info.description or field_name)
138
- grid.addWidget(label, row, 0)
139
- widget = widget_from_type(info.annotation)(info)
140
- widget.valueChanged.connect(self.validate_form)
141
- grid.addWidget(widget, row, 1)
142
-
143
- def _dict_from_grid(self) -> dict[str, str | int | float | Decimal | bool]:
144
- grid = self._md_grid_layout
145
- return {
146
- grid.itemAtPosition(i, 0).widget().property("_model_field_name"): grid.itemAtPosition(i, 1).widget().getValue() # type: ignore # we only add 'MetadataWidget's here
147
- for i in range(grid.rowCount())
148
- }
149
-
150
- def _clear_grid(self):
151
- while self._md_grid_layout.count():
152
- item = self._md_grid_layout.takeAt(0)
153
- widget = item.widget()
154
- if widget is not None:
155
- widget.deleteLater()
156
- self._md_grid_layout.deleteLater()
157
- self._new_grid_layout()
158
- self._grid_container.addLayout(self._md_grid_layout)
159
- self._md_grid.adjustSize()
160
- self.adjustSize()
161
-
162
- def _new_grid_layout(self):
163
- self._md_grid_layout = QGridLayout()
164
- self._md_grid_layout.setContentsMargins(0, 0, 0, 0)
165
- self._md_grid_layout.setSizeConstraint(QLayout.SizeConstraint.SetFixedSize)
166
-
167
57
  @SafeProperty(bool)
168
58
  def hide_optional_metadata(self): # type: ignore
169
59
  """Property to hide the optional metadata table."""
@@ -178,8 +68,25 @@ class ScanMetadata(BECWidget, QWidget):
178
68
  """
179
69
  self._additional_md_box.setVisible(not hide)
180
70
 
71
+ def get_form_data(self):
72
+ """Get the entered metadata as a dict"""
73
+ return self._additional_metadata.dump_dict() | self._dict_from_grid()
74
+
75
+ def populate(self):
76
+ self._additional_metadata.update_disallowed_keys(list(self._md_schema.model_fields.keys()))
77
+ super().populate()
78
+
79
+ def set_schema_from_scan(self, scan_name: str | None):
80
+ self._scan_name = scan_name or ""
81
+ self.set_schema(get_metadata_schema_for_scan(self._scan_name))
82
+ self.populate()
83
+
181
84
 
182
85
  if __name__ == "__main__": # pragma: no cover
86
+ # pylint: disable=redefined-outer-name
87
+ # pylint: disable=protected-access
88
+ # pylint: disable=disallowed-name
89
+
183
90
  from unittest.mock import patch
184
91
 
185
92
  from bec_lib.metadata_schema import BasicScanMetadata
@@ -210,7 +117,6 @@ if __name__ == "__main__": # pragma: no cover
210
117
  "bec_lib.metadata_schema._get_metadata_schema_registry",
211
118
  lambda: {"scan1": ExampleSchema1, "scan2": ExampleSchema2, "scan3": ExampleSchema3},
212
119
  ):
213
-
214
120
  app = QApplication([])
215
121
  w = QWidget()
216
122
  selection = QComboBox()
@@ -0,0 +1 @@
1
+ {'files': ['scan_metadata.py']}
@@ -4,36 +4,36 @@
4
4
  from qtpy.QtDesigner import QDesignerCustomWidgetInterface
5
5
 
6
6
  from bec_widgets.utils.bec_designer import designer_material_icon
7
- from bec_widgets.widgets.plots.multi_waveform.multi_waveform_widget import BECMultiWaveformWidget
7
+ from bec_widgets.widgets.editors.scan_metadata.scan_metadata import ScanMetadata
8
8
 
9
9
  DOM_XML = """
10
10
  <ui language='c++'>
11
- <widget class='BECMultiWaveformWidget' name='bec_multi_waveform_widget'>
11
+ <widget class='ScanMetadata' name='scan_metadata'>
12
12
  </widget>
13
13
  </ui>
14
14
  """
15
15
 
16
16
 
17
- class BECMultiWaveformWidgetPlugin(QDesignerCustomWidgetInterface): # pragma: no cover
17
+ class ScanMetadataPlugin(QDesignerCustomWidgetInterface): # pragma: no cover
18
18
  def __init__(self):
19
19
  super().__init__()
20
20
  self._form_editor = None
21
21
 
22
22
  def createWidget(self, parent):
23
- t = BECMultiWaveformWidget(parent)
23
+ t = ScanMetadata(parent)
24
24
  return t
25
25
 
26
26
  def domXml(self):
27
27
  return DOM_XML
28
28
 
29
29
  def group(self):
30
- return "BEC Plots"
30
+ return ""
31
31
 
32
32
  def icon(self):
33
- return designer_material_icon(BECMultiWaveformWidget.ICON_NAME)
33
+ return designer_material_icon(ScanMetadata.ICON_NAME)
34
34
 
35
35
  def includeFile(self):
36
- return "bec_multi_waveform_widget"
36
+ return "scan_metadata"
37
37
 
38
38
  def initialize(self, form_editor):
39
39
  self._form_editor = form_editor
@@ -45,10 +45,10 @@ class BECMultiWaveformWidgetPlugin(QDesignerCustomWidgetInterface): # pragma: n
45
45
  return self._form_editor is not None
46
46
 
47
47
  def name(self):
48
- return "BECMultiWaveformWidget"
48
+ return "ScanMetadata"
49
49
 
50
50
  def toolTip(self):
51
- return "BECMultiWaveformWidget"
51
+ return "Dynamically generates a form for inclusion of metadata for a scan."
52
52
 
53
53
  def whatsThis(self):
54
54
  return self.toolTip()
@@ -7,9 +7,9 @@ from bec_lib.logger import bec_logger
7
7
  from pydantic import Field
8
8
  from qtpy.QtWidgets import QTextEdit, QVBoxLayout, QWidget
9
9
 
10
- from bec_widgets.qt_utils.error_popups import SafeProperty, SafeSlot
11
10
  from bec_widgets.utils.bec_connector import ConnectionConfig
12
11
  from bec_widgets.utils.bec_widget import BECWidget
12
+ from bec_widgets.utils.error_popups import SafeProperty, SafeSlot
13
13
 
14
14
  logger = bec_logger.logger
15
15
 
@@ -49,8 +49,7 @@ class TextBox(BECWidget, QWidget):
49
49
  if isinstance(config, dict):
50
50
  config = TextBoxConfig(**config)
51
51
  self.config = config
52
- super().__init__(client=client, config=config, gui_id=gui_id, **kwargs)
53
- QWidget.__init__(self, parent)
52
+ super().__init__(parent=parent, client=client, gui_id=gui_id, config=config, **kwargs)
54
53
  self.layout = QVBoxLayout(self)
55
54
  self.text_box_text_edit = QTextEdit(parent=self)
56
55
  self.layout.addWidget(self.text_box_text_edit)
@@ -26,8 +26,7 @@ class WebsiteWidget(BECWidget, QWidget):
26
26
  def __init__(
27
27
  self, parent=None, url: str = None, config=None, client=None, gui_id=None, **kwargs
28
28
  ):
29
- super().__init__(client=client, config=config, gui_id=gui_id, **kwargs)
30
- QWidget.__init__(self, parent=parent)
29
+ super().__init__(parent=parent, client=client, gui_id=gui_id, config=config, **kwargs)
31
30
  layout = QVBoxLayout()
32
31
  layout.setContentsMargins(0, 0, 0, 0)
33
32
  self.website = QWebEngineView()
@@ -118,6 +117,7 @@ class WebsiteWidget(BECWidget, QWidget):
118
117
  Cleanup the widget
119
118
  """
120
119
  self.website.page().deleteLater()
120
+ super().cleanup()
121
121
 
122
122
 
123
123
  if __name__ == "__main__":
@@ -144,10 +144,10 @@ class Minesweeper(BECWidget, QWidget):
144
144
  PLUGIN = True
145
145
  ICON_NAME = "videogame_asset"
146
146
  USER_ACCESS = []
147
+ RPC = True
147
148
 
148
149
  def __init__(self, parent=None, *args, **kwargs):
149
- super().__init__(*args, **kwargs)
150
- QWidget.__init__(self, parent=parent)
150
+ super().__init__(parent=parent, *args, **kwargs)
151
151
 
152
152
  self._ui_initialised = False
153
153
  self._timer_start_num_seconds = 0
@@ -403,6 +403,7 @@ class Minesweeper(BECWidget, QWidget):
403
403
 
404
404
  def cleanup(self):
405
405
  self._timer.stop()
406
+ super().cleanup()
406
407
 
407
408
 
408
409
  if __name__ == "__main__":