diff --git a/bec_widgets/utils/widget_state_manager.py b/bec_widgets/utils/widget_state_manager.py index 22da1208..3a2ad87c 100644 --- a/bec_widgets/utils/widget_state_manager.py +++ b/bec_widgets/utils/widget_state_manager.py @@ -23,14 +23,23 @@ logger = bec_logger.logger class WidgetStateManager: """ - A class to manage the state of a widget by saving and loading the state to and from a INI file. + Manage saving and loading widget state to/from an INI file. Args: - widget(QWidget): The widget to manage the state for. + widget (QWidget): Root widget whose subtree will be serialized. + serialize_from_root (bool): When True, build group names relative to + this root and ignore parents above it. This keeps profiles portable + between different host window hierarchies. + root_id (str | None): Optional stable label to use for the root in + the settings key path. When omitted and `serialize_from_root` is + True, the class name of `widget` is used, falling back to its + objectName and finally to "root". """ - def __init__(self, widget): + def __init__(self, widget, *, serialize_from_root: bool = False, root_id: str | None = None): self.widget = widget + self._serialize_from_root = bool(serialize_from_root) + self._root_id = root_id def save_state(self, filename: str | None = None, settings: QSettings | None = None): """ @@ -97,10 +106,12 @@ class WidgetStateManager: for i in range(meta.propertyCount()): prop = meta.property(i) name = prop.name() + + # Always save `visible` as True to avoid restoring hidden widgets from profiles. if name == "visible": - settings.setValue( - name, True - ) # always save visible as True to avoid invisible widgets on load + settings.setValue(name, True) + continue + if ( name == "objectName" or not prop.isReadable() @@ -108,6 +119,7 @@ class WidgetStateManager: or not prop.isStored() # can be extended to fine filter ): continue + value = widget.property(name) settings.setValue(name, value) settings.endGroup() @@ -178,23 +190,51 @@ class WidgetStateManager: ): self._load_widget_state_qsettings(child, settings, False) - def _get_full_widget_name(self, widget: QWidget): + def _get_full_widget_name(self, widget: QWidget) -> str: """ - Get the full name of the widget including its parent names. + Build a group key for *widget*. + + When `serialize_from_root` is False (default), this preserves the original + behavior and walks all parents up to the top-level widget. + + When `serialize_from_root` is True, the key is built relative to + `self.widget` and parents above the managed root are ignored. The first + path segment is either `root_id` (when provided) or a stable label derived + from the root widget (class name, then objectName, then "root"). Args: - widget(QWidget): The widget to get the full name for. - - Returns: - str: The full name of the widget. + widget (QWidget): The widget to build the key for. """ - name = widget.objectName() - parent = widget.parent() - while parent: - obj_name = parent.objectName() or parent.metaObject().className() - name = obj_name + "." + name - parent = parent.parent() - return name + # Backwards-compatible behavior: include the entire parent chain. + if not getattr(self, "_serialize_from_root", False): + name = widget.objectName() + parent = widget.parent() + while parent: + obj_name = parent.objectName() or parent.metaObject().className() + name = obj_name + "." + name + parent = parent.parent() + return name + + parts: list[str] = [] + current: QWidget | None = widget + + while current is not None: + if current is self.widget: + # Reached the serialization root. + root_label = self._root_id + if not root_label: + meta = current.metaObject() if hasattr(current, "metaObject") else None + class_name = meta.className() if meta is not None else "" + root_label = class_name or current.objectName() or "root" + parts.append(str(root_label)) + break + + obj_name = current.objectName() or current.metaObject().className() + parts.append(obj_name) + current = current.parent() + + parts.reverse() + return ".".join(parts) class ExampleApp(QWidget): # pragma: no cover: diff --git a/bec_widgets/widgets/containers/advanced_dock_area/advanced_dock_area.py b/bec_widgets/widgets/containers/advanced_dock_area/advanced_dock_area.py index b02f5c1d..3bdadcf7 100644 --- a/bec_widgets/widgets/containers/advanced_dock_area/advanced_dock_area.py +++ b/bec_widgets/widgets/containers/advanced_dock_area/advanced_dock_area.py @@ -159,7 +159,9 @@ class AdvancedDockArea(DockAreaWidget): self._exit_snapshot_written = False # State manager - self.state_manager = WidgetStateManager(self) + self.state_manager = WidgetStateManager( + self, serialize_from_root=True, root_id="AdvancedDockArea" + ) # Developer mode state self._editable = None