bec-widgets 2.3.0__py3-none-any.whl → 2.5.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 (46) hide show
  1. .github/ISSUE_TEMPLATE/bug_report.md +26 -0
  2. .github/ISSUE_TEMPLATE/feature_request.md +48 -0
  3. .github/workflows/check_pr.yml +28 -0
  4. .github/workflows/ci.yml +36 -0
  5. .github/workflows/end2end-conda.yml +48 -0
  6. .github/workflows/formatter.yml +61 -0
  7. .github/workflows/generate-cli-check.yml +49 -0
  8. .github/workflows/pytest-matrix.yml +49 -0
  9. .github/workflows/pytest.yml +65 -0
  10. .github/workflows/semantic_release.yml +103 -0
  11. CHANGELOG.md +1726 -1546
  12. LICENSE +1 -1
  13. PKG-INFO +2 -1
  14. README.md +11 -0
  15. bec_widgets/cli/client.py +346 -0
  16. bec_widgets/examples/jupyter_console/jupyter_console_window.py +8 -8
  17. bec_widgets/tests/utils.py +3 -3
  18. bec_widgets/utils/entry_validator.py +13 -3
  19. bec_widgets/utils/side_panel.py +65 -39
  20. bec_widgets/utils/toolbar.py +79 -0
  21. bec_widgets/widgets/containers/layout_manager/layout_manager.py +34 -1
  22. bec_widgets/widgets/control/device_input/base_classes/device_input_base.py +1 -1
  23. bec_widgets/widgets/control/device_input/base_classes/device_signal_input_base.py +27 -31
  24. bec_widgets/widgets/control/device_input/device_combobox/device_combobox.py +1 -1
  25. bec_widgets/widgets/control/device_input/device_line_edit/device_line_edit.py +1 -1
  26. bec_widgets/widgets/editors/dict_backed_table.py +7 -0
  27. bec_widgets/widgets/editors/scan_metadata/scan_metadata.py +1 -0
  28. bec_widgets/widgets/editors/web_console/register_web_console.py +15 -0
  29. bec_widgets/widgets/editors/web_console/web_console.py +230 -0
  30. bec_widgets/widgets/editors/web_console/web_console.pyproject +1 -0
  31. bec_widgets/widgets/editors/web_console/web_console_plugin.py +54 -0
  32. bec_widgets/widgets/plots/image/image.py +90 -0
  33. bec_widgets/widgets/plots/roi/__init__.py +0 -0
  34. bec_widgets/widgets/plots/roi/image_roi.py +867 -0
  35. bec_widgets/widgets/plots/waveform/settings/curve_settings/curve_tree.py +11 -46
  36. bec_widgets/widgets/utility/visual/color_button_native/__init__.py +0 -0
  37. bec_widgets/widgets/utility/visual/color_button_native/color_button_native.py +58 -0
  38. bec_widgets/widgets/utility/visual/color_button_native/color_button_native.pyproject +1 -0
  39. bec_widgets/widgets/utility/visual/color_button_native/color_button_native_plugin.py +56 -0
  40. bec_widgets/widgets/utility/visual/color_button_native/register_color_button_native.py +17 -0
  41. {bec_widgets-2.3.0.dist-info → bec_widgets-2.5.0.dist-info}/METADATA +2 -1
  42. {bec_widgets-2.3.0.dist-info → bec_widgets-2.5.0.dist-info}/RECORD +46 -25
  43. {bec_widgets-2.3.0.dist-info → bec_widgets-2.5.0.dist-info}/licenses/LICENSE +1 -1
  44. pyproject.toml +17 -5
  45. {bec_widgets-2.3.0.dist-info → bec_widgets-2.5.0.dist-info}/WHEEL +0 -0
  46. {bec_widgets-2.3.0.dist-info → bec_widgets-2.5.0.dist-info}/entry_points.txt +0 -0
@@ -31,6 +31,7 @@ class SidePanel(QWidget):
31
31
  panel_max_width: int = 200,
32
32
  animation_duration: int = 200,
33
33
  animations_enabled: bool = True,
34
+ show_toolbar: bool = True,
34
35
  ):
35
36
  super().__init__(parent=parent)
36
37
 
@@ -40,6 +41,7 @@ class SidePanel(QWidget):
40
41
  self._panel_max_width = panel_max_width
41
42
  self._animation_duration = animation_duration
