# SPDX-License-Identifier: LGPL-3.0-or-later from .objects import NavigateRequest from .windows import BaseWindow from .logging import setup_logger from .database import AbstractDatabase from PyQt6.QtWidgets import QApplication from PyQt6.QtCore import QObject, pyqtSignal, pyqtSlot from typing import Dict log = setup_logger(__name__) class Composer(QObject): """ Application router and window lifecycle manager. Manages window registration, navigation between windows, and holds the active database connection. Acts as the central hub of the application. Args: app: QApplication instance. db: Connected AbstractDatabase instance. """ navigate_request = pyqtSignal(NavigateRequest) def __init__(self, app: QApplication, db: AbstractDatabase): super().__init__() self._db = db self._current = None self._app = app self._registry: Dict[str, Dict[str, object]] = dict() self._current_ctx: Dict[str, Dict[str, object]] = dict() self.navigate_request.connect(self.navigate) @property def context(self): """ Return the current navigation context. """ return self._current_ctx def register(self, name: str, window: type[BaseWindow], lazy: bool = True): """ Register a window class under a given name. Args: name: Unique string identifier for the window. window: A subclass of BaseWindow. lazy: If True, the window is instantiated on first navigation. If False, the window is instantiated immediately. Raises: TypeError: If window is not a subclass of BaseWindow. """ if not issubclass(window, BaseWindow): raise TypeError(f"{window} is not a descendant of BaseWindow") entry = { "class": window, "instance": None, "lazy": lazy } if not lazy: entry["instance"] = window(self, self._db) self._registry[name] = entry def _switch_window(self, window): """ Close the current window and show the new one. """ if self._current: self._current.close() self._current = window self._current.show() def _create_window(self, name: str) -> BaseWindow: """ Instantiate or retrieve the window registered under name. For lazy windows, creates a new instance on every call. For eager windows, returns the existing instance. """ entry = self._registry[name] if not entry["lazy"]: return entry["instance"] instance = entry["class"](self, self._db) return instance @pyqtSlot(NavigateRequest) def navigate(self, request: NavigateRequest): """ Handle a NavigateRequest signal and switch to the target window. Raises: KeyError: If the target window is not registered. """ if request.target not in self._registry: raise KeyError("Window is not registered") self._current_ctx = request.context window = self._create_window(request.target) self._switch_window(window) def run(self, start: str): """ Start the application from the given window. Args: start: Name of the window to show first. Raises: KeyError: If the start window is not registered. """ if start not in self._registry: raise KeyError("Window is not registered") window = self._create_window(start) self._switch_window(window) return self._app.exec()