# -*- coding: utf-8 -*- # gui/components/label.py """Стандартный компонент метки.""" from PySide6.QtWidgets import QLabel, QSizePolicy from PySide6.QtCore import Qt, Slot from gui.containers.s_container import SContainer from gui.styles import APP_STYLES from gui.theme_bus import theme_bus class Label(SContainer): """Стандартная метка с выбором стиля по теме внутри SContainer.""" _ALIGN_MAP = { "top": Qt.AlignmentFlag.AlignTop, "bottom": Qt.AlignmentFlag.AlignBottom, "left": Qt.AlignmentFlag.AlignLeft, "right": Qt.AlignmentFlag.AlignRight, "hcenter": Qt.AlignmentFlag.AlignHCenter, "vcenter": Qt.AlignmentFlag.AlignVCenter, "center": Qt.AlignmentFlag.AlignCenter, } def __init__(self, text: str = "", **kwargs): width_percent = kwargs.get("width_percent", None) height_percent = kwargs.get("height_percent", None) margin = kwargs.get("margin", 0) alignment = kwargs.get("alignment", Qt.AlignCenter) style = kwargs.get("style", "WIDGET_LABEL") active_style = kwargs.get("active_style", None) is_active = kwargs.get("is_active", None) content_fit = kwargs.get("content_fit", True) word_wrap = kwargs.get("word_wrap", False) parent = kwargs.get("parent", None) 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, parent=parent, ) self._theme = "dark" self._is_active: bool = False self._base_style_key = style self._style_key_normal = None self._style_key_active = None self._label = QLabel(text) self._apply_alignment(alignment) if word_wrap: self._label.setWordWrap(True) self._label.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Preferred) else: self._label.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding) super().add_widget(self._label) if active_style is not None: self._style_key_normal = style self._style_key_active = active_style if is_active is not None: self._is_active = bool(is_active) self._theme = "dark" if self.palette().window().color().lightness() < 128 else "light" 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, ) -> None: """Короткий метод применения стиля. Можно задать ключи и активность явно.""" if style_key is not None: self._base_style_key = style_key if active_key is not None: self._style_key_normal = style_key self._style_key_active = active_key else: self._style_key_normal = None self._style_key_active = None if is_active is not None: self._is_active = bool(is_active) if self._style_key_normal is not None: active_key = self._style_key_active or self._style_key_normal key = active_key if self._is_active else self._style_key_normal themed = f"{key}_{self._theme.upper()}" if themed in APP_STYLES: key = themed self._label.setStyleSheet(APP_STYLES.get(key, "")) return base_key = self._base_style_key key = base_key if self._theme == "light": if self._is_active and f"{base_key}_LIGHT_ACTIVE" in APP_STYLES: key = f"{base_key}_LIGHT_ACTIVE" elif f"{base_key}_LIGHT" in APP_STYLES: key = f"{base_key}_LIGHT" else: if self._is_active and f"{base_key}_DARK_ACTIVE" in APP_STYLES: key = f"{base_key}_DARK_ACTIVE" elif f"{base_key}_DARK" in APP_STYLES: key = f"{base_key}_DARK" self._label.setStyleSheet(APP_STYLES.get(key, "")) @Slot(str) def set_theme(self, theme: str) -> None: """Внешний слот: принимает 'dark' или 'light'.""" theme = (theme or "").strip().lower() if theme not in ("dark", "light"): return if self._theme == theme: return self._theme = theme self.style() def set_text(self, text: str) -> None: self._label.setText(text) def set_pixmap(self, pixmap) -> None: self._label.setPixmap(pixmap) def get_text(self) -> str: return self._label.text() def _apply_alignment(self, alignment) -> None: """Внутренний метод применения alignment с поддержкой строк.""" if isinstance(alignment, str): key = alignment.strip().lower() alignment = self._ALIGN_MAP.get(key) if alignment is None: raise ValueError(f"Unknown alignment '{key}'. Allowed: {list(self._ALIGN_MAP.keys())}") self._label.setAlignment(alignment) def set_alignment(self, alignment: str | Qt.Alignment) -> None: """Установить выравнивание текста (строка или Qt.Alignment).""" self._apply_alignment(alignment) def set_word_wrap(self, enabled: bool) -> None: """Включить / выключить перенос текста по словам.""" self._label.setWordWrap(enabled) if enabled: self._label.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Preferred) def set_enabled(self, enabled: bool) -> None: self._label.setEnabled(enabled) super().setEnabled(enabled) def set_min_width(self, width: int) -> None: self._label.setMinimumWidth(width) super().setMinimumWidth(width) def set_min_height(self, height: int) -> None: self._label.setMinimumHeight(height) super().setMinimumHeight(height) def set_max_width(self, width: int) -> None: self._label.setMaximumWidth(width) super().setMaximumWidth(width) def set_max_height(self, height: int) -> None: self._label.setMaximumHeight(height) super().setMaximumHeight(height) def set_fixed_size(self, width: int, height: int) -> None: self._label.setMinimumSize(width, height) self._label.setMaximumSize(width, height) super().setMinimumSize(width, height) super().setMaximumSize(width, height) def set_tooltip(self, text: str) -> None: self._label.setToolTip(text) def set_size_policy(self, horizontal, vertical) -> None: self._label.setSizePolicy(horizontal, vertical) super().setSizePolicy(horizontal, vertical) def add_widget(self, widget, alignment=None): raise NotImplementedError("Label can contain only one QLabel") # --------------------------------------------------------------------------- # Module workflow notes # --------------------------------------------------------------------------- # # 1) Назначение модуля: # Стандартная текстовая метка в SContainer с поддержкой # централизованных стилей APP_STYLES, темизации и строкового # выравнивания. # # 2) Зависимости модуля: # Импорты: QLabel, QSizePolicy (PySide6.QtWidgets), # Qt, Slot (PySide6.QtCore), # SContainer (gui.containers.s_container), # APP_STYLES (gui.styles), # theme_bus (gui.theme_bus) # Хост-класс / базовый класс: SContainer # Внешние библиотеки: PySide6 (обязательна) # # 3) Экспорт: # Класс Label — публичный виджет метки. # Методы: style(), set_theme(), set_text(), get_text(), # set_alignment(), set_enabled(), # set_min/max_width/height(), set_fixed_size(), # set_tooltip(), set_size_policy(). # # 4) Состояние (поля): # _theme: str — текущая тема # _is_active: bool — признак активного состояния # _base_style_key: str — базовый ключ стиля ("WIDGET_LABEL") # _style_key_normal: str|None — явный нормальный стиль # _style_key_active: str|None — явный активный стиль # _label: QLabel — внутренний виджет метки # _ALIGN_MAP: dict — маппинг строк → Qt.Alignment # # 5) Последовательность действий и вызовов: # __init__(text="", **kwargs) # -> извлечение параметров из kwargs # -> super().__init__(...) # -> QLabel(text) -> _apply_alignment -> setSizePolicy # -> super().add_widget(_label) # -> style() -> theme_bus.theme_changed.connect(set_theme) # _apply_alignment(alignment) # -> если str → маппинг через _ALIGN_MAP → _label.setAlignment() # -> если Qt.Alignment → прямое применение # -> ValueError при невалидной строке # # 6) Побочные эффекты: # - Устанавливает stylesheet на QLabel. # - Подключается к theme_bus.theme_changed. # # 7) Границы ответственности: # Модуль НЕ поддерживает rich-text/HTML самостоятельно (хотя QLabel # может). НЕ обрабатывает клики. add_widget() заблокирован. # # 8) Обработка ошибок: # add_widget() бросает NotImplementedError. # _apply_alignment() бросает ValueError при невалидной строке. # set_theme() молча игнорирует невалидные значения. # # 9) Инварианты и контракты: # - Контейнер содержит ровно один QLabel. # - _theme ∈ {"dark", "light"}. # - Стиль: явный ключ → base_key + суффикс. # # 10) Правило сопровождения: # Для новых стилей — добавлять в APP_STYLES с суффиксами # _DARK/_LIGHT + _ACTIVE. __init__ принимает **kwargs — # при добавлении новых параметров обновлять извлечение.