bec-widgets 1.21.4__py3-none-any.whl → 1.23.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.
CHANGELOG.md CHANGED
@@ -1,6 +1,46 @@
1
1
  # CHANGELOG
2
2
 
3
3
 
4
+ ## v1.23.0 (2025-02-24)
5
+
6
+ ### Features
7
+
8
+ - **bec_spin_box**: Double spin box with setting inside for defining decimals
9
+ ([`f19d948`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/f19d9485df403cb755315ac1a0ff4402d7a85f77))
10
+
11
+
12
+ ## v1.22.0 (2025-02-19)
13
+
14
+ ### Bug Fixes
15
+
16
+ - **modular_toolbar**: Add action to an already existing bundle
17
+ ([`4c4f159`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/4c4f1592c29974bb095c3c8325e93a1383efa289))
18
+
19
+ - **toolbar**: Qmenu Icons are visible
20
+ ([`c2c0221`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/c2c022154bddc15d81eb55aad912d8fe1e34c698))
21
+
22
+ - **toolbar**: Update_separators logic updated, there cannot be two separators next to each other
23
+ ([`facb8c3`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/facb8c30ffa3b12a97c7c68f8594b0354372ca17))
24
+
25
+ - **toolbar**: Widget actions are more compact
26
+ ([`ef36a71`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/ef36a7124d54319c2cd592433c95e4f7513e982e))
27
+
28
+ ### Features
29
+
30
+ - **toolbar**: Switchabletoolbarbutton
31
+ ([`333570b`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/333570ba2fe67cb51fdbab17718003dfdb7f7b55))
32
+
33
+ ### Refactoring
34
+
35
+ - **toolbar**: Added dark mode button for testing appearance for the toolbar example
36
+ ([`6b08f7c`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/6b08f7cfb2115609a6dc6f681631ecfae23fa899))
37
+
38
+ ### Testing
39
+
40
+ - **toolbar**: Blocking tests fixed
41
+ ([`6ae33a2`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/6ae33a23a62eafb7c820e1fde9d6d91ec1796e55))
42
+
43
+
4
44
  ## v1.21.4 (2025-02-19)
5
45
 
6
46
  ### Bug Fixes
PKG-INFO CHANGED
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bec_widgets
3
- Version: 1.21.4
3
+ Version: 1.23.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
@@ -8,7 +8,7 @@ from collections import defaultdict
8
8
  from typing import Dict, List, Literal, Tuple
9
9
 
10
10
  from bec_qthemes._icon.material_icons import material_icon
11
- from qtpy.QtCore import QSize, Qt
11
+ from qtpy.QtCore import QSize, Qt, QTimer
12
12
  from qtpy.QtGui import QAction, QColor, QIcon