42
43
  self._animations_enabled = animations_enabled
44
+ self._show_toolbar = show_toolbar
43
45
 
44
46
  self._panel_width = 0
45
47
  self._panel_height = 0
@@ -71,13 +73,14 @@ class SidePanel(QWidget):
71
73
  self.stack_widget.setMinimumWidth(5)
72
74
  self.stack_widget.setMaximumWidth(self._panel_max_width)
73
75
 
74
- if self._orientation == "left":
75
- self.main_layout.addWidget(self.toolbar)
76
- self.main_layout.addWidget(self.container)
77
- else:
78
- self.main_layout.addWidget(self.container)
79
- self.main_layout.addWidget(self.toolbar)
76
+ if self._orientation in ("left", "right"):
77
+ if self._show_toolbar:
78
+ self.main_layout.addWidget(self.toolbar)
80
79
 
80
+ if self._orientation == "left":
81
+ self.main_layout.addWidget(self.container)
82
+ else:
83
+ self.main_layout.insertWidget(0, self.container)
81
84
  self.container.layout.addWidget(self.stack_widget)
82
85
 
83
86
  self.menu_anim = QPropertyAnimation(self, b"panel_width")
@@ -102,11 +105,13 @@ class SidePanel(QWidget):
102
105
  self.stack_widget.setMaximumHeight(self._panel_max_width)
103
106
 
104
107
  if self._orientation == "top":
105
- self.main_layout.addWidget(self.toolbar)
108
+ if self._show_toolbar:
109
+ self.main_layout.addWidget(self.toolbar)
106
110
  self.main_layout.addWidget(self.container)
107
111
  else:
108
112
  self.main_layout.addWidget(self.container)
109
- self.main_layout.addWidget(self.toolbar)
113
+ if self._show_toolbar:
114
+ self.main_layout.addWidget(self.toolbar)
110
115
 
111
116
  self.container.layout.addWidget(self.stack_widget)
112
117
 
@@ -233,21 +238,24 @@ class SidePanel(QWidget):
233
238
 
234
239
  def add_menu(
235
240
  self,
236
- action_id: str,
237
- icon_name: str,
238
- tooltip: str,
239
241
  widget: QWidget,
242
+ action_id: str | None = None,
243
+ icon_name: str | None = None,
244
+ tooltip: str | None = None,
240
245
  title: str | None = None,
241
- ):
246
+ ) -> int:
242
247
  """
243
248
  Add a menu to the side panel.
244
249
 
245
250
  Args:
246
- action_id(str): The ID of the action.
247
- icon_name(str): The name of the icon.
248
- tooltip(str): The tooltip for the action.
249
251
  widget(QWidget): The widget to add to the panel.
250
- title(str): The title of the panel.
252
+ action_id(str | None): The ID of the action. Optional if no toolbar action is needed.
253
+ icon_name(str | None): The name of the icon. Optional if no toolbar action is needed.
254
+ tooltip(str | None): The tooltip for the action. Optional if no toolbar action is needed.
255
+ title(str | None): The title of the panel.
256
+
257
+ Returns:
258
+ int: The index of the added panel, which can be used with show_panel() and switch_to().
251
259
  """
252
260
  # container_widget: top-level container for the stacked page
253
261
  container_widget = QWidget()
@@ -278,32 +286,35 @@ class SidePanel(QWidget):
278
286
  index = self.stack_widget.count()
279
287
  self.stack_widget.addWidget(container_widget)
280
288
 
281
- # Add an action to the toolbar
282
- action = MaterialIconAction(icon_name=icon_name, tooltip=tooltip, checkable=True)
283
- self.toolbar.add_action(action_id, action, target_widget=self)
289
+ # Add an action to the toolbar if action_id, icon_name, and tooltip are provided
290
+ if action_id is not None and icon_name is not None and tooltip is not None:
291
+ action = MaterialIconAction(icon_name=icon_name, tooltip=tooltip, checkable=True)
292
+ self.toolbar.add_action(action_id, action, target_widget=self)
284
293
 
285
- def on_action_toggled(checked: bool):
286
- if self.switching_actions:
287
- return
294
+ def on_action_toggled(checked: bool):
295
+ if self.switching_actions:
296
+ return
288
297
 
