1
0
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:
2025-08-15 15:24:15 +02:00
parent 16073dfd6d
commit 9e2d0742ca
3 changed files with 487 additions and 105 deletions

View File

@@ -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())

View File

@@ -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):

View File

@@ -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]