13
13
  from qtpy.QtWidgets import (
14
14
  QApplication,
@@ -18,15 +18,54 @@ from qtpy.QtWidgets import (
18
18
  QMainWindow,
19
19
  QMenu,
20
20
  QSizePolicy,
21
+ QStyle,
21
22
  QToolBar,
22
23
  QToolButton,
24
+ QVBoxLayout,
23
25
  QWidget,
24
26
  )
25
27
 
26
28
  import bec_widgets
29
+ from bec_widgets.utils.colors import set_theme
30
+ from bec_widgets.widgets.utility.visual.dark_mode_button.dark_mode_button import DarkModeButton
27
31
 
28
32
  MODULE_PATH = os.path.dirname(bec_widgets.__file__)
29
33
 
34
+ # Ensure that icons are shown in menus (especially on macOS)
35
+ QApplication.setAttribute(Qt.AA_DontShowIconsInMenus, False)
36
+
37
+
38
+ class LongPressToolButton(QToolButton):
39
+ def __init__(self, *args, long_press_threshold=500, **kwargs):
40
+ super().__init__(*args, **kwargs)
41
+ self.long_press_threshold = long_press_threshold
42
+ self._long_press_timer = QTimer(self)
43
+ self._long_press_timer.setSingleShot(True)
44
+ self._long_press_timer.timeout.connect(self.handleLongPress)
45
+ self._pressed = False
46
+ self._longPressed = False
47
+
48
+ def mousePressEvent(self, event):
49
+ self._pressed = True
50
+ self._longPressed = False
51
+ self._long_press_timer.start(self.long_press_threshold)
52
+ super().mousePressEvent(event)
53
+
54
+ def mouseReleaseEvent(self, event):
55
+ self._pressed = False
56
+ if self._longPressed:
57
+ self._longPressed = False
58
+ self._long_press_timer.stop()
59
+ event.accept() # Prevent normal click action after a long press
60
+ return
61
+ self._long_press_timer.stop()
62
+ super().mouseReleaseEvent(event)
63
+
64
+ def handleLongPress(self):
65
+ if self._pressed:
66
+ self._longPressed = True
67
+ self.showMenu()
68
+
30
69
 
31
70
  class ToolBarAction(ABC):
32
71
  """
@@ -84,6 +123,21 @@ class IconAction(ToolBarAction):
84
123
  toolbar.addAction(self.action)
85
124
 
86
125
 
126
+ class QtIconAction(ToolBarAction):
127
+ def __init__(self, standard_icon, tooltip=None, checkable=False, parent=None):
128
+ super().__init__(icon_path=None, tooltip=tooltip, checkable=checkable)
129
+ self.standard_icon = standard_icon
130
+ self.icon = QApplication.style().standardIcon(standard_icon)
131
+ self.action = QAction(self.icon, self.tooltip, parent)
132
+ self.action.setCheckable(self.checkable)
133
+
134
+ def add_to_toolbar(self, toolbar, target):
135
+ toolbar.addAction(self.action)
136
+
137
+ def get_icon(self):
138
+ return self.icon
139
+
140
+
87
141
  class MaterialIconAction(ToolBarAction):
88
142
  """
89
143
  Action with a Material icon for the toolbar.
@@ -111,7 +165,7 @@ class MaterialIconAction(ToolBarAction):
111
165
  self.icon_name = icon_name
112
166
  self.filled = filled
113
167
  self.color = color
114
- # Generate the icon
168
+ # Generate the icon using the material_icon helper
115
169
  self.icon = material_icon(
116
170
  self.icon_name,
117
171
  size=(20, 20),
@@ -119,7 +173,6 @@ class MaterialIconAction(ToolBarAction):
119
173
  filled=self.filled,
120
174
  color=self.color,
121
175
  )
122
- # Immediately create an QAction with the given parent
123
176
  self.action = QAction(self.icon, self.tooltip, parent=parent)
124
177
  self.action.setCheckable(self.checkable)
125
178
 
@@ -152,7 +205,7 @@ class DeviceSelectionAction(ToolBarAction):
152
205
  device_combobox (DeviceComboBox): The combobox for selecting the device.
153
206
  """
154
207
 
155
- def __init__(self, label: str, device_combobox):
208
+ def __init__(self, label: str | None = None, device_combobox=None):
156
209
  super().__init__()
157
210
  self.label = label
158
211
  self.device_combobox = device_combobox
@@ -161,15 +214,99 @@ class DeviceSelectionAction(ToolBarAction):
161
214
  def add_to_toolbar(self, toolbar, target):
162
215
  widget = QWidget()
163
216
  layout = QHBoxLayout(widget)
164
- label = QLabel(f"{self.label}")
165
- layout.addWidget(label)
166
- layout.addWidget(self.device_combobox)
167
- toolbar.addWidget(widget)
217
+ layout.setContentsMargins(0, 0, 0, 0)
218
+ layout.setSpacing(0)
219
+ if self.label is not None:
220
+ label = QLabel(f"{self.label}")
221
+ layout.addWidget(label)
222
+ if self.device_combobox is not None:
223
+ layout.addWidget(self.device_combobox)
224
+ toolbar.addWidget(widget)
168
225
 
169
226
  def set_combobox_style(self, color: str):
170
227
  self.device_combobox.setStyleSheet(f"QComboBox {{ background-color: {color}; }}")
171
228
 
172
229
 
230
+ class SwitchableToolBarAction(ToolBarAction):
231
+ """
232
+ A split toolbar action that combines a main action and a drop-down menu for additional actions.
233
+
234
+ The main button displays the currently selected action's icon and tooltip. Clicking on the main button
235
+ triggers that action. Clicking on the drop-down arrow displays a menu with alternative actions. When an
236
+ alternative action is selected, it becomes the new default and its callback is immediately executed.
237
+
238
+ This design mimics the behavior seen in Adobe Photoshop or Affinity Designer toolbars.
239
+
240
+ Args:
241
+ actions (dict): A dictionary mapping a unique key to a ToolBarAction instance.
242
+ initial_action (str, optional): The key of the initial default action. If not provided, the first action is used.
243
+ tooltip (str, optional): An optional tooltip for the split action; if provided, it overrides the default action's tooltip.
244
+ checkable (bool, optional): Whether the action is checkable. Defaults to True.
245
+ parent (QWidget, optional): Parent widget for the underlying QAction.
246
+ """
247
+
248
+ def __init__(
249
+ self,
250
+ actions: Dict[str, ToolBarAction],
251
+ initial_action: str = None,
252
+ tooltip: str = None,
253
+ checkable: bool = True,
254
+ parent=None,
255
+ ):
256
+ super().__init__(icon_path=None, tooltip=tooltip, checkable=checkable)
257
+ self.actions = actions
258
+ self.current_key = initial_action if initial_action is not None else next(iter(actions))
259
+ self.parent = parent
260
+ self.checkable = checkable
261
+ self.main_button = None
262
+ self.menu_actions: Dict[str, QAction] = {}
263
+
264
+ def add_to_toolbar(self, toolbar: QToolBar, target: QWidget):
265
+ """
266
+ Adds the split action to the toolbar.
267
+
268
+ Args:
269
+ toolbar (QToolBar): The toolbar to add the action to.
270
+ target (QWidget): The target widget for the action.
271
+ """
272
+ self.main_button = LongPressToolButton(toolbar)
273
+ self.main_button.setPopupMode(QToolButton.MenuButtonPopup)
274
+ self.main_button.setCheckable(self.checkable)
275
+ default_action = self.actions[self.current_key]
276
+ self.main_button.setIcon(default_action.get_icon())
277
+ self.main_button.setToolTip(default_action.tooltip)
278
+ self.main_button.clicked.connect(self._trigger_current_action)
279
+ menu = QMenu(self.main_button)
280
+ self.menu_actions = {}
281
+ for key, action_obj in self.actions.items():
282
+ menu_action = QAction(action_obj.get_icon(), action_obj.tooltip, self.main_button)
283
+ menu_action.setIconVisibleInMenu(True)
284
+ menu_action.setCheckable(self.checkable)
285
+ menu_action.setChecked(key == self.current_key)
286
+ menu_action.triggered.connect(lambda checked, k=key: self._set_default_action(k))
287
+ menu.addAction(menu_action)
288
+ self.menu_actions[key] = menu_action
289
+ self.main_button.setMenu(menu)
290
+ toolbar.addWidget(self.main_button)
291
+
292
+ def _trigger_current_action(self):
293
+ action_obj = self.actions[self.current_key]
294
+ action_obj.action.trigger()
295
+
296
+ def _set_default_action(self, key: str):
297
+ self.current_key = key
298
+ new_action = self.actions[self.current_key]
299
+ self.main_button.setIcon(new_action.get_icon())
300
+ self.main_button.setToolTip(new_action.tooltip)
301
+ # Update check state of menu items
302
+ for k, menu_act in self.menu_actions.items():
303
+ menu_act.setChecked(k == key)
304
+ new_action.action.trigger()
305
+
306
+ def get_icon(self) -> QIcon:
307
+ return self.actions[self.current_key].get_icon()
308
+
309
+
173
310
  class WidgetAction(ToolBarAction):
174
311
  """
175
312
  Action for adding any widget to the toolbar.
@@ -180,15 +317,23 @@ class WidgetAction(ToolBarAction):
180
317
  """
181
318
 
182
319
  def __init__(self, label: str | None = None, widget: QWidget = None, parent=None):
183
- super().__init__(parent)
320
+ super().__init__(icon_path=None, tooltip=label, checkable=False)
184
321
  self.label = label
185
322
  self.widget = widget
323
+ self.container = None
186
324
 
187
325
  def add_to_toolbar(self, toolbar: QToolBar, target: QWidget):
188
- container = QWidget()
189
- layout = QHBoxLayout(container)
326
+ """
327
+ Adds the widget to the toolbar.
328
+
329
+ Args:
330
+ toolbar (QToolBar): The toolbar to add the widget to.
331
+ target (QWidget): The target widget for the action.
332
+ """
333
+ self.container = QWidget()
334
+ layout = QHBoxLayout(self.container)
190
335
  layout.setContentsMargins(0, 0, 0, 0)
191
- layout.setSpacing(5)
336
+ layout.setSpacing(0)
192
337
 
193
338
  if self.label is not None:
194
339
  label_widget = QLabel(f"{self.label}")
@@ -209,19 +354,12 @@ class WidgetAction(ToolBarAction):
209
354
 
210
355
  layout.addWidget(self.widget)
211
356
 
212
- toolbar.addWidget(container)
357
+ toolbar.addWidget(self.container)
358
+ # Store the container as the action to allow toggling visibility.
359
+ self.action = self.container
213
360
 
214
361
  @staticmethod
215
362
  def calculate_minimum_width(combo_box: QComboBox) -> int:
216
- """
217
- Calculate the minimum width required to display the longest item in the combo box.
218
-
219
- Args:
220
- combo_box (QComboBox): The combo box to calculate the width for.
221
-
222
- Returns:
223
- int: The calculated minimum width in pixels.
224
- """
225
363
  font_metrics = combo_box.fontMetrics()
226
364
  max_width = max(font_metrics.width(combo_box.itemText(i)) for i in range(combo_box.count()))
227
365
  return max_width + 60
@@ -261,12 +399,15 @@ class ExpandableMenuAction(ToolBarAction):
261
399
  menu = QMenu(button)
262
400
  for action_id, action in self.actions.items():
263
401
  sub_action = QAction(action.tooltip, target)
264
- if hasattr(action, "icon_path"):
402
+ sub_action.setIconVisibleInMenu(True)
403
+ if action.icon_path:
265
404
  icon = QIcon()
266
405
  icon.addFile(action.icon_path, size=QSize(20, 20))
267
406
  sub_action.setIcon(icon)
268
- elif hasattr(action, "get_icon"):
269
- sub_action.setIcon(action.get_icon())
407
+ elif hasattr(action, "get_icon") and callable(action.get_icon):
408
+ sub_icon = action.get_icon()
409
+ if sub_icon and not sub_icon.isNull():
410
+ sub_action.setIcon(sub_icon)
270
411
  sub_action.setCheckable(action.checkable)
271
412
  menu.addAction(sub_action)
272
413
  self.widgets[action_id] = sub_action
@@ -289,7 +430,6 @@ class ToolbarBundle:
289
430
  self.bundle_id = bundle_id
290
431
  self._actions: dict[str, ToolBarAction] = {}
291
432
 
292
- # If you passed in a list of tuples, load them into the dictionary
293
433
  if actions is not None:
294
434
  for action_id, action in actions:
295
435
  self._actions[action_id] = action
@@ -331,7 +471,7 @@ class ModularToolBar(QToolBar):
331
471
  actions (dict, optional): A dictionary of action creators to populate the toolbar. Defaults to None.
332
472
  target_widget (QWidget, optional): The widget that the actions will target. Defaults to None.
333
473
  orientation (Literal["horizontal", "vertical"], optional): The initial orientation of the toolbar. Defaults to "horizontal".
334
- background_color (str, optional): The background color of the toolbar. Defaults to "rgba(0, 0, 0, 0)" - transparent background.
474
+ background_color (str, optional): The background color of the toolbar. Defaults to "rgba(0, 0, 0, 0)".
335
475
  """
336
476
 
337
477
  def __init__(
@@ -378,7 +518,7 @@ class ModularToolBar(QToolBar):
378
518
  Sets the background color and other appearance settings.
379
519
 
380
520
  Args:
381
- color(str): The background color of the toolbar.
521
+ color (str): The background color of the toolbar.
382
522
  """
383
523
  self.setIconSize(QSize(20, 20))
384
524
  self.setMovable(False)
@@ -402,100 +542,133 @@ class ModularToolBar(QToolBar):
402
542
 
403
543
  def update_material_icon_colors(self, new_color: str | tuple | QColor):
404
544
  """
405
- Updates the color of all MaterialIconAction icons in the toolbar.
545
+ Updates the color of all MaterialIconAction icons.
406
546
 
407
547
  Args:
408
- new_color (str | tuple | QColor): The new color for the icons.
548
+ new_color (str | tuple | QColor): The new color.
409
549
  """
410
550
  for action in self.widgets.values():
411
551
  if isinstance(action, MaterialIconAction):
412
552
  action.color = new_color
413
- # Refresh the icon
414
553
  updated_icon = action.get_icon()
415
554
  action.action.setIcon(updated_icon)
416
555
 
417
556
  def add_action(self, action_id: str, action: ToolBarAction, target_widget: QWidget):
418
557
  """
419
- Adds a new standalone action to the toolbar dynamically.
558
+ Adds a new standalone action dynamically.
420
559
 
421
560
  Args:
422
- action_id (str): Unique identifier for the action.
423
- action (ToolBarAction): The action to add to the toolbar.
424
- target_widget (QWidget): The target widget for the action.
561
+ action_id (str): Unique identifier.
562
+ action (ToolBarAction): The action to add.
563
+ target_widget (QWidget): The target widget.
425
564
  """
426
565
  if action_id in self.widgets:
427
566
  raise ValueError(f"Action with ID '{action_id}' already exists.")
428
567
  action.add_to_toolbar(self, target_widget)
429
568
  self.widgets[action_id] = action
430
569
  self.toolbar_items.append(("action", action_id))
431
- self.update_separators() # Update separators after adding the action
570
+ self.update_separators()
432
571
 
433
572
  def hide_action(self, action_id: str):
434
573
  """
435
- Hides a specific action on the toolbar.
574
+ Hides a specific action.
436
575
 
437
576
  Args:
438
- action_id (str): Unique identifier for the action to hide.
577
+ action_id (str): Unique identifier.
439
578
  """
440
579
  if action_id not in self.widgets:
441
580
  raise ValueError(f"Action with ID '{action_id}' does not exist.")
442
581
  action = self.widgets[action_id]
443
- if hasattr(action, "action") and isinstance(action.action, QAction):
582
+ if hasattr(action, "action") and action.action is not None:
444
583
  action.action.setVisible(False)
445
- self.update_separators() # Update separators after hiding the action
584
+ self.update_separators()
446
585
 
447
586
  def show_action(self, action_id: str):
448
587
  """
449
- Shows a specific action on the toolbar.
588
+ Shows a specific action.
450
589
 
451
590
  Args:
452
- action_id (str): Unique identifier for the action to show.
591
+ action_id (str): Unique identifier.
453
592
  """
454
593
  if action_id not in self.widgets:
455
594
  raise ValueError(f"Action with ID '{action_id}' does not exist.")
456
595
  action = self.widgets[action_id]
457
- if hasattr(action, "action") and isinstance(action.action, QAction):
596
+ if hasattr(action, "action") and action.action is not None:
458
597
  action.action.setVisible(True)
459
- self.update_separators() # Update separators after showing the action
598
+ self.update_separators()
460
599
 
461
600
  def add_bundle(self, bundle: ToolbarBundle, target_widget: QWidget):
462
601
  """
463
- Adds a bundle of actions to the toolbar, separated by a separator.
602
+ Adds a bundle of actions, separated by a separator.
464
603
 
465
604
  Args:
466
- bundle (ToolbarBundle): The bundle to add.
467
- target_widget (QWidget): The target widget for the actions.
605
+ bundle (ToolbarBundle): The bundle.
606
+ target_widget (QWidget): The target widget.
468
607
  """
469
608
  if bundle.bundle_id in self.bundles:
470
609
  raise ValueError(f"ToolbarBundle with ID '{bundle.bundle_id}' already exists.")
471
610
 
472
- # Add a separator before the bundle (but not to first one)
473
611
  if self.toolbar_items:
474
612
  sep = SeparatorAction()
475
613
  sep.add_to_toolbar(self, target_widget)
476
614
  self.toolbar_items.append(("separator", None))
477
615
 
478
- # Add each action in the bundle
479
616
  for action_id, action_obj in bundle.actions.items():
480
617
  action_obj.add_to_toolbar(self, target_widget)
481
618
  self.widgets[action_id] = action_obj
482
619
 
483
- # Register the bundle
484
620
  self.bundles[bundle.bundle_id] = list(bundle.actions.keys())
485
621
  self.toolbar_items.append(("bundle", bundle.bundle_id))
622
+ self.update_separators()
623
+
624
+ def add_action_to_bundle(self, bundle_id: str, action_id: str, action, target_widget: QWidget):
625
+ """
626
+ Dynamically adds an action to an existing bundle.
627
+
628
+ Args:
629
+ bundle_id (str): The bundle ID.
630
+ action_id (str): Unique identifier.
631
+ action (ToolBarAction): The action to add.
632
+ target_widget (QWidget): The target widget.
633
+ """
634
+ if bundle_id not in self.bundles:
635
+ raise ValueError(f"Bundle '{bundle_id}' does not exist.")
636
+ if action_id in self.widgets:
637
+ raise ValueError(f"Action with ID '{action_id}' already exists.")
638
+
639
+ action.add_to_toolbar(self, target_widget)
640
+ new_qaction = action.action
641
+ self.removeAction(new_qaction)
642
+
643
+ bundle_action_ids = self.bundles[bundle_id]
644
+ if bundle_action_ids:
645
+ last_bundle_action = self.widgets[bundle_action_ids[-1]].action
646
+ actions_list = self.actions()
647
+ try:
648
+ index = actions_list.index(last_bundle_action)
649
+ except ValueError:
650
+ self.addAction(new_qaction)
651
+ else:
652
+ if index + 1 < len(actions_list):
653
+ before_action = actions_list[index + 1]
654
+ self.insertAction(before_action, new_qaction)
655
+ else:
656
+ self.addAction(new_qaction)
657
+ else:
658
+ self.addAction(new_qaction)
486
659
 
487
- self.update_separators() # Update separators after adding the bundle
660
+ self.widgets[action_id] = action
661
+ self.bundles[bundle_id].append(action_id)
662
+ self.update_separators()
488
663
 
489
664
  def contextMenuEvent(self, event):
490
665
  """
491
- Overrides the context menu event to show a list of toolbar actions with checkboxes and icons, including separators.
666
+ Overrides the context menu event to show toolbar actions with checkboxes and icons.
492
667
 
493
668
  Args:
494
- event(QContextMenuEvent): The context menu event.
669
+ event (QContextMenuEvent): The context menu event.
495
670
  """
496
671
  menu = QMenu(self)
497
-
498
- # Iterate through the toolbar items in order
499
672
  for item_type, identifier in self.toolbar_items:
500
673
  if item_type == "separator":
501
674
  menu.addSeparator()
@@ -503,18 +676,16 @@ class ModularToolBar(QToolBar):
503
676
  self.handle_bundle_context_menu(menu, identifier)
504
677
  elif item_type == "action":
505
678
  self.handle_action_context_menu(menu, identifier)
506
-
507
- # Connect the triggered signal after all actions are added
508
679
  menu.triggered.connect(self.handle_menu_triggered)
509
680
  menu.exec_(event.globalPos())
510
681
 
511
682
  def handle_bundle_context_menu(self, menu: QMenu, bundle_id: str):
512
683
  """
513
- Adds a set of bundle actions to the context menu.
684
+ Adds bundle actions to the context menu.
514
685
 
515
686
  Args:
516
- menu (QMenu): The context menu to which the actions are added.
517
- bundle_id (str): The identifier for the bundle.
687
+ menu (QMenu): The context menu.
688
+ bundle_id (str): The bundle identifier.
518
689
  """
519
690
  action_ids = self.bundles.get(bundle_id, [])
520
691
  for act_id in action_ids:
@@ -535,7 +706,6 @@ class ModularToolBar(QToolBar):
535
706
  # Set the icon if available
536
707
  if qaction.icon() and not qaction.icon().isNull():
537
708
  menu_action.setIcon(qaction.icon())
538
-
539
709
  menu.addAction(menu_action)
540
710
 
541
711
  def handle_action_context_menu(self, menu: QMenu, action_id: str):
@@ -565,73 +735,95 @@ class ModularToolBar(QToolBar):
565
735
  menu.addAction(menu_action)
566
736
 
567
737
  def handle_menu_triggered(self, action):
568
- """Handles the toggling of toolbar actions from the context menu."""
738
+ """
739
+ Handles the triggered signal from the context menu.
740
+
741
+ Args:
742
+ action: Action triggered.
743
+ """
569
744
  action_id = action.data()
570
745
  if action_id:
571
746
  self.toggle_action_visibility(action_id, action.isChecked())
572
747
 
573
748
  def toggle_action_visibility(self, action_id: str, visible: bool):
574
749
  """
575
- Toggles the visibility of a specific action on the toolbar.
750
+ Toggles the visibility of a specific action.
576
751
 
577
752
  Args:
578
- action_id(str): Unique identifier for the action to toggle.
579
- visible(bool): Whether the action should be visible.
753
+ action_id (str): Unique identifier.
754
+ visible (bool): Whether the action should be visible.
580
755
  """
581
756
  if action_id not in self.widgets:
582
757
  return
583
-
584
758
  tool_action = self.widgets[action_id]
585
- if hasattr(tool_action, "action") and isinstance(tool_action.action, QAction):
759
+ if hasattr(tool_action, "action") and tool_action.action is not None:
586
760
  tool_action.action.setVisible(visible)
587
761
  self.update_separators()
588
762
 
589
763
  def update_separators(self):
590
764
  """
591
- Hide separators that are adjacent to another separator or have no actions next to them.
765
+ Hide separators that are adjacent to another separator or have no non-separator actions between them.
592
766
  """
593
767
  toolbar_actions = self.actions()
594
-
768
+ # First pass: set visibility based on surrounding non-separator actions.
595
769
  for i, action in enumerate(toolbar_actions):
596
770
  if not action.isSeparator():
597
771
  continue
598
- # Find the previous visible action
599
772
  prev_visible = None
600
773
  for j in range(i - 1, -1, -1):
601
774
  if toolbar_actions[j].isVisible():
602
775
  prev_visible = toolbar_actions[j]
603
776
  break
604
-
605
- # Find the next visible action
606
777
  next_visible = None
607
778
  for j in range(i + 1, len(toolbar_actions)):
608
779
  if toolbar_actions[j].isVisible():
609
780
  next_visible = toolbar_actions[j]
610
781
  break
611
-
612
- # Determine if the separator should be hidden
613
- # Hide if both previous and next visible actions are separators or non-existent
614
782
  if (prev_visible is None or prev_visible.isSeparator()) and (
615
783
  next_visible is None or next_visible.isSeparator()
616
784
  ):
617
785
  action.setVisible(False)
618
786
  else:
619
787
  action.setVisible(True)
788
+ # Second pass: ensure no two visible separators are adjacent.
789
+ prev = None
790
+ for action in toolbar_actions:
791
+ if action.isVisible() and action.isSeparator():
792
+ if prev and prev.isSeparator():
793
+ action.setVisible(False)
794
+ else:
795
+ prev = action
796
+ else:
797
+ if action.isVisible():
798
+ prev = action
620
799
 
621
800
 
622
801
  class MainWindow(QMainWindow): # pragma: no cover
623
802
  def __init__(self):
624
803
  super().__init__()
625
804
  self.setWindowTitle("Toolbar / ToolbarBundle Demo")
626
-
627
805
  self.central_widget = QWidget()
628
806
  self.setCentralWidget(self.central_widget)
807
+ self.test_label = QLabel(text="This is a test label.")
808
+ self.central_widget.layout = QVBoxLayout(self.central_widget)
809
+ self.central_widget.layout.addWidget(self.test_label)
629
810
 
630
- # Create a modular toolbar
631
811
  self.toolbar = ModularToolBar(parent=self, target_widget=self)
632
812
  self.addToolBar(self.toolbar)
633
813
 
634
- # Example: Add a single bundle
814
+ self.add_switchable_button_checkable()
815
+ self.add_switchable_button_non_checkable()
816
+ self.add_widget_actions()
817
+ self.add_bundles()
818
+ self.add_menus()
819
+
820
+ # For theme testing
821
+
822
+ self.dark_button = DarkModeButton(toolbar=True)
823
+ dark_mode_action = WidgetAction(label=None, widget=self.dark_button)
824
+ self.toolbar.add_action("dark_mode", dark_mode_action, self)
825
+
826
+ def add_bundles(self):
635
827
  home_action = MaterialIconAction(
636
828
  icon_name="home", tooltip="Home", checkable=True, parent=self
637
829
  )
@@ -651,12 +843,11 @@ class MainWindow(QMainWindow): # pragma: no cover
651
843
  )
652
844
  self.toolbar.add_bundle(main_actions_bundle, target_widget=self)
653
845
 
654
- # Another bundle
655
846
  search_action = MaterialIconAction(
656
- icon_name="search", tooltip="Search", checkable=True, parent=self
847
+ icon_name="search", tooltip="Search", checkable=False, parent=self
657
848
  )
658
849
  help_action = MaterialIconAction(
659
- icon_name="help", tooltip="Help", checkable=True, parent=self
850
+ icon_name="help", tooltip="Help", checkable=False, parent=self
660
851
  )
661
852
  second_bundle = ToolbarBundle(
662
853
  bundle_id="secondary_actions",
@@ -664,9 +855,101 @@ class MainWindow(QMainWindow): # pragma: no cover
664
855
  )
665
856
  self.toolbar.add_bundle(second_bundle, target_widget=self)
666
857
 
858
+ new_action = MaterialIconAction(
859
+ icon_name="counter_1", tooltip="New Action", checkable=True, parent=self
860
+ )
861
+ self.toolbar.add_action_to_bundle(
862
+ "main_actions", "new_action", new_action, target_widget=self
863
+ )
864
+
865
+ def add_menus(self):
866
+ menu_material_actions = {
867
+ "mat1": MaterialIconAction(
868
+ icon_name="home", tooltip="Material Home", checkable=True, parent=self
869
+ ),
870
+ "mat2": MaterialIconAction(
871
+ icon_name="settings", tooltip="Material Settings", checkable=True, parent=self
872
+ ),
873
+ "mat3": MaterialIconAction(
874
+ icon_name="info", tooltip="Material Info", checkable=True, parent=self
875
+ ),
876
+ }
877
+ menu_qt_actions = {
878
+ "qt1": QtIconAction(
879
+ standard_icon=QStyle.SP_FileIcon, tooltip="Qt File", checkable=True, parent=self
880
+ ),
881
+ "qt2": QtIconAction(
882
+ standard_icon=QStyle.SP_DirIcon, tooltip="Qt Directory", checkable=True, parent=self
883
+ ),
884
+ "qt3": QtIconAction(
885
+ standard_icon=QStyle.SP_TrashIcon, tooltip="Qt Trash", checkable=True, parent=self
886
+ ),
887
+ }
888
+ expandable_menu_material = ExpandableMenuAction(
889
+ label="Material Menu", actions=menu_material_actions
890
+ )
891
+ expandable_menu_qt = ExpandableMenuAction(label="Qt Menu", actions=menu_qt_actions)
892
+
893
+ self.toolbar.add_action("material_menu", expandable_menu_material, self)
894
+ self.toolbar.add_action("qt_menu", expandable_menu_qt, self)
895
+
896
+ def add_switchable_button_checkable(self):
897
+ action1 = MaterialIconAction(
898
+ icon_name="counter_1", tooltip="Action 1", checkable=True, parent=self
899
+ )
900
+ action2 = MaterialIconAction(
901
+ icon_name="counter_2", tooltip="Action 2", checkable=True, parent=self
902
+ )
903
+
904
+ switchable_action = SwitchableToolBarAction(
905
+ actions={"action1": action1, "action2": action2},
906
+ initial_action="action1",
907
+ tooltip="Switchable Action",
908
+ checkable=True,
909
+ parent=self,
910
+ )
911
+ self.toolbar.add_action("switchable_action", switchable_action, self)
912
+
913
+ action1.action.toggled.connect(
914
+ lambda checked: self.test_label.setText(f"Action 1 triggered, checked = {checked}")
915
+ )
916
+ action2.action.toggled.connect(
917
+ lambda checked: self.test_label.setText(f"Action 2 triggered, checked = {checked}")
918
+ )
919
+
920
+ def add_switchable_button_non_checkable(self):
921
+ action1 = MaterialIconAction(
922
+ icon_name="counter_1", tooltip="Action 1", checkable=False, parent=self
923
+ )
924
+ action2 = MaterialIconAction(
925
+ icon_name="counter_2", tooltip="Action 2", checkable=False, parent=self
926
+ )
927
+
928
+ switchable_action = SwitchableToolBarAction(
929
+ actions={"action1": action1, "action2": action2},
930
+ initial_action="action1",
931
+ tooltip="Switchable Action",
932
+ checkable=False,
933
+ parent=self,
934
+ )
935
+ self.toolbar.add_action("switchable_action_no_toggle", switchable_action, self)
936
+
937
+ action1.action.triggered.connect(
938
+ lambda checked: self.test_label.setText(f"Action 1 triggered, checked = {checked}")
939
+ )
940
+ action2.action.triggered.connect(
941
+ lambda checked: self.test_label.setText(f"Action 2 triggered, checked = {checked}")
942
+ )
943
+
944
+ def add_widget_actions(self):
945
+ combo = QComboBox()
946
+ combo.addItems(["Option 1", "Option 2", "Option 3"])
947
+ self.toolbar.add_action("device_combo", WidgetAction(label="Device:", widget=combo), self)
948
+
667
949
 
668
950
  if __name__ == "__main__": # pragma: no cover
669
951
  app = QApplication(sys.argv)
952
+ set_theme("light")
670
953
  main_window = MainWindow()
671
954
  main_window.show()
672
955
  sys.exit(app.exec_())
File without changes
@@ -0,0 +1 @@
1
+ {'files': ['decimal_spinbox.py']}
@@ -0,0 +1,54 @@
1
+ # Copyright (C) 2022 The Qt Company Ltd.
2
+ # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
3
+
4
+ from qtpy.QtDesigner import QDesignerCustomWidgetInterface
5
+
6
+ from bec_widgets.utils.bec_designer import designer_material_icon
7
+ from bec_widgets.widgets.utility.spinbox.decimal_spinbox import BECSpinBox
8
+
9
+ DOM_XML = """
10
+ <ui language='c++'>
11
+ <widget class='BECSpinBox' name='bec_spin_box'>
12
+ </widget>
13
+ </ui>
14
+ """
15
+
16
+
17
+ class BECSpinBoxPlugin(QDesignerCustomWidgetInterface): # pragma: no cover
18
+ def __init__(self):
19
+ super().__init__()
20
+ self._form_editor = None
21
+
22
+ def createWidget(self, parent):
23
+ t = BECSpinBox(parent)
24
+ return t
25
+
26
+ def domXml(self):
27
+ return DOM_XML
28
+
29
+ def group(self):
30
+ return ""
31
+
32
+ def icon(self):
33
+ return designer_material_icon(BECSpinBox.ICON_NAME)
34
+
35
+ def includeFile(self):
36
+ return "bec_spin_box"
37
+
38
+ def initialize(self, form_editor):
39
+ self._form_editor = form_editor
40
+
41
+ def isContainer(self):
42
+ return False
43
+
44
+ def isInitialized(self):
45
+ return self._form_editor is not None
46
+
47
+ def name(self):
48
+ return "BECSpinBox"
49
+
50
+ def toolTip(self):
51
+ return "BECSpinBox"
52
+
53
+ def whatsThis(self):
54
+ return self.toolTip()
@@ -0,0 +1,83 @@
1
+ import sys
2
+
3
+ from bec_qthemes import material_icon
4
+ from qtpy.QtGui import Qt
5
+ from qtpy.QtWidgets import (
6
+ QApplication,
7
+ QDoubleSpinBox,
8
+ QInputDialog,
9
+ QSizePolicy,
10
+ QToolButton,
11
+ QWidget,
12
+ )
13
+
14
+ from bec_widgets.utils import ConnectionConfig
15
+ from bec_widgets.utils.bec_widget import BECWidget
16
+
17
+
18
+ class BECSpinBox(BECWidget, QDoubleSpinBox):
19
+ PLUGIN = True
20
+ RPC = False
21
+ ICON_NAME = "123"
22
+
23
+ def __init__(
24
+ self,
25
+ parent: QWidget | None = None,
26
+ config: ConnectionConfig | None = None,
27
+ client=None,
28
+ gui_id: str | None = None,
29
+ ) -> None:
30
+ if config is None:
31
+ config = ConnectionConfig(widget_class=self.__class__.__name__)
32
+ super().__init__(client=client, gui_id=gui_id, config=config)
33
+ QDoubleSpinBox.__init__(self, parent=parent)
34
+
35
+ self.setObjectName("BECSpinBox")
36
+ # Make the widget as compact as possible horizontally.
37
+ self.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)
38
+ self.setAlignment(Qt.AlignHCenter)
39
+
40
+ # Configure default QDoubleSpinBox settings.
41
+ self.setRange(-2147483647, 2147483647)
42
+ self.setDecimals(2)
43
+
44
+ # Create an embedded settings button.
45
+ self.setting_button = QToolButton(self)
46
+ self.setting_button.setIcon(material_icon("settings"))
47
+ self.setting_button.setToolTip("Set number of decimals")
48
+ self.setting_button.setCursor(Qt.PointingHandCursor)
49
+ self.setting_button.setFocusPolicy(Qt.NoFocus)
50
+ self.setting_button.setStyleSheet("QToolButton { border: none; padding: 0px; }")
51
+
52
+ self.setting_button.clicked.connect(self.change_decimals)
53
+
54
+ self._button_size = 12
55
+ self._arrow_width = 20
56
+
57
+ def resizeEvent(self, event):
58
+ super().resizeEvent(event)
59
+ arrow_width = self._arrow_width
60
+
61
+ # Position the settings button inside the spin box, to the left of the arrow buttons.
62
+ x = self.width() - arrow_width - self._button_size - 2 # 2px margin
63
+ y = (self.height() - self._button_size) // 2
64
+ self.setting_button.setFixedSize(self._button_size, self._button_size)
65
+ self.setting_button.move(x, y)
66
+
67
+ def change_decimals(self):
68
+ """
69
+ Change the number of decimals in the spin box.
70
+ """
71
+ current = self.decimals()
72
+ new_decimals, ok = QInputDialog.getInt(
73
+ self, "Set Decimals", "Number of decimals:", current, 0, 10, 1
74
+ )
75
+ if ok:
76
+ self.setDecimals(new_decimals)
77
+
78
+
79
+ if __name__ == "__main__": # pragma: no cover
80
+ app = QApplication(sys.argv)
81
+ window = BECSpinBox()
82
+ window.show()
83
+ sys.exit(app.exec())
@@ -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.utility.spinbox.bec_spin_box_plugin import BECSpinBoxPlugin
10
+
11
+ QPyDesignerCustomWidgetCollection.addCustomWidget(BECSpinBoxPlugin())
12
+
13
+
14
+ if __name__ == "__main__": # pragma: no cover
15
+ main()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bec_widgets
3
- Version: 1.21.4
3
+ Version: 1.23.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
@@ -2,11 +2,11 @@
2
2
  .gitlab-ci.yml,sha256=PuL-FmkTHm7qs467Mh9D8quWcEj4tgEA-UUGDieMuWk,8774
3
3
  .pylintrc,sha256=eeY8YwSI74oFfq6IYIbCqnx3Vk8ZncKaatv96n_Y8Rs,18544
4
4
  .readthedocs.yaml,sha256=aSOc277LqXcsTI6lgvm_JY80lMlr69GbPKgivua2cS0,603
5
- CHANGELOG.md,sha256=-QLbBdGYSOTPppL7AldwA3tN0fZX8lKbMY22o2RljUE,229125
5
+ CHANGELOG.md,sha256=areIe8x_CISa3v2AYaePk8DGFWijiSCy-ILBAZGrYdA,230584
6
6
  LICENSE,sha256=YRKe85CBRyP7UpEAWwU8_qSIyuy5-l_9C-HKg5Qm8MQ,1511
7
- PKG-INFO,sha256=HHoSDKocVfOqHOQ1v4D0zGIt7bh21an3XrIT-fHdFaI,1173
7
+ PKG-INFO,sha256=WcdVimfKWBYa3l0ifogd51I_0VM7a9qy1uAOAj4oeEU,1173
8
8
  README.md,sha256=KgdKusjlvEvFtdNZCeDMO91y77MWK2iDcYMDziksOr4,2553
9
- pyproject.toml,sha256=C7YNftTOcCZQF3r-TZqY-hnBUvc_msEBEk6q5j56eIQ,2540
9
+ pyproject.toml,sha256=BUpfPrJNoboBinXX3OGzf4SD1ImSX6TqmS179jRRMXI,2540
10
10
  .git_hooks/pre-commit,sha256=n3RofIZHJl8zfJJIUomcMyYGFi_rwq4CC19z0snz3FI,286
11
11
  .gitlab/issue_templates/bug_report_template.md,sha256=gAuyEwl7XlnebBrkiJ9AqffSNOywmr8vygUFWKTuQeI,386
12
12
  .gitlab/issue_templates/documentation_update_template.md,sha256=FHLdb3TS_D9aL4CYZCjyXSulbaW5mrN2CmwTaeLPbNw,860
@@ -55,7 +55,7 @@ bec_widgets/qt_utils/redis_message_waiter.py,sha256=fvL_QgC0cTDv_FPJdRyp5AKjf401
55
55
  bec_widgets/qt_utils/round_frame.py,sha256=ldLQ4BaWygFRrRv1s0w--5qZ4sj8MAqhdXuzho50xMg,4860
56
56
  bec_widgets/qt_utils/settings_dialog.py,sha256=NhtzTer_xzlB2lLLrGklkI1QYLJEWQpJoZbCz4o5daI,3645
57
57
  bec_widgets/qt_utils/side_panel.py,sha256=H2Ko7FPYwQ2Nemrud-q-rmqzHGO8vly_pzE_ySEfoGQ,12569
58
- bec_widgets/qt_utils/toolbar.py,sha256=YY_-UGc7uZhahYn7xnTvBGbalmTkpTa4WLikpsHwnMw,24433
58
+ bec_widgets/qt_utils/toolbar.py,sha256=AizwnTiY2RkRJfouJu4nCiFoWd0wEgS_nDF36EiUQjk,35779
59
59
  bec_widgets/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
60
60
  bec_widgets/tests/utils.py,sha256=GbQtN7qf9n-8FoAfNddZ4aAqA7oBo_hGAlnKELd6Xzw,6943
61
61
  bec_widgets/utils/__init__.py,sha256=1930ji1Jj6dVuY81Wd2kYBhHYNV-2R0bN_L4o9zBj1U,533
@@ -325,6 +325,11 @@ bec_widgets/widgets/utility/logpanel/log_panel.pyproject,sha256=2ncs1bsu-wICstR1
325
325
  bec_widgets/widgets/utility/logpanel/log_panel_plugin.py,sha256=KY7eS1uGZzLYtDAdBv6S2mw8UjcDGVt3UklN_D5M06A,1250
326
326
  bec_widgets/widgets/utility/logpanel/logpanel.py,sha256=jMQKn5O7qUFkN-2YnQ3HY7vSf8LIH5ox-T1E_lL3zfQ,19675
327
327
  bec_widgets/widgets/utility/logpanel/register_log_panel.py,sha256=LFUE5JzCYvIwJQtTqZASLVAHYy3gO1nrHzPVH_kpCEY,470
328
+ bec_widgets/widgets/utility/spinbox/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
329
+ bec_widgets/widgets/utility/spinbox/bec_spin_box.pyproject,sha256=RIg1SZuCltuZZuK1O4Djg0TpCInhoCw8KeqNaf1_K0A,33
330
+ bec_widgets/widgets/utility/spinbox/bec_spin_box_plugin.py,sha256=-XNrUAz1LZQPhJrH1sszfGrpBfpHUIfNO4bw7MPcc3k,1255
331
+ bec_widgets/widgets/utility/spinbox/decimal_spinbox.py,sha256=JhTh4Iz_7Hv69G4kksdpa1QZihtAzqaYlkWf3v-SGN8,2671
332
+ bec_widgets/widgets/utility/spinbox/register_bec_spin_box.py,sha256=_whARPM42TCYV_rUBtThGUK7XC1VoIwRn8fVHzxI2pc,476
328
333
  bec_widgets/widgets/utility/spinner/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
329
334
  bec_widgets/widgets/utility/spinner/register_spinner_widget.py,sha256=96A13dEcyTgXfc9G0sTdlXYCDcVav8Z2P2eDC95bESQ,484
330
335
  bec_widgets/widgets/utility/spinner/spinner.py,sha256=6c0fN7mdGzELg4mf_yG08ubses3svb6w0EqMeHDFkIw,2651
@@ -356,8 +361,8 @@ bec_widgets/widgets/utility/visual/dark_mode_button/dark_mode_button.py,sha256=Z
356
361
  bec_widgets/widgets/utility/visual/dark_mode_button/dark_mode_button.pyproject,sha256=Lbi9zb6HNlIq14k6hlzR-oz6PIFShBuF7QxE6d87d64,34
357
362
  bec_widgets/widgets/utility/visual/dark_mode_button/dark_mode_button_plugin.py,sha256=CzChz2SSETYsR8-36meqWnsXCT-FIy_J_xeU5coWDY8,1350
358
363
  bec_widgets/widgets/utility/visual/dark_mode_button/register_dark_mode_button.py,sha256=rMpZ1CaoucwobgPj1FuKTnt07W82bV1GaSYdoqcdMb8,521
359
- bec_widgets-1.21.4.dist-info/METADATA,sha256=HHoSDKocVfOqHOQ1v4D0zGIt7bh21an3XrIT-fHdFaI,1173
360
- bec_widgets-1.21.4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
361
- bec_widgets-1.21.4.dist-info/entry_points.txt,sha256=dItMzmwA1wizJ1Itx15qnfJ0ZzKVYFLVJ1voxT7K7D4,214
362
- bec_widgets-1.21.4.dist-info/licenses/LICENSE,sha256=YRKe85CBRyP7UpEAWwU8_qSIyuy5-l_9C-HKg5Qm8MQ,1511
363
- bec_widgets-1.21.4.dist-info/RECORD,,
364
+ bec_widgets-1.23.0.dist-info/METADATA,sha256=WcdVimfKWBYa3l0ifogd51I_0VM7a9qy1uAOAj4oeEU,1173
365
+ bec_widgets-1.23.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
366
+ bec_widgets-1.23.0.dist-info/entry_points.txt,sha256=dItMzmwA1wizJ1Itx15qnfJ0ZzKVYFLVJ1voxT7K7D4,214
367
+ bec_widgets-1.23.0.dist-info/licenses/LICENSE,sha256=YRKe85CBRyP7UpEAWwU8_qSIyuy5-l_9C-HKg5Qm8MQ,1511
368
+ bec_widgets-1.23.0.dist-info/RECORD,,
pyproject.toml CHANGED
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "bec_widgets"
7
- version = "1.21.4"
7
+ version = "1.23.0"
8
8
  description = "BEC Widgets"
9
9
  requires-python = ">=3.10"
10
10
  classifiers = [