289
- if checked:
290
- if self.current_action and self.current_action != action.action:
291
- self.switching_actions = True
292
- self.current_action.setChecked(False)
293
- self.switching_actions = False
298
+ if checked:
299
+ if self.current_action and self.current_action != action.action:
300
+ self.switching_actions = True
301
+ self.current_action.setChecked(False)
302
+ self.switching_actions = False
294
303
 
295
- self.current_action = action.action
304
+ self.current_action = action.action
296
305
 
297
- if not self.panel_visible:
298
- self.show_panel(index)
306
+ if not self.panel_visible:
307
+ self.show_panel(index)
308
+ else:
309
+ self.switch_to(index)
299
310
  else:
300
- self.switch_to(index)
301
- else:
302
- if self.current_action == action.action:
303
- self.current_action = None
304
- self.hide_panel()
311
+ if self.current_action == action.action:
312
+ self.current_action = None
313
+ self.hide_panel()
314
+
315
+ action.action.toggled.connect(on_action_toggled)
305
316
 
306
- action.action.toggled.connect(on_action_toggled)
317
+ return index
307
318
 
308
319
 
309
320
  ############################################
@@ -332,41 +343,56 @@ class ExampleApp(QMainWindow): # pragma: no cover
332
343
  self.add_side_menus()
333
344
 
334
345
  def add_side_menus(self):
346
+ # Example 1: With action, icon, and tooltip
335
347
  widget1 = QWidget()
336
348
  layout1 = QVBoxLayout(widget1)
337
349
  for i in range(15):
338
350
  layout1.addWidget(QLabel(f"Widget 1 label row {i}"))
339
351
  self.side_panel.add_menu(
352
+ widget=widget1,
340
353
  action_id="widget1",
341
354
  icon_name="counter_1",
342
355
  tooltip="Show Widget 1",
343
- widget=widget1,
344
356
  title="Widget 1 Panel",
345
357
  )
346
358
 
359
+ # Example 2: With action, icon, and tooltip
347
360
  widget2 = QWidget()
348
361
  layout2 = QVBoxLayout(widget2)
349
362
  layout2.addWidget(QLabel("Short widget 2 content"))
350
363
  self.side_panel.add_menu(
364
+ widget=widget2,
351
365
  action_id="widget2",
352
366
  icon_name="counter_2",
353
367
  tooltip="Show Widget 2",
354
- widget=widget2,
355
368
  title="Widget 2 Panel",
356
369
  )
357
370
 
371
+ # Example 3: With action, icon, and tooltip
358
372
  widget3 = QWidget()
359
373
  layout3 = QVBoxLayout(widget3)
360
374
  for i in range(10):
361
375
  layout3.addWidget(QLabel(f"Line {i} for Widget 3"))
362
376
  self.side_panel.add_menu(
377
+ widget=widget3,
363
378
  action_id="widget3",
364
379
  icon_name="counter_3",
365
380
  tooltip="Show Widget 3",
366
- widget=widget3,
367
381
  title="Widget 3 Panel",
368
382
  )
369
383
 
384
+ # Example 4: Without action, icon, and tooltip (can only be shown programmatically)
385
+ widget4 = QWidget()
386
+ layout4 = QVBoxLayout(widget4)
387
+ layout4.addWidget(QLabel("This panel has no toolbar button"))
388
+ layout4.addWidget(QLabel("It can only be shown programmatically"))
389
+ self.hidden_panel_index = self.side_panel.add_menu(widget=widget4, title="Hidden Panel")
390
+
391
+ # Example of how to show the hidden panel programmatically after 3 seconds
392
+ from qtpy.QtCore import QTimer
393
+
394
+ QTimer.singleShot(3000, lambda: self.side_panel.show_panel(self.hidden_panel_index))
395
+
370
396
 
371
397
  if __name__ == "__main__": # pragma: no cover
372
398
  app = QApplication(sys.argv)
@@ -702,6 +702,85 @@ class ModularToolBar(QToolBar):
702
702
  self.bundles[bundle_id].append(action_id)
703
703
  self.update_separators()
704
704
 
