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

290 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/text_input.py
"""Поле ввода текста"""
from PySide6.QtWidgets import QApplication, QLineEdit, QSizePolicy, QTextEdit
from PySide6.QtCore import Slot
from gui.styles import APP_STYLES
from gui.theme_bus import theme_bus
from gui.containers.s_container import SContainer
from error_logger import log_exception
class TextInput(SContainer):
"""Поле ввода текста на базе SContainer."""
def __init__(
self,
text: str = "",
placeholder: str = "",
width_percent: int | None = None,
height_percent: int | None = None,
margin: int | tuple[int, int, int, int] = 0,
parent=None,
style: str = "TEXT_INPUT",
active_style: str | None = None,
is_active: bool | None = None,
content_fit: bool = True,
multiline: bool = False,
):
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 = False
self._base_style_key = style
self._style_key_normal = None
self._style_key_active = None
self._is_multiline = bool(multiline)
if self._is_multiline:
self._input = QTextEdit()
self._input.setPlainText(text)
self._input.setPlaceholderText(placeholder)
else:
self._input = QLineEdit(text)
self._input.setPlaceholderText(placeholder)
self._input.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
super().add_widget(self._input)
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._input.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._input.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:
if self._is_multiline:
self._input.setPlainText(text)
return
self._input.setText(text)
def get_text(self) -> str:
if self._is_multiline:
return self._input.toPlainText()
return self._input.text()
def clear(self) -> None:
self._input.clear()
def set_placeholder(self, text: str) -> None:
self._input.setPlaceholderText(text)
def set_enabled(self, enabled: bool) -> None:
self._input.setEnabled(enabled)
super().setEnabled(enabled)
def set_read_only(self, readonly: bool) -> None:
self._input.setReadOnly(readonly)
def set_validator(self, validator) -> None:
if self._is_multiline:
return
self._input.setValidator(validator)
def install_event_filter(self, event_filter_obj) -> None:
self._input.installEventFilter(event_filter_obj)
def has_focus_within(self) -> bool:
focused = QApplication.focusWidget()
if focused is None:
return False
if focused is self._input:
return True
try:
return bool(self._input.isAncestorOf(focused))
except Exception as e:
log_exception(__name__, "has_focus_within", e)
return False
def clear_focus(self) -> None:
self._input.clearFocus()
def set_min_width(self, width: int) -> None:
self._input.setMinimumWidth(width)
super().setMinimumWidth(width)
def set_min_height(self, height: int) -> None:
self._input.setMinimumHeight(height)
super().setMinimumHeight(height)
def set_max_width(self, width: int) -> None:
self._input.setMaximumWidth(width)
super().setMaximumWidth(width)
def set_max_height(self, height: int) -> None:
self._input.setMaximumHeight(height)
super().setMaximumHeight(height)
def set_fixed_size(self, width: int, height: int) -> None:
self._input.setMinimumSize(width, height)
self._input.setMaximumSize(width, height)
super().setMinimumSize(width, height)
super().setMaximumSize(width, height)
def set_tooltip(self, text: str) -> None:
self._input.setToolTip(text)
def set_size_policy(self, horizontal, vertical) -> None:
self._input.setSizePolicy(horizontal, vertical)
super().setSizePolicy(horizontal, vertical)
def add_widget(self, widget, alignment=None):
raise NotImplementedError("TextInput can contain only one QLineEdit")
@property
def text_changed(self):
return self._input.textChanged
@property
def return_pressed(self):
if self._is_multiline:
raise AttributeError("return_pressed is unavailable for multiline TextInput")
return self._input.returnPressed
@property
def editing_finished(self):
if self._is_multiline:
raise AttributeError("editing_finished is unavailable for multiline TextInput")
return self._input.editingFinished
@property
def text_edited(self):
if self._is_multiline:
raise AttributeError("text_edited is unavailable for multiline TextInput")
return self._input.textEdited
# ---------------------------------------------------------------------------
# Module workflow notes
# ---------------------------------------------------------------------------
#
# 1) Назначение модуля:
# Поле ввода текста на базе QLineEdit в SContainer с поддержкой
# placeholder, централизованных стилей APP_STYLES и автоматической
# темизации.
#
# 2) Зависимости модуля:
# Импорты: QLineEdit, QSizePolicy (PySide6.QtWidgets),
# Slot (PySide6.QtCore),
# APP_STYLES (gui.styles),
# theme_bus (gui.theme_bus),
# SContainer (gui.containers.s_container)
# Хост-класс / базовый класс: SContainer
# Внешние библиотеки: PySide6 (обязательна)
#
# 3) Экспорт:
# Класс TextInput — публичный виджет поля ввода.
# Методы: style(), set_theme(), set_text(), get_text(), clear(),
# set_enabled(), set_read_only(),
# set_min/max_width/height(), set_fixed_size(),
# set_tooltip(), set_size_policy().
# Свойство: text_changed — сигнал QLineEdit.textChanged.
#
# 4) Состояние (поля):
# _theme: str — текущая тема
# _is_active: bool — признак активного состояния
# _base_style_key: str — базовый ключ стиля ("TEXT_INPUT")
# _style_key_normal: str|None — явный нормальный стиль
# _style_key_active: str|None — явный активный стиль
# _input: QLineEdit — внутренний виджет
#
# 5) Последовательность действий и вызовов:
# __init__(text, placeholder, ...)
# -> super().__init__(...)
# -> QLineEdit(text) -> setPlaceholderText -> setSizePolicy
# -> super().add_widget(_input)
# -> style() -> theme_bus.theme_changed.connect(set_theme)
# text_changed (property)
# -> делегирует к _input.textChanged
#
# 6) Побочные эффекты:
# - Устанавливает stylesheet на QLineEdit.
# - Подключается к theme_bus.theme_changed.
#
# 7) Границы ответственности:
# Модуль НЕ валидирует содержимое ввода.
# НЕ поддерживает маски ввода (для этого — наследник).
# add_widget() заблокирован.
#
# 8) Обработка ошибок:
# add_widget() бросает NotImplementedError.
# set_theme() молча игнорирует невалидные значения.
#
# 9) Инварианты и контракты:
# - Контейнер содержит ровно один QLineEdit.
# - _theme ∈ {"dark", "light"}.
#
# 10) Правило сопровождения:
# Для добавления валидации — использовать QValidator извне через
# _input (расширить API при необходимости). Стили — через APP_STYLES
# с ключом TEXT_INPUT + суффиксы.