Source code for radioviz.services.window_manager

#  Copyright 2025–2026 European Union
#  Author: Bulgheroni Antonio (antonio.bulgheroni@ec.europa.eu)
#  SPDX-License-Identifier: EUPL-1.2
"""
Window management service for handling multiple sub-windows in the application.

This module provides the core functionality for managing application windows,
including registration, unregistration, activation switching, and state tracking
of sub-windows within the application.
"""

from __future__ import annotations

import uuid
from dataclasses import dataclass, field
from typing import TYPE_CHECKING, Any, Optional
from uuid import UUID

from PySide6.QtCore import QObject, Signal

from radioviz.services.workspace_manager import UIWindowState, UIWorkspace

if TYPE_CHECKING:
    from radioviz.controllers.sub_window_controller import SubWindowController


[docs] class WindowManager(QObject): """ Manages the lifecycle and state of application sub-windows. This class handles the registration and unregistration of sub-window controllers, tracks the currently active window, and provides navigation between windows. It emits signals when the window list changes or when the active window changes. """ list_changed = Signal() """Signal emitted when the list of registered windows changes.""" active_changed = Signal(object) """Signal emitted when the active window changes.""" request_update_app_state = Signal(object) """Signal emitted when application state update is requested.""" def __init__(self) -> None: """ Initialize the WindowManager instance. Creates an empty window list and sets up the Qt signal connections. """ super().__init__() self._active: Optional['SubWindowController[Any]'] = None self._windows: list['SubWindowController[Any]'] = []
[docs] def register(self, window_controller: 'SubWindowController[Any]', *, activate: bool = True) -> None: """ Register a window controller. Adds the given window controller to the internal list of managed windows. If activate is True (default), sets the newly registered window as the active window. :param window_controller: The window controller to register :type window_controller: SubWindowController[Any] :param activate: Whether to activate the registered window, defaults to True :type activate: bool """ self._windows.append(window_controller) if activate: self.active_window = window_controller self.list_changed.emit()
[docs] def unregister(self, window_controller: 'SubWindowController[Any]') -> None: """ Unregister a window controller. Removes the given window controller from the internal list. If the removed window was the active one, sets the last window in the list as active. :param window_controller: The window controller to unregister :type window_controller: SubWindowController[Any] """ if window_controller in self._windows: was_active = window_controller == self._active self._windows.remove(window_controller) if was_active: self.active_window = self._windows[-1] if self._windows else None self.list_changed.emit()
[docs] def set_window_as_active(self, window_idx: int) -> None: """ Set a specific window as the active window. :param window_idx: The index of the window to activate :type window_idx: int :raise IndexError: if the window index is out of range """ if 0 <= window_idx < len(self._windows): self.active_window = self._windows[window_idx]
[docs] def has_windows(self) -> bool: """ Check if there are any registered windows. :return: True if there are registered windows, False otherwise :rtype: bool """ return bool(self._windows)
[docs] def next_window(self) -> 'SubWindowController[Any] | None': """ Switch to the next window in the sequence. :return: The newly activated window or None if no windows exist :rtype: SubWindowController[Any] or None """ if self._active is None: return None if len(self._windows) == 1: return self._active active_index = self._windows.index(self._active) next_index = (active_index + 1) % len(self._windows) self.active_window = self._windows[next_index] return self._active
[docs] def previous_window(self) -> 'SubWindowController[Any] | None': """ Switch to the previous window in the sequence. :return: The newly activated window or None if no windows exist :rtype: SubWindowController[Any] or None """ if self._active is None: return None if len(self._windows) == 1: return self._active active_index = self._windows.index(self._active) prev_index = (active_index - 1) % len(self._windows) self.active_window = self._windows[prev_index] return self._active
@property def active_window(self) -> 'SubWindowController[Any] | None': """ Get the currently active window. :return: The active window controller or None if no windows exist :rtype: SubWindowController[Any] or None """ return self._active @active_window.setter def active_window(self, window: 'SubWindowController[Any] | None') -> None: """ Set the active window. Emits signals when the active window changes. :param window: The window controller to make active :type window: SubWindowController[Any] | None """ if self._active != window: self._active = window self.active_changed.emit(self._active) self.request_update_app_state.emit(self._active) @property def window_list(self) -> tuple['SubWindowController[Any]', ...]: """ Get the list of registered windows. :return: Tuple of registered window controllers :rtype: tuple[SubWindowController[Any]] """ return tuple(self._windows)
[docs] def snapshot_ui(self) -> UIWorkspace: """ Create a snapshot of the current UI workspace state. Captures the state of all registered windows including their IDs, states, and z-order positions. Also records the ID of the currently active window. :return: A UIWorkspace object containing the current UI state snapshot :rtype: UIWorkspace """ windows: list[UIWindowState] = [] for idx, controller in enumerate(self._windows): windows.append(UIWindowState(window_id=controller.id, state=controller.window_state(), z_order=idx)) active_id = self._active.id if self._active else None return UIWorkspace( active_window_id=active_id, windows=windows, )
[docs] def restore_ui_workspace(self, ui: UIWorkspace) -> None: """ Restore the UI state from a previously saved workspace snapshot. Reconstructs the window order based on the z-order information in the workspace, restores individual window states, and activates the appropriate window if specified in the snapshot. :param ui: The UI workspace snapshot to restore from :type ui: UIWorkspace """ if not ui.windows: return by_id = {w.id: w for w in self._windows} for win in ui.windows: win_controller = by_id.get(win.window_id, None) if win_controller is None: continue win_controller.set_window_state(win.state) ordered = [by_id[ws.window_id] for ws in sorted(ui.windows, key=lambda w: w.z_order) if ws.window_id in by_id] if ordered: self._windows[:] = ordered self.list_changed.emit() if ui.active_window_id: wc = by_id.get(ui.active_window_id) if wc: self.active_window = wc
[docs] def close_active_window(self) -> None: """ Close the currently active window. If there is an active window, this method will call the close() method on the active window controller, effectively closing that window. This method does not emit any signals as it directly operates on the active window controller's close method. """ if self.active_window: self.active_window.close()
[docs] def close_all_windows(self) -> None: """ Close all registered windows. Iteratively closes all windows in the registry by calling the close() method on each window controller. Windows are closed in the order they appear in the internal window list. """ while self._windows: controller = self._windows[0] # Each window controller is expected to unregister itself on close. controller.close()
[docs] def get_windows_by_id(self, id: UUID) -> 'SubWindowController[Any] | None': """ Retrieve a window controller by its unique identifier. Searches through the registered windows to find one with the specified UUID. Returns the matching window controller if found, otherwise returns None. :param id: The unique identifier of the window to search for :type id: UUID :return: The window controller with the specified ID or None if not found :rtype: SubWindowController[Any] or None """ for wc in self._windows: if wc.id == id: return wc return None
[docs] def show_on_top(self, id: UUID) -> None: """ Bring the window with the specified ID to the top of the window stack. This method retrieves the window controller associated with the given UUID and raises its view to the top of the window hierarchy, making it visible and ensuring it's in the foreground. :param id: The unique identifier of the window to bring to the top :type id: UUID """ wc = self.get_windows_by_id(id) if wc and wc.view is not None: wc.view.raise_()
[docs] @dataclass class WindowRequest: """ Data class representing a request to create a new window. Contains information about the window to be created including its controller, type, and initialization parameters. """ controller: 'SubWindowController[Any]' """The window controller associated with this request.""" window_type: str """The type identifier for the window to be created.""" requested_id: Optional[uuid.UUID] = None """The id to be assigned to the controller, optional""" init_kwargs: dict[str, Any] = field(default_factory=dict) """Keyword arguments for initializing the window."""