# -*- coding: utf-8 -*- # dispatch/window.py """Главное окно независимого приложения Dispatch. Назначение модуля: Минимальный графический каркас, выполняющий единственную обязанность — разместить корневой виджет модуля `hub.ticket.TicketPlugin` в окне верхнего уровня. Управление содержимым модуля Ticket остаётся полностью внутри самого модуля. Архитектурные ограничения: - Окно не содержит предметной логики и не обращается к внутренним полям модуля Ticket: взаимодействие выполняется только через его публичный API (`TicketPlugin`). - Стилевое оформление берётся из внешнего реестра `APP_STYLES`; локальные QSS-литералы запрещены (правило 6.3). - Отказ модуля Ticket изолирован: при ошибке инициализации окно продолжает существовать, ошибка фиксируется в журнале. """ from PySide6.QtCore import Qt, QEvent, QTimer from PySide6.QtWidgets import QMainWindow from error_logger import log_exception from gui.containers.percent_sized_widget import PercentSizedWidget from gui.styles import APP_STYLES from gui.theme_bus import theme_bus as _default_theme_bus from application import TaskApplicationService from ticket_plugin import TicketPlugin from null_hardware_gateway import NullHardwareGateway class DispatchMainWindow(QMainWindow): """Главное окно приложения Dispatch с единственным модулем Ticket.""" def __init__(self, theme_bus=None): super().__init__() self._theme_bus = theme_bus or _default_theme_bus self._ticket_plugin: TicketPlugin | None = None self._setup_window() self._setup_ui() self._connect_signals() def _setup_window(self) -> None: """Настроить параметры окна верхнего уровня.""" self.setWindowTitle("Dispatch — Ticket") self.setMinimumSize(1200, 800) self.setStyleSheet(APP_STYLES.get("MAIN_WINDOW_DARK", "")) self.showMaximized() def _setup_ui(self) -> None: """Разместить корневой виджет модуля Ticket в качестве центрального. В составе Dispatch модуль работы с COM-портом отключён: приложение собирает application-сервис Ticket с null-шлюзом и передаёт его в `TicketPlugin`, который владеет жизненным циклом сервиса. """ try: null_gateway = NullHardwareGateway(parent=self) application_service = TaskApplicationService( hardware_gateway=null_gateway, parent=self, ) self._ticket_plugin = TicketPlugin(application_service=application_service) self.setCentralWidget(self._ticket_plugin) except Exception as exc: log_exception(__name__, "DispatchMainWindow._setup_ui", exc) raise def _connect_signals(self) -> None: """Подключить шину тем для согласованного оформления окна.""" self._theme_bus.theme_changed.connect(self._on_theme_changed) def _on_theme_changed(self, theme: str) -> None: """Применить тему окна по ключам из внешнего реестра стилей.""" is_light = str(theme or "").strip().lower() == "light" key = "MAIN_WINDOW_LIGHT" if is_light else "MAIN_WINDOW_DARK" self.setStyleSheet(APP_STYLES.get(key, "")) def changeEvent(self, event): """Пересчитать процентную разметку при восстановлении окна.""" if event.type() == QEvent.Type.WindowStateChange: if not (self.windowState() & Qt.WindowState.WindowMinimized): QTimer.singleShot(0, self._refresh_percent_layout) return super().changeEvent(event) def _refresh_percent_layout(self) -> None: """Принудительно пересчитать процентные размеры дочерних виджетов.""" for widget in self.findChildren(PercentSizedWidget): try: widget.schedule_percent_update() except Exception as exc: log_exception(__name__, "DispatchMainWindow._refresh_percent_layout", exc) def closeEvent(self, event) -> None: """Гарантировать корректную остановку модуля Ticket при закрытии.""" if self._ticket_plugin is not None: try: self._ticket_plugin.cleanup() except Exception as exc: log_exception(__name__, "DispatchMainWindow.closeEvent", exc) super().closeEvent(event)