705
+ def remove_action(self, action_id: str):
706
+ """
707
+ Completely remove a single action from the toolbar.
708
+
709
+ The method takes care of both standalone actions and actions that are
710
+ part of an existing bundle.
711
+
712
+ Args:
713
+ action_id (str): Unique identifier for the action.
714
+ """
715
+ if action_id not in self.widgets:
716
+ raise ValueError(f"Action with ID '{action_id}' does not exist.")
717
+
718
+ # Identify potential bundle membership
719
+ parent_bundle = None
720
+ for b_id, a_ids in self.bundles.items():
721
+ if action_id in a_ids:
722
+ parent_bundle = b_id
723
+ break
724
+
725
+ # 1. Remove the QAction from the QToolBar and delete it
726
+ tool_action = self.widgets.pop(action_id)
727
+ if hasattr(tool_action, "action") and tool_action.action is not None:
728
+ self.removeAction(tool_action.action)
729
+ tool_action.action.deleteLater()
730
+
731
+ # 2. Clean bundle bookkeeping if the action belonged to one
732
+ if parent_bundle:
733
+ self.bundles[parent_bundle].remove(action_id)
734
+ # If the bundle becomes empty, get rid of the bundle entry as well
735
+ if not self.bundles[parent_bundle]:
736
+ self.remove_bundle(parent_bundle)
737
+
738
+ # 3. Remove from the ordering list
739
+ self.toolbar_items = [
740
+ item
741
+ for item in self.toolbar_items
742
+ if not (item[0] == "action" and item[1] == action_id)
743
+ ]
744
+
745
+ self.update_separators()
746
+
747
+ def remove_bundle(self, bundle_id: str):
748
+ """
749
+ Remove an entire bundle (and all of its actions) from the toolbar.
750
+
751
+ Args:
752
+ bundle_id (str): Unique identifier for the bundle.
753
+ """
754
+ if bundle_id not in self.bundles:
755
+ raise ValueError(f"Bundle '{bundle_id}' does not exist.")
756
+
757
+ # Remove every action belonging to this bundle
758
+ for action_id in list(self.bundles[bundle_id]): # copy the list
759
+ if action_id in self.widgets:
760
+ tool_action = self.widgets.pop(action_id)
761
+ if hasattr(tool_action, "action") and tool_action.action is not None:
762
+ self.removeAction(tool_action.action)
763
+ tool_action.action.deleteLater()
764
+
765
+ # Drop the bundle entry
766
+ self.bundles.pop(bundle_id, None)
767
+
768
+ # Remove bundle entry and its preceding separator (if any) from the ordering list
769
+ cleaned_items = []
770
+ skip_next_separator = False
771
+ for item_type, ident in self.toolbar_items:
772
+ if item_type == "bundle" and ident == bundle_id:
773
+ # mark to skip one following separator if present
774
+ skip_next_separator = True
775
+ continue
776
+ if skip_next_separator and item_type == "separator":
777
+ skip_next_separator = False
778
+ continue
779
+ cleaned_items.append((item_type, ident))
780
+ self.toolbar_items = cleaned_items
781
+
782
+ self.update_separators()
783
+
705
784
  def contextMenuEvent(self, event):
706
785
  """
707
786
  Overrides the context menu event to show toolbar actions with checkboxes and icons.
@@ -53,7 +53,7 @@ class LayoutManagerWidget(QWidget):
53
53
  self,
54
54
  widget: QWidget | str,
55
55
  row: int | None = None,
56
- col: Optional[int] = None,
56
+ col: int | None = None,
57
57
  rowspan: int = 1,
58
58
  colspan: int = 1,
59
59
  shift_existing: bool = True,
@@ -138,6 +138,39 @@ class LayoutManagerWidget(QWidget):
138
138
  ref_row, ref_col, ref_rowspan, ref_colspan = self.widget_positions[reference_widget]
139
139
 
140
140
  # Determine new widget position based on the specified relative position
141
+
142
+ # If adding to the left or right with shifting, shift the entire column
143
+ if (
144
+ position in ("left", "right")
145
+ and shift_existing
146
+ and shift_direction in ("left", "right")
147
+ ):
148
+ column = ref_col
149
+ # Collect all rows in this column and sort for safe shifting
150
+ rows = sorted(
151
+ {row for (row, col) in self.position_widgets.keys() if col == column},
152
+ reverse=(shift_direction == "right"),
153
+ )
154
+ # Shift each widget in the column
155
+ for r in rows:
156
+ self.shift_widgets(direction=shift_direction, start_row=r, start_col=column)
157
+ # Update reference widget's position after the column shift
158
+ ref_row, ref_col, ref_rowspan, ref_colspan = self.widget_positions[reference_widget]
159
+ new_row = ref_row
160
+ # Compute insertion column based on relative position
161
+ if position == "left":
162
+ new_col = ref_col - ref_colspan
163
+ else:
164
+ new_col = ref_col + ref_colspan
165
+ # Add the new widget without triggering another shift
166
+ return self.add_widget(
167
+ widget=widget,
168
+ row=new_row,
169
+ col=new_col,
170
+ rowspan=rowspan,
171
+ colspan=colspan,
172
+ shift_existing=False,
173
+ )
141
174
  if position == "left":
142
175
  new_row = ref_row
143
176
  new_col = ref_col - 1
@@ -397,7 +397,7 @@ class DeviceInputBase(BECWidget):
397
397
  object: Device object, can be device of type Device, Positioner, Signal or ComputedSignal.
398
398
  """
