mirror of
https://github.com/bec-project/bec_widgets.git
synced 2026-05-05 06:16:32 +02:00
feat: add plugin widgets menu to BECDockArea toolbar
Agent-Logs-Url: https://github.com/bec-project/bec_widgets/sessions/872a29f8-acdf-42e5-94b9-bad871aef4a0 Co-authored-by: wyzula-jan <133381102+wyzula-jan@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
2d2647c20b
commit
9b070f2031
@@ -21,6 +21,7 @@ import bec_widgets.widgets.containers.qt_ads as QtAds
|
||||
from bec_widgets import BECWidget, SafeProperty, SafeSlot
|
||||
from bec_widgets.applications.views.view import ViewTourSteps
|
||||
from bec_widgets.utils.bec_dispatcher import BECDispatcher
|
||||
from bec_widgets.utils.bec_plugin_helper import get_all_plugin_widgets
|
||||
from bec_widgets.utils.colors import apply_theme
|
||||
from bec_widgets.utils.rpc_decorator import rpc_timeout
|
||||
from bec_widgets.utils.rpc_widget_handler import widget_handler
|
||||
@@ -398,6 +399,19 @@ class BECDockArea(DockAreaWidget):
|
||||
_build_menu("menu_devices", "Add Device Control ", device_actions)
|
||||
_build_menu("menu_utils", "Add Utils ", util_actions)
|
||||
|
||||
# Build plugin widget menu (only shown when plugin widgets are available)
|
||||
plugin_widgets_dict = get_all_plugin_widgets().as_dict()
|
||||
plugin_actions: dict[str, tuple[str, str, str]] = {
|
||||
widget_name: (
|
||||
getattr(widget_cls, "ICON_NAME", "widgets"),
|
||||
f"Add {widget_name}",
|
||||
widget_name,
|
||||
)
|
||||
for widget_name, widget_cls in plugin_widgets_dict.items()
|
||||
}
|
||||
if plugin_actions:
|
||||
_build_menu("menu_plugins", "Add Plugins ", plugin_actions)
|
||||
|
||||
# Create flat toolbar bundles for each widget type
|
||||
def _build_flat_bundles(category: str, mapping: dict[str, tuple[str, str, str]]):
|
||||
bundle = ToolbarBundle(f"flat_{category}", self.toolbar.components)
|
||||
@@ -468,14 +482,16 @@ class BECDockArea(DockAreaWidget):
|
||||
bda.add_action("dark_mode")
|
||||
self.toolbar.add_bundle(bda)
|
||||
|
||||
self._apply_toolbar_layout()
|
||||
|
||||
# Store mappings on self for use in _hook_toolbar
|
||||
# Store mappings on self for use in _hook_toolbar and _apply_toolbar_layout
|
||||
self._ACTION_MAPPINGS = {
|
||||
"menu_plots": plot_actions,
|
||||
"menu_devices": device_actions,
|
||||
"menu_utils": util_actions,
|
||||
}
|
||||
if plugin_actions:
|
||||
self._ACTION_MAPPINGS["menu_plugins"] = plugin_actions
|
||||
|
||||
self._apply_toolbar_layout()
|
||||
|
||||
def _hook_toolbar(self):
|
||||
def _connect_menu(menu_key: str):
|
||||
@@ -501,6 +517,8 @@ class BECDockArea(DockAreaWidget):
|
||||
_connect_menu("menu_plots")
|
||||
_connect_menu("menu_devices")
|
||||
_connect_menu("menu_utils")
|
||||
if "menu_plugins" in self._ACTION_MAPPINGS:
|
||||
_connect_menu("menu_plugins")
|
||||
|
||||
def _connect_flat_actions(mapping: dict[str, tuple[str, str, str]]):
|
||||
for action_id, (_, _, widget_type) in mapping.items():
|
||||
@@ -1109,6 +1127,10 @@ class BECDockArea(DockAreaWidget):
|
||||
"menu_plots",
|
||||
"menu_devices",
|
||||
"menu_utils",
|
||||
]
|
||||
if "menu_plugins" in getattr(self, "_ACTION_MAPPINGS", {}):
|
||||
bundles.append("menu_plugins")
|
||||
bundles += [
|
||||
"spacer_bundle",
|
||||
"workspace",
|
||||
"dock_actions",
|
||||
|
||||
@@ -1024,6 +1024,89 @@ class TestToolbarFunctionality:
|
||||
# Verify save was called with the filename
|
||||
mock_screenshot.save.assert_called_once_with(str(screenshot_path))
|
||||
|
||||
def test_plugin_menu_not_shown_when_no_plugins(self, qtbot, mocked_client):
|
||||
"""Test that the plugin menu is not shown when there are no plugin widgets."""
|
||||
with patch(
|
||||
"bec_widgets.widgets.containers.dock_area.dock_area.get_all_plugin_widgets"
|
||||
) as mock_plugins:
|
||||
from bec_widgets.utils.plugin_utils import BECClassContainer
|
||||
|
||||
mock_plugins.return_value = BECClassContainer()
|
||||
widget = BECDockArea(client=mocked_client)
|
||||
qtbot.addWidget(widget)
|
||||
qtbot.waitExposed(widget)
|
||||
|
||||
assert "menu_plugins" not in widget._ACTION_MAPPINGS
|
||||
# Verify no "menu_plugins" bundle exists in the toolbar
|
||||
assert "menu_plugins" not in widget.toolbar.bundles
|
||||
|
||||
def test_plugin_menu_shown_when_plugins_available(self, qtbot, mocked_client):
|
||||
"""Test that the plugin menu is shown when plugin widgets are available."""
|
||||
from bec_widgets.utils.bec_widget import BECWidget
|
||||
from bec_widgets.utils.plugin_utils import BECClassContainer, BECClassInfo
|
||||
from qtpy.QtWidgets import QWidget as _QWidget
|
||||
|
||||
class FakePluginWidget(BECWidget, _QWidget):
|
||||
ICON_NAME = "star"
|
||||
PLUGIN = True
|
||||
|
||||
def __init__(self, parent=None, **kwargs):
|
||||
super().__init__(parent=parent, **kwargs)
|
||||
|
||||
container = BECClassContainer(
|
||||
[BECClassInfo(name="FakePluginWidget", module="fake", file="fake.py", obj=FakePluginWidget)]
|
||||
)
|
||||
|
||||
with patch(
|
||||
"bec_widgets.widgets.containers.dock_area.dock_area.get_all_plugin_widgets"
|
||||
) as mock_plugins:
|
||||
mock_plugins.return_value = container
|
||||
# Make as_dict() return our fake widget
|
||||
container_mock = MagicMock()
|
||||
container_mock.as_dict.return_value = {"FakePluginWidget": FakePluginWidget}
|
||||
mock_plugins.return_value = container_mock
|
||||
|
||||
widget = BECDockArea(client=mocked_client)
|
||||
qtbot.addWidget(widget)
|
||||
qtbot.waitExposed(widget)
|
||||
|
||||
assert "menu_plugins" in widget._ACTION_MAPPINGS
|
||||
assert "FakePluginWidget" in widget._ACTION_MAPPINGS["menu_plugins"]
|
||||
plugin_entry = widget._ACTION_MAPPINGS["menu_plugins"]["FakePluginWidget"]
|
||||
assert plugin_entry[0] == "star" # icon name
|
||||
assert plugin_entry[1] == "Add FakePluginWidget" # tooltip
|
||||
assert plugin_entry[2] == "FakePluginWidget" # widget type name
|
||||
assert "menu_plugins" in widget.toolbar.bundles
|
||||
|
||||
def test_plugin_menu_action_triggers_new(self, qtbot, mocked_client):
|
||||
"""Test that clicking a plugin menu action creates a new dock with the plugin widget."""
|
||||
from bec_widgets.utils.bec_widget import BECWidget
|
||||
from qtpy.QtWidgets import QWidget as _QWidget
|
||||
|
||||
class FakePluginWidget(BECWidget, _QWidget):
|
||||
ICON_NAME = "star"
|
||||
PLUGIN = True
|
||||
|
||||
def __init__(self, parent=None, **kwargs):
|
||||
super().__init__(parent=parent, **kwargs)
|
||||
|
||||
with patch(
|
||||
"bec_widgets.widgets.containers.dock_area.dock_area.get_all_plugin_widgets"
|
||||
) as mock_plugins:
|
||||
container_mock = MagicMock()
|
||||
container_mock.as_dict.return_value = {"FakePluginWidget": FakePluginWidget}
|
||||
mock_plugins.return_value = container_mock
|
||||
|
||||
widget = BECDockArea(client=mocked_client)
|
||||
qtbot.addWidget(widget)
|
||||
qtbot.waitExposed(widget)
|
||||
|
||||
with patch.object(widget, "new") as mock_new:
|
||||
menu_plugins = widget.toolbar.components.get_action("menu_plugins")
|
||||
action = menu_plugins.actions["FakePluginWidget"].action
|
||||
action.trigger()
|
||||
mock_new.assert_called_once_with(widget="FakePluginWidget")
|
||||
|
||||
|
||||
class TestDockSettingsDialog:
|
||||
"""Test dock settings dialog functionality."""
|
||||
|
||||
Reference in New Issue
Block a user