Files
Dispatch/Dispatch_V0.1.1/gui/components/tab_button.py
2026-04-29 08:18:54 +04:00

220 lines
8.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- 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()).