"""Bốn màn chính và cách chuyển giữa chúng — R08-T10. Dashboard và Lịch chỉ được dựng ở lần mở đầu tiên (dựng lười) — mở app không phải trả giá cho hai màn có thể cả phiên không ai vào. ``_ensure_page`` là chỗ duy nhất biết điều đó, nên mọi đường tới một trang đều phải đi qua ``_goto``. Cùng kiểu mixin, xem ghi chú ở đầu ``nav_rail.py``. """ from __future__ import annotations from PySide6.QtCore import Qt from ...i18n import tr from ..dashboard.dashboard_tab import DashboardTab from ...ui.monitoring_tab import MonitoringTab from ..scheduling.schedule_task_tab import ScheduleTaskTab class PageRegistryMixin: def _page_index(self, widget) -> int: return self.pages.indexOf(widget) def _build_dashboard(self): d = DashboardTab(self.ctx) d.status_message.connect(self.statusBar().showMessage) self.dashboard = d return d def _build_schedule(self): s = ScheduleTaskTab(self.ctx, self.task_scheduler) s.status_message.connect(self.statusBar().showMessage) self.schedule = s return s def _build_monitoring(self): m = MonitoringTab(self.ctx, cowork=self.cowork, structure=self.structure, task_scheduler=self.task_scheduler) m.status_message.connect(self.statusBar().showMessage) self.monitoring = m return m def _ensure_page(self, row: int) -> None: """Build a lazy nav page on first visit and swap it in for its placeholder.""" if not (0 <= row < len(self._built)) or self._built[row]: return builder = self._nav_defs[row][2] if builder is None: return real = builder() placeholder = self._page_widgets[row] self.pages.insertWidget(row, real) # placeholder shifts to row+1 self.pages.removeWidget(placeholder) placeholder.deleteLater() self._page_widgets[row] = real self._built[row] = True def _page_index(self, widget) -> int: if widget is self.workspace: return self._ROW_WORKSPACE if self.dashboard is not None and widget is self.dashboard: return self._ROW_DASHBOARD if self.schedule is not None and widget is self.schedule: return self._ROW_SCHEDULE if self.monitoring is not None and widget is self.monitoring: return self._ROW_MONITORING return self.pages.indexOf(widget) def _goto(self, page: int, sub) -> None: self._ensure_page(page) # build lazy page on first visit self.pages.setCurrentIndex(page) if page == self._ROW_WORKSPACE: self.workspace.refresh() # re-list projects + threads on entry widget = self._page_widgets[page] if sub is not None and hasattr(widget, "select_subtab"): # Enforce the project gate here rather than at each entry point. A # greyed rail row cannot be clicked, but _goto is also reached from # RECENTS and from startup restore, and it used to open a sub-tab # the gate was holding shut — page shown, tab strip still hiding it. if hasattr(widget, "subtab_available") and not widget.subtab_available(sub): self.statusBar().showMessage(tr("app.nav.needs_project"), 4000) else: widget.select_subtab(sub) # Move the highlight with the content, however navigation was triggered — # a programmatic _goto used to leave it on whatever was clicked last. if not self._nav_building: self._select_nav_row(page, sub) self._update_dock_guard() # Switching pages updates which conversation is "current". self._refresh_history()