399
399
  self.validate_device(device)
400
- dev = getattr(self.dev, device.lower(), None)
400
+ dev = getattr(self.dev, device, None)
401
401
  if dev is None:
402
402
  raise ValueError(
403
403
  f"Device {device} is not found in the device manager {self.dev} as enabled device."
@@ -36,14 +36,16 @@ class DeviceSignalInputBase(BECWidget):
36
36
  Kind.config: "include_config_signals",
37
37
  }
38
38
 
39
- def __init__(self, client=None, config=None, gui_id: str = None, **kwargs):
40
- if config is None:
41
- config = DeviceSignalInputBaseConfig(widget_class=self.__class__.__name__)
42
- else:
43
- if isinstance(config, dict):
44
- config = DeviceSignalInputBaseConfig(**config)
45
- self.config = config
46
- super().__init__(client=client, config=config, gui_id=gui_id, **kwargs)
39
+ def __init__(
40
+ self,
41
+ client=None,
42
+ config: DeviceSignalInputBaseConfig | dict | None = None,
43
+ gui_id: str = None,
44
+ **kwargs,
45
+ ):
46
+
47
+ self.config = self._process_config_input(config)
48
+ super().__init__(client=client, config=self.config, gui_id=gui_id, **kwargs)
47
49
 
48
50
  self._device = None
49
51
  self.get_bec_shortcuts()
@@ -102,10 +104,7 @@ class DeviceSignalInputBase(BECWidget):
102
104
  """
103
105
  self.config.signal_filter = self.signal_filter
104
106
  # pylint: disable=protected-access
105
- self._hinted_signals = []
106
- self._normal_signals = []
107
- self._config_signals = []
108
- if self.validate_device(self._device) is False:
107
+ if not self.validate_device(self._device):
109
108
  self._device = None
110
109
  self.config.device = self._device
111
110
  return
@@ -116,27 +115,19 @@ class DeviceSignalInputBase(BECWidget):
116
115
  FilterIO.set_selection(widget=self, selection=[self._device])
117
116
  return
118
117
  device_info = device._info["signals"]
119
- if Kind.hinted in self.signal_filter:
120
- hinted_signals = [
121
- signal
122
- for signal, signal_info in device_info.items()
123
- if (signal_info.get("kind_str", None) == str(Kind.hinted.value))
124
- ]
125
- self._hinted_signals = hinted_signals
126
- if Kind.normal in self.signal_filter:
127
- normal_signals = [
128
- signal
129
- for signal, signal_info in device_info.items()
130
- if (signal_info.get("kind_str", None) == str(Kind.normal.value))
131
- ]
132
- self._normal_signals = normal_signals
133
- if Kind.config in self.signal_filter:
134
- config_signals = [
118
+
119
+ def _update(kind: Kind):
120
+ return [
135
121
  signal
136
122
  for signal, signal_info in device_info.items()
137
- if (signal_info.get("kind_str", None) == str(Kind.config.value))
123
+ if kind in self.signal_filter
124
+ and (signal_info.get("kind_str", None) == str(kind.name))
138
125
  ]
139
- self._config_signals = config_signals
126
+
127
+ self._hinted_signals = _update(Kind.hinted)
128
+ self._normal_signals = _update(Kind.normal)
129
+ self._config_signals = _update(Kind.config)
130
+
140
131
  self._signals = self._hinted_signals + self._normal_signals + self._config_signals
141
132
  FilterIO.set_selection(widget=self, selection=self.signals)
142
133
 
@@ -250,7 +241,7 @@ class DeviceSignalInputBase(BECWidget):
250
241
  object: Device object, can be device of type Device, Positioner, Signal or ComputedSignal.
251
242
  """
