# -*- coding: utf-8 -*- # gui/containers/stack_container.py from PySide6.QtCore import Qt from PySide6.QtWidgets import QStackedWidget, QGridLayout, QLayout, QWidget from .percent_sized_widget import PercentSizedWidget from .content_host import ContentHost class StackContainer(PercentSizedWidget): """Контейнер-обёртка над QStackedWidget с размерами в процентах от родителя.""" def __init__( self, width_percent: int | float | None = None, height_percent: int | float | None = None, margin: int | tuple[int, int, int, int] = 0, content_margins: int | tuple[int, int, int, int] | None = None, spacing: int = 0, content_width_percent: int | None = None, content_height_percent: int | None = None, content_width: int | None = None, content_height: int | None = None, content_fit: bool = True, parent: QWidget | None = None, style: str | None = None, active_style: str | None = None, is_active: bool | None = None, ): super().__init__(width_percent, height_percent, parent) self._init_stylable() self._auto_add_children = True self._stack = QStackedWidget(self) self._content_host = ContentHost( width_percent=content_width_percent, height_percent=content_height_percent, orientation="v", margin=0, spacing=spacing, parent=self, ) self._content_host.set_content_fit(content_fit) self._content_host.add_widget(self._stack) self._outer_layout = QGridLayout(self) self._outer_layout.setSpacing(0) applied_margins = content_margins if content_margins is not None else margin if isinstance(applied_margins, (list, tuple)) and len(applied_margins) == 4: self._outer_layout.setContentsMargins(*applied_margins) else: self._outer_layout.setContentsMargins(applied_margins, applied_margins, applied_margins, applied_margins) if content_width is not None or content_height is not None: w = content_width if content_width is not None else self._content_host.sizeHint().width() h = content_height if content_height is not None else self._content_host.sizeHint().height() self._content_host.set_fixed_size(w, h) self._outer_layout.addWidget(self._content_host, 0, 0) self._outer_layout.setRowStretch(0, 1) self._outer_layout.setColumnStretch(0, 1) if spacing: self._outer_layout.setSpacing(spacing) if style is not None or active_style is not None or is_active is not None: self._apply_style(style_key=style, active_key=active_style, is_active=is_active) # Методы работы со страницами (интерфейс похож на QStackedWidget) def add_widget(self, widget: QWidget) -> int: index = self._stack.addWidget(widget) if isinstance(widget, PercentSizedWidget): widget.schedule_percent_update() return index def set_current_index(self, index: int) -> None: self._stack.setCurrentIndex(index) def set_current_widget(self, widget: QWidget) -> None: self._stack.setCurrentWidget(widget) def current_index(self) -> int: return self._stack.currentIndex() def current_widget(self) -> QWidget | None: return self._stack.currentWidget() def count(self) -> int: return self._stack.count() def widget(self, index: int) -> QWidget | None: return self._stack.widget(index) def remove_widget(self, widget: QWidget) -> None: self._stack.removeWidget(widget) def get_available_size_for_content(self) -> tuple[int, int]: """Полезная внутренняя область (без учёта margin).""" margins = self._outer_layout.contentsMargins() w = self.width() - margins.left() - margins.right() h = self.height() - margins.top() - margins.bottom() return max(0, w), max(0, h) def get_layout(self) -> QLayout: return self._outer_layout def set_margins(self, margin: int | tuple[int, int, int, int]) -> None: if isinstance(margin, (list, tuple)) and len(margin) == 4: self._outer_layout.setContentsMargins(*margin) else: self._outer_layout.setContentsMargins(margin, margin, margin, margin) def set_spacing(self, spacing: int) -> None: self._outer_layout.setSpacing(spacing) def set_alignment(self, alignment: str) -> None: raise NotImplementedError("Qt alignment for containers is disabled; use content springs.") @property def stacked_widget(self) -> QStackedWidget: return self._stack # --------------------------------------------------------------------------- # Module workflow notes # --------------------------------------------------------------------------- # # 1) Назначение модуля: # Контейнер-обёртка над QStackedWidget с процентным sizing. # Реализует переключение «страниц» (одна видна # в каждый момент). Используется для панели плагинов, табов и т.п. # # 2) Зависимости модуля: # Импорты: Qt, QStackedWidget, QGridLayout, QLayout, QWidget (PySide6) # Хост/базовый класс: StylableMixin + PercentSizedWidget (MRO) # Внутренние: ContentHost (content_host.py), StylableMixin (stylable_mixin.py) # Внешние библиотеки: PySide6 # # 3) Экспорт: # Класс StackContainer — стековый контейнер. # Методы: add_widget(), set_current_index(), set_current_widget(), # current_index(), current_widget(), count(), widget(), # remove_widget(), get_available_size_for_content(), # get_layout(), set_margins(), set_spacing(), set_alignment() # Свойство: stacked_widget (доступ к QStackedWidget). # # 4) Состояние (поля): # _stack : QStackedWidget — Qt stacked widget. # _content_host : ContentHost — промежуточный хост. # _outer_layout : QGridLayout — внешний layout (spring-based). # _auto_add_children : bool = True — авто-добавление потомков. # # 5) Последовательность действий и вызовов: # __init__(params) -> super().__init__(w%, h%, parent) # -> _init_stylable() -> создание QStackedWidget # -> создание ContentHost -> _content_host.add_widget(_stack) # -> создание _outer_layout (QGridLayout) # -> setContentsMargins(applied_margins) # -> _apply_style() если style задан # add_widget(w) -> _stack.addWidget(w) → возвращает int index # set_current_index(i) -> _stack.setCurrentIndex(i) # # 6) Побочные эффекты: # QStackedWidget скрывает все страницы, кроме текущей. # _auto_add_children = True — дочерние виджеты авто-добавляются. # set_alignment() бросает NotImplementedError. # _apply_style() применяет stylesheet. # # 7) Границы ответственности: # НЕ управляет навигацией между страницами — только хранит и показывает. # НЕ создаёт кнопок/табов для переключения — это делает вызывающий код. # НЕ поддерживает Qt alignment (только springs). # # 8) Обработка ошибок: # set_alignment() → NotImplementedError. # Защита от нетипичных значений alignment — параметр удалён. # # 9) Инварианты и контракты: # - add_widget возвращает index добавленной страницы. # - content_margins приоритетнее margin. # # 10) Правило сопровождения: # Интерфейс CRUD-страниц (add/remove/set_current) повторяет # QStackedWidget — при обновлении Qt API проверять совместимость. # Не добавлять alignment в конструктор — запрещено правилами.