mirror of
https://github.com/bec-project/bec_widgets.git
synced 2025-12-30 02:31:20 +01:00
refactor(advanced_dock_area): ads changed to separate widget
This commit is contained in:
@@ -5,8 +5,7 @@ from typing import cast
|
||||
|
||||
import PySide6QtAds as QtAds
|
||||
from PySide6QtAds import CDockManager, CDockWidget
|
||||
from qtpy.QtCore import QSettings, QSize, Qt
|
||||
from qtpy.QtGui import QAction
|
||||
from qtpy.QtCore import Property, QSettings, QSize, Signal
|
||||
from qtpy.QtWidgets import (
|
||||
QApplication,
|
||||
QCheckBox,
|
||||
@@ -39,7 +38,7 @@ from bec_widgets.widgets.containers.advanced_dock_area.toolbar_components.worksp
|
||||
WorkspaceConnection,
|
||||
workspace_bundle,
|
||||
)
|
||||
from bec_widgets.widgets.containers.main_window.main_window import BECMainWindow
|
||||
from bec_widgets.widgets.containers.main_window.main_window import BECMainWindowNoRPC
|
||||
from bec_widgets.widgets.control.device_control.positioner_box import PositionerBox
|
||||
from bec_widgets.widgets.control.scan_control import ScanControl
|
||||
from bec_widgets.widgets.editors.vscode.vscode import VSCodeEditor
|
||||
@@ -200,45 +199,64 @@ class SaveProfileDialog(QDialog):
|
||||
return self.readonly_checkbox.isChecked()
|
||||
|
||||
|
||||
class AdvancedDockArea(BECMainWindow):
|
||||
class AdvancedDockArea(BECWidget, QWidget):
|
||||
RPC = True
|
||||
PLUGIN = False
|
||||
USER_ACCESS = ["new", "widget_map", "widget_list", "lock_workspace", "attach_all", "delete_all"]
|
||||
|
||||
def __init__(self, parent=None, *args, **kwargs):
|
||||
# Define a signal for mode changes
|
||||
mode_changed = Signal(str)
|
||||
|
||||
def __init__(self, parent=None, mode: str = "developer", *args, **kwargs):
|
||||
super().__init__(parent=parent, *args, **kwargs)
|
||||
|
||||
# Title (as a top-level QWidget it can have a window title)
|
||||
self.setWindowTitle("Advanced Dock Area")
|
||||
|
||||
# Top-level layout hosting a toolbar and the dock manager
|
||||
self._root_layout = QVBoxLayout(self)
|
||||
self._root_layout.setContentsMargins(0, 0, 0, 0)
|
||||
self._root_layout.setSpacing(0)
|
||||
|
||||
# Setting the dock manager with flags
|
||||
QtAds.CDockManager.setConfigFlag(QtAds.CDockManager.eConfigFlag.FocusHighlighting, True)
|
||||
QtAds.CDockManager.setConfigFlag(
|
||||
QtAds.CDockManager.eConfigFlag.RetainTabSizeWhenCloseButtonHidden, True
|
||||
)
|
||||
QtAds.CDockManager.setConfigFlag(
|
||||
QtAds.CDockManager.eConfigFlag.HideSingleCentralWidgetTitleBar, True
|
||||
)
|
||||
self.dock_manager = CDockManager(self)
|
||||
|
||||
# Dock manager helper variables
|
||||
self._locked = False # Lock state of the workspace
|
||||
|
||||
# Initialize mode property first (before toolbar setup)
|
||||
self._mode = "developer"
|
||||
|
||||
# Toolbar
|
||||
self.dark_mode_button = DarkModeButton(parent=self, toolbar=True)
|
||||
self._setup_toolbar()
|
||||
self._hook_toolbar()
|
||||
|
||||
# Place toolbar and dock manager into layout
|
||||
self._root_layout.addWidget(self.toolbar)
|
||||
self._root_layout.addWidget(self.dock_manager, 1)
|
||||
|
||||
# Populate and hook the workspace combo
|
||||
self._refresh_workspace_list()
|
||||
|
||||
# State manager
|
||||
self.state_manager = WidgetStateManager(self)
|
||||
|
||||
# Insert Mode menu
|
||||
# Developer mode state
|
||||
self._editable = None
|
||||
self._setup_developer_mode_menu()
|
||||
# Initialize default editable state based on current lock
|
||||
self._set_editable(True) # default to editable; will sync toolbar toggle below
|
||||
|
||||
# Notification center re-raise
|
||||
self.notification_centre.raise_()
|
||||
self.statusBar().raise_()
|
||||
# Sync Developer toggle icon state after initial setup
|
||||
dev_action = self.toolbar.components.get_action("developer_mode").action
|
||||
dev_action.setChecked(self._editable)
|
||||
|
||||
# Apply the requested mode after everything is set up
|
||||
self.mode = mode
|
||||
|
||||
def minimumSizeHint(self):
|
||||
return QSize(1200, 800)
|
||||
@@ -350,6 +368,7 @@ class AdvancedDockArea(BECMainWindow):
|
||||
"sbb_monitor": ("train", "Add SBB Monitor", "SBBMonitor"),
|
||||
}
|
||||
|
||||
# Create expandable menu actions (original behavior)
|
||||
def _build_menu(key: str, label: str, mapping: dict[str, tuple[str, str, str]]):
|
||||
self.toolbar.components.add_safe(
|
||||
key,
|
||||
@@ -371,6 +390,27 @@ class AdvancedDockArea(BECMainWindow):
|
||||
_build_menu("menu_devices", "Add Device Control ", DEVICE_ACTIONS)
|
||||
_build_menu("menu_utils", "Add Utils ", UTIL_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)
|
||||
|
||||
for action_id, (icon_name, tooltip, widget_type) in mapping.items():
|
||||
# Create individual action for each widget type
|
||||
flat_action_id = f"flat_{action_id}"
|
||||
self.toolbar.components.add_safe(
|
||||
flat_action_id,
|
||||
MaterialIconAction(
|
||||
icon_name=icon_name, tooltip=tooltip, filled=True, parent=self
|
||||
),
|
||||
)
|
||||
bundle.add_action(flat_action_id)
|
||||
|
||||
self.toolbar.add_bundle(bundle)
|
||||
|
||||
_build_flat_bundles("plots", PLOT_ACTIONS)
|
||||
_build_flat_bundles("devices", DEVICE_ACTIONS)
|
||||
_build_flat_bundles("utils", UTIL_ACTIONS)
|
||||
|
||||
# Workspace
|
||||
spacer_bundle = ToolbarBundle("spacer_bundle", self.toolbar.components)
|
||||
spacer = QWidget(parent=self.toolbar.components.toolbar)
|
||||
@@ -398,12 +438,21 @@ class AdvancedDockArea(BECMainWindow):
|
||||
self.toolbar.components.add_safe(
|
||||
"dark_mode", WidgetAction(widget=self.dark_mode_button, adjust_size=False, parent=self)
|
||||
)
|
||||
# Developer mode toggle (moved from menu into toolbar)
|
||||
self.toolbar.components.add_safe(
|
||||
"developer_mode",
|
||||
MaterialIconAction(
|
||||
icon_name="code", tooltip="Developer Mode", checkable=True, parent=self
|
||||
),
|
||||
)
|
||||
bda = ToolbarBundle("dock_actions", self.toolbar.components)
|
||||
bda.add_action("attach_all")
|
||||
bda.add_action("screenshot")
|
||||
bda.add_action("dark_mode")
|
||||
bda.add_action("developer_mode")
|
||||
self.toolbar.add_bundle(bda)
|
||||
|
||||
# Default bundle configuration (show menus by default)
|
||||
self.toolbar.show_bundles(
|
||||
[
|
||||
"menu_plots",
|
||||
@@ -414,7 +463,6 @@ class AdvancedDockArea(BECMainWindow):
|
||||
"dock_actions",
|
||||
]
|
||||
)
|
||||
self.addToolBar(Qt.TopToolBarArea, self.toolbar)
|
||||
|
||||
# Store mappings on self for use in _hook_toolbar
|
||||
self._ACTION_MAPPINGS = {
|
||||
@@ -439,42 +487,26 @@ class AdvancedDockArea(BECMainWindow):
|
||||
_connect_menu("menu_devices")
|
||||
_connect_menu("menu_utils")
|
||||
|
||||
# Connect flat toolbar actions
|
||||
def _connect_flat_actions(category: str, mapping: dict[str, tuple[str, str, str]]):
|
||||
for action_id, (_, _, widget_type) in mapping.items():
|
||||
flat_action_id = f"flat_{action_id}"
|
||||
flat_action = self.toolbar.components.get_action(flat_action_id).action
|
||||
if widget_type == "LogPanel":
|
||||
flat_action.setEnabled(False) # keep disabled per issue #644
|
||||
else:
|
||||
flat_action.triggered.connect(lambda _, t=widget_type: self.new(widget=t))
|
||||
|
||||
_connect_flat_actions("plots", self._ACTION_MAPPINGS["menu_plots"])
|
||||
_connect_flat_actions("devices", self._ACTION_MAPPINGS["menu_devices"])
|
||||
_connect_flat_actions("utils", self._ACTION_MAPPINGS["menu_utils"])
|
||||
|
||||
self.toolbar.components.get_action("attach_all").action.triggered.connect(self.attach_all)
|
||||
self.toolbar.components.get_action("screenshot").action.triggered.connect(self.screenshot)
|
||||
|
||||
def _setup_developer_mode_menu(self):
|
||||
"""Add a 'Developer' checkbox to the View menu after theme actions."""
|
||||
mb = self.menuBar()
|
||||
|
||||
# Find the View menu (inherited from BECMainWindow)
|
||||
view_menu = None
|
||||
for action in mb.actions():
|
||||
if action.menu() and action.menu().title() == "View":
|
||||
view_menu = action.menu()
|
||||
break
|
||||
|
||||
if view_menu is None:
|
||||
# If View menu doesn't exist, create it
|
||||
view_menu = mb.addMenu("View")
|
||||
|
||||
# Add separator after existing theme actions
|
||||
view_menu.addSeparator()
|
||||
|
||||
# Add Developer mode checkbox
|
||||
self._developer_mode_action = QAction("Developer", self, checkable=True)
|
||||
|
||||
# Default selection based on current lock state
|
||||
self._editable = not self.lock_workspace
|
||||
self._developer_mode_action.setChecked(self._editable)
|
||||
|
||||
# Wire up action
|
||||
self._developer_mode_action.triggered.connect(self._on_developer_mode_toggled)
|
||||
|
||||
view_menu.addAction(self._developer_mode_action)
|
||||
|
||||
def _on_developer_mode_toggled(self, checked: bool) -> None:
|
||||
"""Handle developer mode checkbox toggle."""
|
||||
self._set_editable(checked)
|
||||
# Developer mode toggle
|
||||
self.toolbar.components.get_action("developer_mode").action.toggled.connect(
|
||||
self._on_developer_mode_toggled
|
||||
)
|
||||
|
||||
def _set_editable(self, editable: bool) -> None:
|
||||
self.lock_workspace = not editable
|
||||
@@ -504,8 +536,11 @@ class AdvancedDockArea(BECMainWindow):
|
||||
self.toolbar.show_bundles(["spacer_bundle", "workspace", "dock_actions"])
|
||||
|
||||
# Keep Developer mode UI in sync
|
||||
if hasattr(self, "_developer_mode_action"):
|
||||
self._developer_mode_action.setChecked(editable)
|
||||
self.toolbar.components.get_action("developer_mode").action.setChecked(editable)
|
||||
|
||||
def _on_developer_mode_toggled(self, checked: bool) -> None:
|
||||
"""Handle developer mode checkbox toggle."""
|
||||
self._set_editable(checked)
|
||||
|
||||
################################################################################
|
||||
# Adding widgets
|
||||
@@ -718,7 +753,9 @@ class AdvancedDockArea(BECMainWindow):
|
||||
# Save the profile
|
||||
settings = open_settings(name)
|
||||
settings.setValue(SETTINGS_KEYS["geom"], self.saveGeometry())
|
||||
settings.setValue(SETTINGS_KEYS["state"], self.saveState())
|
||||
settings.setValue(
|
||||
SETTINGS_KEYS["state"], b""
|
||||
) # No QMainWindow state; placeholder for backward compat
|
||||
settings.setValue(SETTINGS_KEYS["ads_state"], self.dock_manager.saveState())
|
||||
self.dock_manager.addPerspective(name)
|
||||
self.dock_manager.savePerspectives(settings)
|
||||
@@ -766,9 +803,8 @@ class AdvancedDockArea(BECMainWindow):
|
||||
geom = settings.value(SETTINGS_KEYS["geom"])
|
||||
if geom:
|
||||
self.restoreGeometry(geom)
|
||||
window_state = settings.value(SETTINGS_KEYS["state"])
|
||||
if window_state:
|
||||
self.restoreState(window_state)
|
||||
# No window state for QWidget-based host; keep for backwards compat read
|
||||
# window_state = settings.value(SETTINGS_KEYS["state"]) # ignored
|
||||
dock_state = settings.value(SETTINGS_KEYS["ads_state"])
|
||||
if dock_state:
|
||||
self.dock_manager.restoreState(dock_state)
|
||||
@@ -831,9 +867,60 @@ class AdvancedDockArea(BECMainWindow):
|
||||
combo.blockSignals(False)
|
||||
|
||||
################################################################################
|
||||
# Styling
|
||||
# Mode Switching
|
||||
################################################################################
|
||||
|
||||
@SafeProperty(str)
|
||||
def mode(self) -> str:
|
||||
return self._mode
|
||||
|
||||
@mode.setter
|
||||
def mode(self, new_mode: str):
|
||||
if new_mode not in ["plot", "device", "utils", "developer", "user"]:
|
||||
raise ValueError(f"Invalid mode: {new_mode}")
|
||||
self._mode = new_mode
|
||||
self.mode_changed.emit(new_mode)
|
||||
|
||||
# Update toolbar visibility based on mode
|
||||
if new_mode == "user":
|
||||
# User mode: show only essential tools
|
||||
self.toolbar.show_bundles(["spacer_bundle", "workspace", "dock_actions"])
|
||||
elif new_mode == "developer":
|
||||
# Developer mode: show all tools (use menu bundles)
|
||||
self.toolbar.show_bundles(
|
||||
[
|
||||
"menu_plots",
|
||||
"menu_devices",
|
||||
"menu_utils",
|
||||
"spacer_bundle",
|
||||
"workspace",
|
||||
"dock_actions",
|
||||
]
|
||||
)
|
||||
elif new_mode in ["plot", "device", "utils"]:
|
||||
# Specific modes: show flat toolbar for that category
|
||||
bundle_name = f"flat_{new_mode}s" if new_mode != "utils" else "flat_utils"
|
||||
self.toolbar.show_bundles([bundle_name])
|
||||
# self.toolbar.show_bundles([bundle_name, "spacer_bundle", "workspace", "dock_actions"])
|
||||
else:
|
||||
# Fallback to user mode
|
||||
self.toolbar.show_bundles(["spacer_bundle", "workspace", "dock_actions"])
|
||||
|
||||
def switch_to_plot_mode(self):
|
||||
self.mode = "plot"
|
||||
|
||||
def switch_to_device_mode(self):
|
||||
self.mode = "device"
|
||||
|
||||
def switch_to_utils_mode(self):
|
||||
self.mode = "utils"
|
||||
|
||||
def switch_to_developer_mode(self):
|
||||
self.mode = "developer"
|
||||
|
||||
def switch_to_user_mode(self):
|
||||
self.mode = "user"
|
||||
|
||||
def cleanup(self):
|
||||
"""
|
||||
Cleanup the dock area.
|
||||
@@ -841,6 +928,7 @@ class AdvancedDockArea(BECMainWindow):
|
||||
self.delete_all()
|
||||
self.dark_mode_button.close()
|
||||
self.dark_mode_button.deleteLater()
|
||||
self.toolbar.cleanup()
|
||||
super().cleanup()
|
||||
|
||||
|
||||
@@ -849,7 +937,9 @@ if __name__ == "__main__":
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
dispatcher = BECDispatcher(gui_id="ads")
|
||||
main_window = AdvancedDockArea()
|
||||
main_window.show()
|
||||
main_window.resize(800, 600)
|
||||
window = BECMainWindowNoRPC()
|
||||
ads = AdvancedDockArea(parent=window, mode="developer")
|
||||
window.setCentralWidget(ads)
|
||||
window.show()
|
||||
window.resize(800, 600)
|
||||
sys.exit(app.exec())
|
||||
|
||||
@@ -7,6 +7,11 @@ from qtpy.QtWidgets import QComboBox, QSizePolicy, QWidget
|
||||
from bec_widgets import SafeSlot
|
||||
from bec_widgets.utils.toolbars.actions import MaterialIconAction, WidgetAction
|
||||
from bec_widgets.utils.toolbars.bundles import ToolbarBundle, ToolbarComponents
|
||||
from bec_widgets.utils.toolbars.connections import BundleConnection
|
||||
from bec_widgets.widgets.containers.advanced_dock_area.profile_utils import (
|
||||
is_profile_readonly,
|
||||
list_profiles,
|
||||
)
|
||||
|
||||
|
||||
class ProfileComboBox(QComboBox):
|
||||
@@ -18,7 +23,6 @@ class ProfileComboBox(QComboBox):
|
||||
|
||||
def refresh_profiles(self):
|
||||
"""Refresh the profile list with appropriate icons."""
|
||||
from ..advanced_dock_area import is_profile_readonly, list_profiles
|
||||
|
||||
current_text = self.currentText()
|
||||
self.blockSignals(True)
|
||||
@@ -107,18 +111,18 @@ def workspace_bundle(components: ToolbarComponents) -> ToolbarBundle:
|
||||
return bundle
|
||||
|
||||
|
||||
class WorkspaceConnection:
|
||||
class WorkspaceConnection(BundleConnection):
|
||||
"""
|
||||
Connection class for workspace actions in AdvancedDockArea.
|
||||
"""
|
||||
|
||||
def __init__(self, components: ToolbarComponents, target_widget=None):
|
||||
super().__init__(parent=components.toolbar)
|
||||
self.bundle_name = "workspace"
|
||||
self.components = components
|
||||
self.target_widget = target_widget
|
||||
if not hasattr(self.target_widget, "lock_workspace"):
|
||||
raise AttributeError("Target widget must implement 'lock_workspace'.")
|
||||
super().__init__()
|
||||
self._connected = False
|
||||
|
||||
def connect(self):
|
||||
@@ -155,6 +159,7 @@ class WorkspaceConnection:
|
||||
self.components.get_action("delete_workspace").action.triggered.disconnect(
|
||||
self.target_widget.delete_profile
|
||||
)
|
||||
self._connected = False
|
||||
|
||||
@SafeSlot(bool)
|
||||
def _lock_workspace(self, value: bool):
|
||||
|
||||
@@ -6,8 +6,7 @@ from unittest import mock
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
from qtpy.QtCore import QSettings, Qt
|
||||
from qtpy.QtGui import QAction
|
||||
from qtpy.QtCore import QSettings
|
||||
from qtpy.QtWidgets import QDialog, QMessageBox
|
||||
|
||||
from bec_widgets.widgets.containers.advanced_dock_area.advanced_dock_area import (
|
||||
@@ -50,6 +49,7 @@ class TestAdvancedDockAreaInit:
|
||||
def test_init(self, advanced_dock_area):
|
||||
assert advanced_dock_area is not None
|
||||
assert isinstance(advanced_dock_area, AdvancedDockArea)
|
||||
assert advanced_dock_area.mode == "developer"
|
||||
assert hasattr(advanced_dock_area, "dock_manager")
|
||||
assert hasattr(advanced_dock_area, "toolbar")
|
||||
assert hasattr(advanced_dock_area, "dark_mode_button")
|
||||
@@ -173,28 +173,6 @@ class TestDockManagement:
|
||||
new_widget_list = advanced_dock_area.widget_list()
|
||||
assert len(new_widget_list) == initial_count + 1
|
||||
|
||||
def test_attach_all(self, advanced_dock_area, qtbot):
|
||||
"""Test attach_all functionality."""
|
||||
# Create multiple widgets
|
||||
advanced_dock_area.new("DarkModeButton", start_floating=True)
|
||||
advanced_dock_area.new("DarkModeButton", start_floating=True)
|
||||
|
||||
# Wait for docks to be created
|
||||
qtbot.wait(200)
|
||||
|
||||
# Should have floating widgets
|
||||
initial_floating = len(advanced_dock_area.dock_manager.floatingWidgets())
|
||||
|
||||
# Attach all floating docks
|
||||
advanced_dock_area.attach_all()
|
||||
|
||||
# Wait a bit for the operation to complete
|
||||
qtbot.wait(200)
|
||||
|
||||
# Should have fewer floating widgets (or none if all were attached)
|
||||
final_floating = len(advanced_dock_area.dock_manager.floatingWidgets())
|
||||
assert final_floating <= initial_floating
|
||||
|
||||
def test_delete_all(self, advanced_dock_area, qtbot):
|
||||
"""Test delete_all functionality."""
|
||||
# Create multiple widgets
|
||||
@@ -252,13 +230,6 @@ class TestWorkspaceLocking:
|
||||
class TestDeveloperMode:
|
||||
"""Test developer mode functionality."""
|
||||
|
||||
def test_setup_developer_mode_menu(self, advanced_dock_area):
|
||||
"""Test developer mode menu setup."""
|
||||
# The menu should be set up during initialization
|
||||
assert hasattr(advanced_dock_area, "_developer_mode_action")
|
||||
assert isinstance(advanced_dock_area._developer_mode_action, QAction)
|
||||
assert advanced_dock_area._developer_mode_action.isCheckable()
|
||||
|
||||
def test_developer_mode_toggle(self, advanced_dock_area):
|
||||
"""Test developer mode toggle functionality."""
|
||||
# Check initial state
|
||||
@@ -715,19 +686,6 @@ class TestWorkspaceProfileOperations:
|
||||
class TestCleanupAndMisc:
|
||||
"""Test cleanup and miscellaneous functionality."""
|
||||
|
||||
def test_cleanup(self, advanced_dock_area):
|
||||
"""Test cleanup functionality."""
|
||||
with patch.object(advanced_dock_area.dark_mode_button, "close") as mock_close:
|
||||
with patch.object(advanced_dock_area.dark_mode_button, "deleteLater") as mock_delete:
|
||||
with patch(
|
||||
"bec_widgets.widgets.containers.main_window.main_window.BECMainWindow.cleanup"
|
||||
) as mock_super_cleanup:
|
||||
advanced_dock_area.cleanup()
|
||||
|
||||
mock_close.assert_called_once()
|
||||
mock_delete.assert_called_once()
|
||||
mock_super_cleanup.assert_called_once()
|
||||
|
||||
def test_delete_dock(self, advanced_dock_area, qtbot):
|
||||
"""Test _delete_dock functionality."""
|
||||
from bec_widgets.widgets.utility.visual.dark_mode_button.dark_mode_button import (
|
||||
@@ -804,3 +762,332 @@ class TestCleanupAndMisc:
|
||||
# Verify title bar actions were set
|
||||
title_bar_actions = dock.titleBarActions()
|
||||
assert len(title_bar_actions) >= 1
|
||||
|
||||
|
||||
class TestModeSwitching:
|
||||
"""Test mode switching functionality."""
|
||||
|
||||
def test_mode_property_setter_valid_modes(self, advanced_dock_area):
|
||||
"""Test setting valid modes."""
|
||||
valid_modes = ["plot", "device", "utils", "developer", "user"]
|
||||
|
||||
for mode in valid_modes:
|
||||
advanced_dock_area.mode = mode
|
||||
assert advanced_dock_area.mode == mode
|
||||
|
||||
def test_mode_changed_signal_emission(self, advanced_dock_area, qtbot):
|
||||
"""Test that mode_changed signal is emitted when mode changes."""
|
||||
# Set up signal spy
|
||||
with qtbot.waitSignal(advanced_dock_area.mode_changed, timeout=1000) as blocker:
|
||||
advanced_dock_area.mode = "plot"
|
||||
|
||||
# Check signal was emitted with correct argument
|
||||
assert blocker.args == ["plot"]
|
||||
|
||||
def test_switch_to_plot_mode(self, advanced_dock_area):
|
||||
"""Test switch_to_plot_mode method."""
|
||||
advanced_dock_area.switch_to_plot_mode()
|
||||
assert advanced_dock_area.mode == "plot"
|
||||
|
||||
def test_switch_to_device_mode(self, advanced_dock_area):
|
||||
"""Test switch_to_device_mode method."""
|
||||
advanced_dock_area.switch_to_device_mode()
|
||||
assert advanced_dock_area.mode == "device"
|
||||
|
||||
def test_switch_to_utils_mode(self, advanced_dock_area):
|
||||
"""Test switch_to_utils_mode method."""
|
||||
advanced_dock_area.switch_to_utils_mode()
|
||||
assert advanced_dock_area.mode == "utils"
|
||||
|
||||
def test_switch_to_developer_mode(self, advanced_dock_area):
|
||||
"""Test switch_to_developer_mode method."""
|
||||
advanced_dock_area.switch_to_developer_mode()
|
||||
assert advanced_dock_area.mode == "developer"
|
||||
|
||||
def test_switch_to_user_mode(self, advanced_dock_area):
|
||||
"""Test switch_to_user_mode method."""
|
||||
advanced_dock_area.switch_to_user_mode()
|
||||
assert advanced_dock_area.mode == "user"
|
||||
|
||||
|
||||
class TestToolbarModeBundles:
|
||||
"""Test toolbar bundle creation and visibility for different modes."""
|
||||
|
||||
def test_flat_bundles_created(self, advanced_dock_area):
|
||||
"""Test that flat bundles are created during toolbar setup."""
|
||||
# Check that flat bundles exist
|
||||
assert "flat_plots" in advanced_dock_area.toolbar.bundles
|
||||
assert "flat_devices" in advanced_dock_area.toolbar.bundles
|
||||
assert "flat_utils" in advanced_dock_area.toolbar.bundles
|
||||
|
||||
def test_plot_mode_toolbar_visibility(self, advanced_dock_area):
|
||||
"""Test toolbar bundle visibility in plot mode."""
|
||||
advanced_dock_area.mode = "plot"
|
||||
|
||||
# Should show only flat_plots bundle (and essential bundles in real implementation)
|
||||
shown_bundles = advanced_dock_area.toolbar.shown_bundles
|
||||
assert "flat_plots" in shown_bundles
|
||||
|
||||
# Should not show other flat bundles
|
||||
assert "flat_devices" not in shown_bundles
|
||||
assert "flat_utils" not in shown_bundles
|
||||
|
||||
# Should not show menu bundles
|
||||
assert "menu_plots" not in shown_bundles
|
||||
assert "menu_devices" not in shown_bundles
|
||||
assert "menu_utils" not in shown_bundles
|
||||
|
||||
def test_device_mode_toolbar_visibility(self, advanced_dock_area):
|
||||
"""Test toolbar bundle visibility in device mode."""
|
||||
advanced_dock_area.mode = "device"
|
||||
|
||||
shown_bundles = advanced_dock_area.toolbar.shown_bundles
|
||||
assert "flat_devices" in shown_bundles
|
||||
|
||||
# Should not show other flat bundles
|
||||
assert "flat_plots" not in shown_bundles
|
||||
assert "flat_utils" not in shown_bundles
|
||||
|
||||
def test_utils_mode_toolbar_visibility(self, advanced_dock_area):
|
||||
"""Test toolbar bundle visibility in utils mode."""
|
||||
advanced_dock_area.mode = "utils"
|
||||
|
||||
shown_bundles = advanced_dock_area.toolbar.shown_bundles
|
||||
assert "flat_utils" in shown_bundles
|
||||
|
||||
# Should not show other flat bundles
|
||||
assert "flat_plots" not in shown_bundles
|
||||
assert "flat_devices" not in shown_bundles
|
||||
|
||||
def test_developer_mode_toolbar_visibility(self, advanced_dock_area):
|
||||
"""Test toolbar bundle visibility in developer mode."""
|
||||
advanced_dock_area.mode = "developer"
|
||||
|
||||
shown_bundles = advanced_dock_area.toolbar.shown_bundles
|
||||
|
||||
# Should show menu bundles
|
||||
assert "menu_plots" in shown_bundles
|
||||
assert "menu_devices" in shown_bundles
|
||||
assert "menu_utils" in shown_bundles
|
||||
|
||||
# Should show essential bundles
|
||||
assert "spacer_bundle" in shown_bundles
|
||||
assert "workspace" in shown_bundles
|
||||
assert "dock_actions" in shown_bundles
|
||||
|
||||
def test_user_mode_toolbar_visibility(self, advanced_dock_area):
|
||||
"""Test toolbar bundle visibility in user mode."""
|
||||
advanced_dock_area.mode = "user"
|
||||
|
||||
shown_bundles = advanced_dock_area.toolbar.shown_bundles
|
||||
|
||||
# Should show only essential bundles
|
||||
assert "spacer_bundle" in shown_bundles
|
||||
assert "workspace" in shown_bundles
|
||||
assert "dock_actions" in shown_bundles
|
||||
|
||||
# Should not show any widget creation bundles
|
||||
assert "menu_plots" not in shown_bundles
|
||||
assert "menu_devices" not in shown_bundles
|
||||
assert "menu_utils" not in shown_bundles
|
||||
assert "flat_plots" not in shown_bundles
|
||||
assert "flat_devices" not in shown_bundles
|
||||
assert "flat_utils" not in shown_bundles
|
||||
|
||||
|
||||
class TestFlatToolbarActions:
|
||||
"""Test flat toolbar actions functionality."""
|
||||
|
||||
def test_flat_plot_actions_created(self, advanced_dock_area):
|
||||
"""Test that flat plot actions are created."""
|
||||
plot_actions = [
|
||||
"flat_waveform",
|
||||
"flat_scatter_waveform",
|
||||
"flat_multi_waveform",
|
||||
"flat_image",
|
||||
"flat_motor_map",
|
||||
"flat_heatmap",
|
||||
]
|
||||
|
||||
for action_name in plot_actions:
|
||||
assert advanced_dock_area.toolbar.components.exists(action_name)
|
||||
|
||||
def test_flat_device_actions_created(self, advanced_dock_area):
|
||||
"""Test that flat device actions are created."""
|
||||
device_actions = ["flat_scan_control", "flat_positioner_box"]
|
||||
|
||||
for action_name in device_actions:
|
||||
assert advanced_dock_area.toolbar.components.exists(action_name)
|
||||
|
||||
def test_flat_utils_actions_created(self, advanced_dock_area):
|
||||
"""Test that flat utils actions are created."""
|
||||
utils_actions = [
|
||||
"flat_queue",
|
||||
"flat_vs_code",
|
||||
"flat_status",
|
||||
"flat_progress_bar",
|
||||
"flat_log_panel",
|
||||
"flat_sbb_monitor",
|
||||
]
|
||||
|
||||
for action_name in utils_actions:
|
||||
assert advanced_dock_area.toolbar.components.exists(action_name)
|
||||
|
||||
def test_flat_plot_actions_trigger_widget_creation(self, advanced_dock_area):
|
||||
"""Test flat plot actions trigger widget creation."""
|
||||
plot_action_mapping = {
|
||||
"flat_waveform": "Waveform",
|
||||
"flat_scatter_waveform": "ScatterWaveform",
|
||||
"flat_multi_waveform": "MultiWaveform",
|
||||
"flat_image": "Image",
|
||||
"flat_motor_map": "MotorMap",
|
||||
"flat_heatmap": "Heatmap",
|
||||
}
|
||||
|
||||
for action_name, widget_type in plot_action_mapping.items():
|
||||
with patch.object(advanced_dock_area, "new") as mock_new:
|
||||
action = advanced_dock_area.toolbar.components.get_action(action_name).action
|
||||
action.trigger()
|
||||
mock_new.assert_called_once_with(widget=widget_type)
|
||||
|
||||
def test_flat_device_actions_trigger_widget_creation(self, advanced_dock_area):
|
||||
"""Test flat device actions trigger widget creation."""
|
||||
device_action_mapping = {
|
||||
"flat_scan_control": "ScanControl",
|
||||
"flat_positioner_box": "PositionerBox",
|
||||
}
|
||||
|
||||
for action_name, widget_type in device_action_mapping.items():
|
||||
with patch.object(advanced_dock_area, "new") as mock_new:
|
||||
action = advanced_dock_area.toolbar.components.get_action(action_name).action
|
||||
action.trigger()
|
||||
mock_new.assert_called_once_with(widget=widget_type)
|
||||
|
||||
def test_flat_utils_actions_trigger_widget_creation(self, advanced_dock_area):
|
||||
"""Test flat utils actions trigger widget creation."""
|
||||
utils_action_mapping = {
|
||||
"flat_queue": "BECQueue",
|
||||
"flat_vs_code": "VSCodeEditor",
|
||||
"flat_status": "BECStatusBox",
|
||||
"flat_progress_bar": "RingProgressBar",
|
||||
"flat_sbb_monitor": "SBBMonitor",
|
||||
}
|
||||
|
||||
for action_name, widget_type in utils_action_mapping.items():
|
||||
with patch.object(advanced_dock_area, "new") as mock_new:
|
||||
action = advanced_dock_area.toolbar.components.get_action(action_name).action
|
||||
|
||||
# Skip log_panel as it's disabled
|
||||
if action_name == "flat_log_panel":
|
||||
assert not action.isEnabled()
|
||||
continue
|
||||
|
||||
action.trigger()
|
||||
mock_new.assert_called_once_with(widget=widget_type)
|
||||
|
||||
def test_flat_log_panel_action_disabled(self, advanced_dock_area):
|
||||
"""Test that flat log panel action is disabled."""
|
||||
action = advanced_dock_area.toolbar.components.get_action("flat_log_panel").action
|
||||
assert not action.isEnabled()
|
||||
|
||||
|
||||
class TestModeTransitions:
|
||||
"""Test mode transitions and state consistency."""
|
||||
|
||||
def test_mode_transition_sequence(self, advanced_dock_area, qtbot):
|
||||
"""Test sequence of mode transitions."""
|
||||
modes = ["plot", "device", "utils", "developer", "user"]
|
||||
|
||||
for mode in modes:
|
||||
with qtbot.waitSignal(advanced_dock_area.mode_changed, timeout=1000) as blocker:
|
||||
advanced_dock_area.mode = mode
|
||||
|
||||
assert advanced_dock_area.mode == mode
|
||||
assert blocker.args == [mode]
|
||||
|
||||
def test_mode_consistency_after_multiple_changes(self, advanced_dock_area):
|
||||
"""Test mode consistency after multiple rapid changes."""
|
||||
# Rapidly change modes
|
||||
advanced_dock_area.mode = "plot"
|
||||
advanced_dock_area.mode = "device"
|
||||
advanced_dock_area.mode = "utils"
|
||||
advanced_dock_area.mode = "developer"
|
||||
advanced_dock_area.mode = "user"
|
||||
|
||||
# Final state should be consistent
|
||||
assert advanced_dock_area.mode == "user"
|
||||
|
||||
# Toolbar should show correct bundles for user mode
|
||||
shown_bundles = advanced_dock_area.toolbar.shown_bundles
|
||||
assert "spacer_bundle" in shown_bundles
|
||||
assert "workspace" in shown_bundles
|
||||
assert "dock_actions" in shown_bundles
|
||||
|
||||
def test_toolbar_refresh_on_mode_change(self, advanced_dock_area):
|
||||
"""Test that toolbar is properly refreshed when mode changes."""
|
||||
initial_bundles = set(advanced_dock_area.toolbar.shown_bundles)
|
||||
|
||||
# Change to a different mode
|
||||
advanced_dock_area.mode = "plot"
|
||||
plot_bundles = set(advanced_dock_area.toolbar.shown_bundles)
|
||||
|
||||
# Bundles should be different
|
||||
assert initial_bundles != plot_bundles
|
||||
assert "flat_plots" in plot_bundles
|
||||
|
||||
def test_mode_switching_preserves_existing_docks(self, advanced_dock_area, qtbot):
|
||||
"""Test that mode switching doesn't affect existing docked widgets."""
|
||||
# Create some widgets
|
||||
advanced_dock_area.new("DarkModeButton")
|
||||
advanced_dock_area.new("DarkModeButton")
|
||||
qtbot.wait(200)
|
||||
|
||||
initial_dock_count = len(advanced_dock_area.dock_list())
|
||||
initial_widget_count = len(advanced_dock_area.widget_list())
|
||||
|
||||
# Switch modes
|
||||
advanced_dock_area.mode = "plot"
|
||||
advanced_dock_area.mode = "device"
|
||||
advanced_dock_area.mode = "user"
|
||||
|
||||
# Dock and widget counts should remain the same
|
||||
assert len(advanced_dock_area.dock_list()) == initial_dock_count
|
||||
assert len(advanced_dock_area.widget_list()) == initial_widget_count
|
||||
|
||||
|
||||
class TestModeProperty:
|
||||
"""Test mode property getter and setter behavior."""
|
||||
|
||||
def test_mode_property_getter(self, advanced_dock_area):
|
||||
"""Test mode property getter returns correct value."""
|
||||
# Set internal mode directly and test getter
|
||||
advanced_dock_area._mode = "plot"
|
||||
assert advanced_dock_area.mode == "plot"
|
||||
|
||||
advanced_dock_area._mode = "device"
|
||||
assert advanced_dock_area.mode == "device"
|
||||
|
||||
def test_mode_property_setter_updates_internal_state(self, advanced_dock_area):
|
||||
"""Test mode property setter updates internal state."""
|
||||
advanced_dock_area.mode = "plot"
|
||||
assert advanced_dock_area._mode == "plot"
|
||||
|
||||
advanced_dock_area.mode = "utils"
|
||||
assert advanced_dock_area._mode == "utils"
|
||||
|
||||
def test_mode_property_setter_triggers_toolbar_update(self, advanced_dock_area):
|
||||
"""Test mode property setter triggers toolbar update."""
|
||||
with patch.object(advanced_dock_area.toolbar, "show_bundles") as mock_show_bundles:
|
||||
advanced_dock_area.mode = "plot"
|
||||
mock_show_bundles.assert_called_once()
|
||||
|
||||
def test_multiple_mode_changes(self, advanced_dock_area, qtbot):
|
||||
"""Test multiple rapid mode changes."""
|
||||
modes = ["plot", "device", "utils", "developer", "user"]
|
||||
|
||||
for i, mode in enumerate(modes):
|
||||
with qtbot.waitSignal(advanced_dock_area.mode_changed, timeout=1000) as blocker:
|
||||
advanced_dock_area.mode = mode
|
||||
|
||||
assert advanced_dock_area.mode == mode
|
||||
assert blocker.args == [mode]
|
||||
|
||||
Reference in New Issue
Block a user