Files
2026-04-29 08:18:54 +04:00

266 lines
11 KiB
Python
Raw Permalink 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/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 —
# при добавлении новых параметров обновлять извлечение.