mirror of
https://github.com/bec-project/bec_widgets.git
synced 2026-05-05 06:16:32 +02:00
330 lines
12 KiB
Python
330 lines
12 KiB
Python
from __future__ import annotations
|
|
|
|
from collections import defaultdict
|
|
from typing import TYPE_CHECKING, DefaultDict
|
|
from weakref import ReferenceType
|
|
|
|
import louie
|
|
from bec_lib.logger import bec_logger
|
|
from pydantic import BaseModel
|
|
from qtpy.QtCore import Qt
|
|
from qtpy.QtWidgets import QSizePolicy
|
|
|
|
from bec_widgets.utils.toolbars.actions import SeparatorAction, SplitterAction, ToolBarAction
|
|
|
|
DEFAULT_SIZE = 400
|
|
MAX_SIZE = 10_000_000
|
|
|
|
if TYPE_CHECKING:
|
|
from qtpy.QtWidgets import QWidget
|
|
|
|
from bec_widgets.utils.toolbars.connections import BundleConnection
|
|
from bec_widgets.utils.toolbars.toolbar import ModularToolBar
|
|
|
|
logger = bec_logger.logger
|
|
|
|
|
|
class ActionInfo(BaseModel):
|
|
action: ToolBarAction
|
|
toolbar_bundle: ToolbarBundle | None = None
|
|
|
|
model_config = {"arbitrary_types_allowed": True}
|
|
|
|
|
|
class ToolbarComponents:
|
|
def __init__(self, toolbar: ModularToolBar):
|
|
"""
|
|
Initializes the toolbar components.
|
|
|
|
Args:
|
|
toolbar (ModularToolBar): The toolbar to which the components will be added.
|
|
"""
|
|
self.toolbar = toolbar
|
|
|
|
self._components: dict[str, ActionInfo] = {}
|
|
self.add("separator", SeparatorAction())
|
|
|
|
def add(self, name: str, component: ToolBarAction):
|
|
"""
|
|
Adds a component to the toolbar.
|
|
|
|
Args:
|
|
component (ToolBarAction): The component to add.
|
|
"""
|
|
if name in self._components:
|
|
raise ValueError(f"Component with name '{name}' already exists.")
|
|
self._components[name] = ActionInfo(action=component, toolbar_bundle=None)
|
|
|
|
def add_safe(self, name: str, component: ToolBarAction):
|
|
"""
|
|
Adds a component to the toolbar, ensuring it does not already exist.
|
|
|
|
Args:
|
|
name (str): The name of the component.
|
|
component (ToolBarAction): The component to add.
|
|
"""
|
|
if self.exists(name):
|
|
logger.info(f"Component with name '{name}' already exists. Skipping addition.")
|
|
return
|
|
self.add(name, component)
|
|
|
|
def exists(self, name: str) -> bool:
|
|
"""
|
|
Checks if a component exists in the toolbar.
|
|
|
|
Args:
|
|
name (str): The name of the component to check.
|
|
|
|
Returns:
|
|
bool: True if the component exists, False otherwise.
|
|
"""
|
|
return name in self._components
|
|
|
|
def get_action_reference(self, name: str) -> ReferenceType[ToolBarAction]:
|
|
"""
|
|
Retrieves a component by name.
|
|
|
|
Args:
|
|
name (str): The name of the component to retrieve.
|
|
|
|
"""
|
|
if not self.exists(name):
|
|
raise KeyError(f"Component with name '{name}' does not exist.")
|
|
return louie.saferef.safe_ref(self._components[name].action)
|
|
|
|
def get_action(self, name: str) -> ToolBarAction:
|
|
"""
|
|
Retrieves a component by name.
|
|
|
|
Args:
|
|
name (str): The name of the component to retrieve.
|
|
|
|
Returns:
|
|
ToolBarAction: The action associated with the given name.
|
|
"""
|
|
if not self.exists(name):
|
|
raise KeyError(
|
|
f"Component with name '{name}' does not exist. The following components are available: {list(self._components.keys())}"
|
|
)
|
|
return self._components[name].action
|
|
|
|
def set_bundle(self, name: str, bundle: ToolbarBundle):
|
|
"""
|
|
Sets the bundle for a component.
|
|
|
|
Args:
|
|
name (str): The name of the component.
|
|
bundle (ToolbarBundle): The bundle to set.
|
|
"""
|
|
if not self.exists(name):
|
|
raise KeyError(f"Component with name '{name}' does not exist.")
|
|
comp = self._components[name]
|
|
if comp.toolbar_bundle is not None:
|
|
logger.info(
|
|
f"Component '{name}' already has a bundle ({comp.toolbar_bundle.name}). Setting it to {bundle.name}."
|
|
)
|
|
comp.toolbar_bundle.bundle_actions.pop(name, None)
|
|
comp.toolbar_bundle = bundle
|
|
|
|
def remove_action(self, name: str):
|
|
"""
|
|
Removes a component from the toolbar.
|
|
|
|
Args:
|
|
name (str): The name of the component to remove.
|
|
"""
|
|
if not self.exists(name):
|
|
raise KeyError(f"Action with ID '{name}' does not exist.")
|
|
action_info = self._components.pop(name)
|
|
if action_info.toolbar_bundle:
|
|
action_info.toolbar_bundle.bundle_actions.pop(name, None)
|
|
self.toolbar.refresh()
|
|
action_info.toolbar_bundle = None
|
|
if hasattr(action_info.action, "cleanup"):
|
|
# Call cleanup if the action has a cleanup method
|
|
action_info.action.cleanup()
|
|
|
|
def cleanup(self):
|
|
"""
|
|
Cleans up the toolbar components by removing all actions and bundles.
|
|
"""
|
|
for action_info in self._components.values():
|
|
if hasattr(action_info.action, "cleanup"):
|
|
# Call cleanup if the action has a cleanup method
|
|
action_info.action.cleanup()
|
|
self._components.clear()
|
|
|
|
|
|
class ToolbarBundle:
|
|
def __init__(self, name: str, components: ToolbarComponents):
|
|
"""
|
|
Initializes a new bundle component.
|
|
|
|
Args:
|
|
bundle_name (str): Unique identifier for the bundle.
|
|
"""
|
|
self.name = name
|
|
self.components = components
|
|
self.bundle_actions: DefaultDict[str, ReferenceType[ToolBarAction]] = defaultdict()
|
|
self._connections: dict[str, BundleConnection] = {}
|
|
|
|
def add_action(self, name: str):
|
|
"""
|
|
Adds an action to the bundle.
|
|
|
|
Args:
|
|
name (str): Unique identifier for the action.
|
|
action (ToolBarAction): The action to add.
|
|
"""
|
|
if name in self.bundle_actions:
|
|
raise ValueError(f"Action with name '{name}' already exists in bundle '{self.name}'.")
|
|
if not self.components.exists(name):
|
|
raise ValueError(
|
|
f"Component with name '{name}' does not exist in the toolbar. Please add it first using the `ToolbarComponents.add` method."
|
|
)
|
|
self.bundle_actions[name] = self.components.get_action_reference(name)
|
|
self.components.set_bundle(name, self)
|
|
|
|
def remove_action(self, name: str):
|
|
"""
|
|
Removes an action from the bundle.
|
|
|
|
Args:
|
|
name (str): The name of the action to remove.
|
|
"""
|
|
if name not in self.bundle_actions:
|
|
raise KeyError(f"Action with name '{name}' does not exist in bundle '{self.name}'.")
|
|
del self.bundle_actions[name]
|
|
|
|
def add_separator(self):
|
|
"""
|
|
Adds a separator action to the bundle.
|
|
"""
|
|
self.add_action("separator")
|
|
|
|
def add_splitter(
|
|
self,
|
|
name: str = "splitter",
|
|
target_widget: QWidget | None = None,
|
|
initial_width: int = 10,
|
|
min_width: int | None = None,
|
|
max_width: int | None = None,
|
|
size_policy_expanding: bool = True,
|
|
):
|
|
"""
|
|
Adds a resizable splitter action to the bundle.
|
|
|
|
Args:
|
|
name (str): Unique identifier for the splitter action.
|
|
target_widget (QWidget, optional): The widget whose size (width for horizontal,
|
|
height for vertical orientation) will be controlled by the splitter. If None,
|
|
the splitter will not control any widget.
|
|
initial_width (int): The initial size of the splitter (width for horizontal,
|
|
height for vertical orientation).
|
|
min_width (int, optional): The minimum size the target widget can be resized to
|
|
(width for horizontal, height for vertical orientation). If None, the target
|
|
widget's minimum size hint in that orientation will be used.
|
|
max_width (int, optional): The maximum size the target widget can be resized to
|
|
(width for horizontal, height for vertical orientation). If None, the target
|
|
widget's maximum size hint in that orientation will be used.
|
|
size_policy_expanding (bool): If True, the size policy of the target_widget will be
|
|
set to Expanding in the appropriate orientation if it is not already set.
|
|
"""
|
|
|
|
# Resolve effective bounds
|
|
eff_min = min_width if min_width is not None else None
|
|
eff_max = max_width if max_width is not None else None
|
|
|
|
is_horizontal = self.components.toolbar.orientation() == Qt.Orientation.Horizontal
|
|
|
|
if target_widget is not None:
|
|
# Use widget hints if bounds not provided
|
|
if eff_min is None:
|
|
eff_min = (
|
|
target_widget.minimumWidth() if is_horizontal else target_widget.minimumHeight()
|
|
) or 6
|
|
if eff_max is None:
|
|
mw = (
|
|
target_widget.maximumWidth() if is_horizontal else target_widget.maximumHeight()
|
|
)
|
|
eff_max = mw if mw and mw < MAX_SIZE else DEFAULT_SIZE # avoid "no limit"
|
|
|
|
# Adjust size policy if needed
|
|
if size_policy_expanding:
|
|
size_policy = target_widget.sizePolicy()
|
|
|
|
if is_horizontal:
|
|
if size_policy.horizontalPolicy() not in (
|
|
QSizePolicy.Policy.Expanding,
|
|
QSizePolicy.Policy.MinimumExpanding,
|
|
):
|
|
size_policy.setHorizontalPolicy(QSizePolicy.Policy.Expanding)
|
|
target_widget.setSizePolicy(size_policy)
|
|
else:
|
|
if size_policy.verticalPolicy() not in (
|
|
QSizePolicy.Policy.Expanding,
|
|
QSizePolicy.Policy.MinimumExpanding,
|
|
):
|
|
size_policy.setVerticalPolicy(QSizePolicy.Policy.Expanding)
|
|
target_widget.setSizePolicy(size_policy)
|
|
|
|
splitter_action = SplitterAction(
|
|
orientation="auto",
|
|
parent=self.components.toolbar,
|
|
initial_width=initial_width,
|
|
min_width=eff_min,
|
|
max_width=eff_max,
|
|
target_widget=target_widget,
|
|
)
|
|
|
|
self.components.add_safe(name, splitter_action)
|
|
self.add_action(name)
|
|
|
|
def add_connection(self, name: str, connection):
|
|
"""
|
|
Adds a connection to the bundle.
|
|
|
|
Args:
|
|
name (str): Unique identifier for the connection.
|
|
connection: The connection to add.
|
|
"""
|
|
if name in self._connections:
|
|
raise ValueError(
|
|
f"Connection with name '{name}' already exists in bundle '{self.name}'."
|
|
)
|
|
self._connections[name] = connection
|
|
|
|
def remove_connection(self, name: str):
|
|
"""
|
|
Removes a connection from the bundle.
|
|
|
|
Args:
|
|
name (str): The name of the connection to remove.
|
|
"""
|
|
if name not in self._connections:
|
|
raise KeyError(f"Connection with name '{name}' does not exist in bundle '{self.name}'.")
|
|
self._connections[name].disconnect()
|
|
del self._connections[name]
|
|
|
|
def get_connection(self, name: str):
|
|
"""
|
|
Retrieves a connection by name.
|
|
|
|
Args:
|
|
name (str): The name of the connection to retrieve.
|
|
|
|
Returns:
|
|
The connection associated with the given name.
|
|
"""
|
|
if name not in self._connections:
|
|
raise KeyError(f"Connection with name '{name}' does not exist in bundle '{self.name}'.")
|
|
return self._connections[name]
|
|
|
|
def disconnect(self):
|
|
"""
|
|
Disconnects all connections in the bundle.
|
|
"""
|
|
for connection in self._connections.values():
|
|
connection.disconnect()
|
|
self._connections.clear()
|