252
243
  self.validate_device(device)
253
- dev = getattr(self.dev, device.lower(), None)
244
+ dev = getattr(self.dev, device, None)
254
245
  if dev is None:
255
246
  logger.warning(f"Device {device} not found in devicemanager.")
256
247
  return None
@@ -279,3 +270,8 @@ class DeviceSignalInputBase(BECWidget):
279
270
  if signal in self.signals:
280
271
  return True
281
272
  return False
273
+
274
+ def _process_config_input(self, config: DeviceSignalInputBaseConfig | dict | None):
275
+ if config is None:
276
+ return DeviceSignalInputBaseConfig(widget_class=self.__class__.__name__)
277
+ return DeviceSignalInputBaseConfig.model_validate(config)
@@ -140,7 +140,7 @@ class DeviceComboBox(DeviceInputBase, QComboBox):
140
140
  """
141
141
  if self.validate_device(input_text) is True:
142
142
  self._is_valid_input = True
143
- self.device_selected.emit(input_text.lower())
143
+ self.device_selected.emit(input_text)
144
144
  else:
145
145
  self._is_valid_input = False
146
146
  self.update()
@@ -147,7 +147,7 @@ class DeviceLineEdit(DeviceInputBase, QLineEdit):
147
147
  """
148
148
  if self.validate_device(input_text) is True:
149
149
  self._is_valid_input = True
150
- self.device_selected.emit(input_text.lower())
150
+ self.device_selected.emit(input_text)
151
151
  else:
152
152
  self._is_valid_input = False
153
153
  self.update()
@@ -53,6 +53,7 @@ class DictBackedTableModel(QAbstractTableModel):
53
53
  if value in self._disallowed_keys or value in self._other_keys(index.row()):
54
54
  return False
55
55
  self._data[index.row()][index.column()] = str(value)
56
+ self.dataChanged.emit(index, index)
56
57
  return True
57
58
  return False
58
59
 
@@ -109,6 +110,7 @@ class DictBackedTableModel(QAbstractTableModel):
109
110
 
110
111
  class DictBackedTable(QWidget):
111
112
  delete_rows = Signal(list)
113
+ data_updated = Signal()
112
114
 
113
115
  def __init__(self, initial_data: list[list[str]]):
114
116
  """Widget which uses a DictBackedTableModel to display an editable table
@@ -141,6 +143,11 @@ class DictBackedTable(QWidget):
141
143
  self._add_button.clicked.connect(self._table_model.add_row)
142
144
  self._remove_button.clicked.connect(self.delete_selected_rows)
143
145
  self.delete_rows.connect(self._table_model.delete_rows)
146
+ self._table_model.dataChanged.connect(self._emit_data_updated)
147
+
148
+ def _emit_data_updated(self, *args, **kwargs):
149
+ """Just to swallow the args"""
150
+ self.data_updated.emit()
144
151
 
145
152
  def delete_selected_rows(self):
146
153
  """Delete rows which are part of the selection model"""
@@ -43,6 +43,7 @@ class ScanMetadata(PydanticModelForm):
43
43
  self._additional_metadata = DictBackedTable(initial_extras or [])
44
44
  self._scan_name = scan_name or ""
45
45
  self._md_schema = get_metadata_schema_for_scan(self._scan_name)
46
+ self._additional_metadata.data_updated.connect(self.validate_form)
46
47
 
47
48
  super().__init__(parent=parent, metadata_model=self._md_schema, client=client, **kwargs)
48
49
 
@@ -0,0 +1,15 @@
1
+ def main(): # pragma: no cover
2
+ from qtpy import PYSIDE6
3
+
4
+ if not PYSIDE6:
5
+ print("PYSIDE6 is not available in the environment. Cannot patch designer.")
6
+ return
7
+ from PySide6.QtDesigner import QPyDesignerCustomWidgetCollection
8
+
9
+ from bec_widgets.widgets.editors.web_console.web_console_plugin import WebConsolePlugin
10
+
11
+ QPyDesignerCustomWidgetCollection.addCustomWidget(WebConsolePlugin())
12
+
13
+
14
+ if __name__ == "__main__": # pragma: no cover
15
+ main()