# -*- coding: utf-8 -*- # gui/components/tab_button.py from PySide6.QtWidgets import QPushButton, QSizePolicy from PySide6.QtCore import Slot from gui.containers.s_container import SContainer from gui.styles import APP_STYLES from gui.theme_bus import theme_bus class TabButton(SContainer): """Обёртка кнопки вкладки со стилями, зависящими от темы.""" def __init__(self, text: str, index: int, **kwargs): width_percent = kwargs.get("width_percent", None) height_percent = kwargs.get("height_percent", None) margin = kwargs.get("margin", [0, 2, 0, 2]) style = kwargs.get("style", None) active_style = kwargs.get("active_style", None) is_active = kwargs.get("is_active", None) content_fit = kwargs.get("content_fit", True) super().__init__( width_percent=width_percent, height_percent=height_percent, margin=margin, style=style, active_style=active_style, is_active=is_active, content_fit=content_fit, ) self.index = index self._theme = "dark" self._button = QPushButton(text) self._button.setProperty("tab_index", index) super().add_widget(self._button) self._button.setSizePolicy( QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding ) self._style_key_normal = style or "TAB_BUTTON_NORMAL" self._style_key_active = active_style or "TAB_BUTTON_ACTIVE" self._is_active = bool(is_active) if is_active is not None else False self.style() theme_bus.theme_changed.connect(self.set_theme) def style( self, style_key: str | None = None, active_key: str | None = None, is_active: bool | None = None, ): if style_key is not None: self._style_key_normal = style_key self._style_key_active = active_key or style_key if is_active is not None: self._is_active = bool(is_active) normal_key = self._style_key_normal active_key_to_use = self._style_key_active if self._theme == "light": themed_normal = f"{normal_key}_LIGHT" themed_active = f"{active_key_to_use}_LIGHT" if themed_normal in APP_STYLES: normal_key = themed_normal if themed_active in APP_STYLES: active_key_to_use = themed_active key = active_key_to_use if self._is_active else normal_key self._button.setStyleSheet(APP_STYLES.get(key, "")) @Slot(str) def set_theme(self, theme: str): theme = (theme or "").strip().lower() if theme not in ("dark", "light"): return if self._theme == theme: return self._theme = theme self.style() @property def clicked(self): return self._button.clicked def click(self): self._button.click() def set_text(self, text: str): self._button.setText(text) def get_text(self) -> str: return self._button.text() def set_enabled(self, enabled: bool) -> None: self._button.setEnabled(enabled) super().setEnabled(enabled) def set_tooltip(self, text: str) -> None: self._button.setToolTip(text) def set_min_width(self, width: int) -> None: self._button.setMinimumWidth(width) super().setMinimumWidth(width) def set_min_height(self, height: int) -> None: self._button.setMinimumHeight(height) super().setMinimumHeight(height) def set_max_width(self, width: int) -> None: self._button.setMaximumWidth(width) super().setMaximumWidth(width) def set_max_height(self, height: int) -> None: self._button.setMaximumHeight(height) super().setMaximumHeight(height) def set_fixed_size(self, width: int, height: int) -> None: self._button.setMinimumSize(width, height) self._button.setMaximumSize(width, height) super().setMinimumSize(width, height) super().setMaximumSize(width, height) def set_checkable(self, checkable: bool) -> None: self._button.setCheckable(checkable) def set_checked(self, checked: bool) -> None: self._button.setChecked(checked) def is_checked(self) -> bool: return self._button.isChecked() def set_size_policy(self, horizontal, vertical) -> None: self._button.setSizePolicy(horizontal, vertical) super().setSizePolicy(horizontal, vertical) def set_property(self, name: str, value) -> None: super().setProperty(name, value) self._button.setProperty(name, value) def add_widget(self, widget, alignment=None): raise NotImplementedError("TabButton can contain only one QPushButton") # --------------------------------------------------------------------------- # Module workflow notes # --------------------------------------------------------------------------- # # 1) Назначение модуля: # Кнопка вкладки для TabWidget, реализованная как SContainer # с QPushButton внутри и стилями, зависящими от темы. # Поддерживает нормальное и активное состояние для визуализации # текущей вкладки. # # 2) Зависимости модуля: # Импорты: QPushButton, QSizePolicy (PySide6.QtWidgets), # Slot (PySide6.QtCore), # SContainer (gui.containers.s_container), # APP_STYLES (gui.styles), # theme_bus (gui.theme_bus) # Хост-класс / базовый класс: SContainer # Внешние библиотеки: PySide6 (обязательна) # # 3) Экспорт: # Класс TabButton — кнопка вкладки. # Методы: style(), set_theme(), click(), set_text(), get_text(), # set_enabled(), set_tooltip(), set_checkable(), set_checked(), # is_checked(), set_min/max_width/height(), set_fixed_size(), # set_size_policy(), set_property(). # Свойство: clicked. # # 4) Состояние (поля): # index: int — номер вкладки # _theme: str — текущая тема # _button: QPushButton — внутренняя кнопка # _style_key_normal: str — ключ нормального стиля (TAB_BUTTON_NORMAL) # _style_key_active: str — ключ активного стиля (TAB_BUTTON_ACTIVE) # _is_active: bool — признак активной вкладки # # 5) Последовательность действий и вызовов: # __init__(text, index, **kwargs) # -> super().__init__(...) # -> QPushButton(text) -> setProperty("tab_index", index) # -> super().add_widget(_button) -> setSizePolicy(Expanding) # -> style() -> theme_bus.theme_changed.connect(set_theme) # style(style_key?, active_key?, is_active?) # -> для light-темы: проверить суффикс _LIGHT в APP_STYLES # -> выбрать active или normal ключ # -> _button.setStyleSheet(APP_STYLES[key]) # Вызывается из TabWidget._apply_active_style(index) # # 6) Побочные эффекты: # - Устанавливает stylesheet на QPushButton. # - Подключается к theme_bus.theme_changed. # # 7) Границы ответственности: # Модуль НЕ управляет содержимым вкладок. # НЕ переключает страницы — это делает TabWidget. # add_widget() заблокирован. # # 8) Обработка ошибок: # add_widget() бросает NotImplementedError. # set_theme() молча игнорирует невалидные значения. # # 9) Инварианты и контракты: # - Контейнер содержит ровно одну QPushButton. # - _theme ∈ {"dark", "light"}. # - index совпадает с порядковым номером вкладки в TabWidget. # # 10) Правило сопровождения: # Стили TAB_BUTTON_NORMAL / TAB_BUTTON_ACTIVE должны быть # определены в APP_STYLES. Для light-темы — суффикс _LIGHT. # Не менять index после добавления в TabWidget (переиндексация # делается в TabWidget.remove_tab()).