diff --git a/bec_widgets/qt_utils/toolbar.py b/bec_widgets/qt_utils/toolbar.py index da17b221..3ae2f45b 100644 --- a/bec_widgets/qt_utils/toolbar.py +++ b/bec_widgets/qt_utils/toolbar.py @@ -486,6 +486,52 @@ class ModularToolBar(QToolBar): self.update_separators() # Update separators after adding the bundle + def add_action_to_bundle(self, bundle_id: str, action_id: str, action, target_widget: QWidget): + """ + Dynamically adds a new action to an existing toolbar bundle. + + Args: + bundle_id (str): The ID of the bundle to extend. + action_id (str): Unique identifier for the new action. + action (ToolBarAction): The action instance to add. + target_widget (QWidget): The target widget for the action. + """ + if bundle_id not in self.bundles: + raise ValueError(f"Bundle '{bundle_id}' does not exist.") + if action_id in self.widgets: + raise ValueError(f"Action with ID '{action_id}' already exists.") + + # Add the new action temporarily to create its QAction + action.add_to_toolbar(self, target_widget) + new_qaction = action.action + + # Remove the newly added action from its default position + self.removeAction(new_qaction) + + # Find the last action in the existing bundle to determine insertion point + bundle_action_ids = self.bundles[bundle_id] + if bundle_action_ids: + last_bundle_action = self.widgets[bundle_action_ids[-1]].action + actions_list = self.actions() + try: + index = actions_list.index(last_bundle_action) + except ValueError: + self.addAction(new_qaction) + else: + if index + 1 < len(actions_list): + before_action = actions_list[index + 1] + self.insertAction(before_action, new_qaction) + else: + self.addAction(new_qaction) + else: + # If bundle is empty, simply add the action at the end + self.addAction(new_qaction) + + # Update internal tracking + self.widgets[action_id] = action + self.bundles[bundle_id].append(action_id) + self.update_separators() + def contextMenuEvent(self, event): """ Overrides the context menu event to show a list of toolbar actions with checkboxes and icons, including separators. @@ -664,6 +710,13 @@ class MainWindow(QMainWindow): # pragma: no cover ) self.toolbar.add_bundle(second_bundle, target_widget=self) + new_action = MaterialIconAction( + icon_name="counter_1", tooltip="New Action", checkable=True, parent=self + ) + self.toolbar.add_action_to_bundle( + "main_actions", "new_action", new_action, target_widget=self + ) + if __name__ == "__main__": # pragma: no cover app = QApplication(sys.argv) diff --git a/tests/unit_tests/test_modular_toolbar.py b/tests/unit_tests/test_modular_toolbar.py index c54baade..17440232 100644 --- a/tests/unit_tests/test_modular_toolbar.py +++ b/tests/unit_tests/test_modular_toolbar.py @@ -318,6 +318,38 @@ def test_widget_action_calculate_minimum_width(qtbot): assert width > 100 +def test_add_action_to_bundle(toolbar_fixture, dummy_widget, material_icon_action): + # Create an initial bundle with one action + bundle = ToolbarBundle( + bundle_id="test_bundle", actions=[("initial_action", material_icon_action)] + ) + toolbar_fixture.add_bundle(bundle, dummy_widget) + + # Create a new action to add to the existing bundle + new_action = MaterialIconAction( + icon_name="counter_1", tooltip="New Action", checkable=True, parent=dummy_widget + ) + toolbar_fixture.add_action_to_bundle("test_bundle", "new_action", new_action, dummy_widget) + + # Verify the new action is registered in the toolbar's widgets + assert "new_action" in toolbar_fixture.widgets + assert toolbar_fixture.widgets["new_action"] == new_action + + # Verify the new action is included in the bundle tracking + assert "new_action" in toolbar_fixture.bundles["test_bundle"] + assert toolbar_fixture.bundles["test_bundle"][-1] == "new_action" + + # Verify the new action's QAction is present in the toolbar's action list + actions_list = toolbar_fixture.actions() + assert new_action.action in actions_list + + # Verify that the new action is inserted immediately after the last action of the bundle + last_bundle_action = material_icon_action.action + index_last = actions_list.index(last_bundle_action) + index_new = actions_list.index(new_action.action) + assert index_new == index_last + 1 + + # FIXME test is stucking CI, works locally # def test_context_menu_contains_added_actions( # qtbot, icon_action, material_icon_action, dummy_widget