Add Dispatch_V0.1.1
This commit is contained in:
281
Dispatch_V0.1.1/gui/components/combo_box.py
Normal file
281
Dispatch_V0.1.1/gui/components/combo_box.py
Normal file
@@ -0,0 +1,281 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# gui/components/combo_box.py
|
||||
"""Обёртка над QComboBox с централизованными стилями."""
|
||||
|
||||
from PySide6.QtWidgets import QComboBox, QSizePolicy
|
||||
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
|
||||
|
||||
|
||||
class ComboBox(SContainer):
|
||||
"""Кастомный QComboBox с темизацией и стилями APP_STYLES."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
width_percent: int | None = None,
|
||||
height_percent: int | None = None,
|
||||
margin: int | tuple[int, int, int, int] = 0,
|
||||
style: str = "FORM_WIDGET",
|
||||
active_style: str | None = None,
|
||||
is_active: bool | None = None,
|
||||
content_fit: bool = True,
|
||||
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 = False
|
||||
self._base_style_key = style
|
||||
self._style_key_normal = None
|
||||
self._style_key_active = None
|
||||
|
||||
self._combo = QComboBox()
|
||||
self._combo.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
|
||||
super().add_widget(self._combo)
|
||||
|
||||
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.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._combo.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._combo.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_items(self, items: list[str]) -> None:
|
||||
"""Заменить список элементов."""
|
||||
self._combo.clear()
|
||||
self._combo.addItems(items)
|
||||
|
||||
def set_editable(self, editable: bool) -> None:
|
||||
self._combo.setEditable(editable)
|
||||
|
||||
def set_enabled(self, enabled: bool) -> None:
|
||||
"""Управление доступностью."""
|
||||
self._combo.setEnabled(enabled)
|
||||
super().setEnabled(enabled)
|
||||
|
||||
def set_min_width(self, width: int) -> None:
|
||||
"""Минимальная ширина."""
|
||||
self._combo.setMinimumWidth(width)
|
||||
super().setMinimumWidth(width)
|
||||
|
||||
def set_min_height(self, height: int) -> None:
|
||||
"""Минимальная высота."""
|
||||
self._combo.setMinimumHeight(height)
|
||||
super().setMinimumHeight(height)
|
||||
|
||||
def set_max_width(self, width: int) -> None:
|
||||
"""Максимальная ширина."""
|
||||
self._combo.setMaximumWidth(width)
|
||||
super().setMaximumWidth(width)
|
||||
|
||||
def set_max_height(self, height: int) -> None:
|
||||
"""Максимальная высота."""
|
||||
self._combo.setMaximumHeight(height)
|
||||
super().setMaximumHeight(height)
|
||||
|
||||
def set_fixed_size(self, width: int, height: int) -> None:
|
||||
"""Фиксированный размер."""
|
||||
self._combo.setMinimumSize(width, height)
|
||||
self._combo.setMaximumSize(width, height)
|
||||
super().setMinimumSize(width, height)
|
||||
super().setMaximumSize(width, height)
|
||||
|
||||
def set_index(self, index: int) -> None:
|
||||
"""Установить текущий индекс."""
|
||||
self._combo.setCurrentIndex(index)
|
||||
|
||||
def set_current_text(self, text: str) -> None:
|
||||
self._combo.setCurrentText(text)
|
||||
|
||||
def set_placeholder_text(self, text: str) -> None:
|
||||
"""Установить текст-заполнитель (если поддерживается Qt)."""
|
||||
line_edit = self._combo.lineEdit()
|
||||
if line_edit is not None:
|
||||
line_edit.setPlaceholderText(text)
|
||||
if hasattr(self._combo, "setPlaceholderText"):
|
||||
self._combo.setPlaceholderText(text)
|
||||
|
||||
def get_index(self) -> int:
|
||||
"""Получить текущий индекс."""
|
||||
return self._combo.currentIndex()
|
||||
|
||||
def get_current_text(self) -> str:
|
||||
return self._combo.currentText()
|
||||
|
||||
def set_tooltip(self, text: str) -> None:
|
||||
"""Подсказка."""
|
||||
self._combo.setToolTip(text)
|
||||
|
||||
def set_size_policy(self, horizontal, vertical) -> None:
|
||||
"""Политика размеров."""
|
||||
self._combo.setSizePolicy(horizontal, vertical)
|
||||
super().setSizePolicy(horizontal, vertical)
|
||||
|
||||
@property
|
||||
def current_index_changed(self):
|
||||
return self._combo.currentIndexChanged
|
||||
|
||||
@property
|
||||
def current_text_changed(self):
|
||||
return self._combo.currentTextChanged
|
||||
|
||||
@property
|
||||
def text_edited(self):
|
||||
line_edit = self._combo.lineEdit()
|
||||
if line_edit is None:
|
||||
raise AttributeError("text_edited is unavailable for non-editable ComboBox")
|
||||
return line_edit.textEdited
|
||||
|
||||
def set_item_enabled(self, index: int, enabled: bool) -> None:
|
||||
"""Включить/выключить элемент списка по индексу."""
|
||||
model = self._combo.model()
|
||||
if model is None:
|
||||
return
|
||||
item = model.item(index) if hasattr(model, "item") else None
|
||||
if item is not None and hasattr(item, "setEnabled"):
|
||||
item.setEnabled(bool(enabled))
|
||||
|
||||
def show_popup(self) -> None:
|
||||
self._combo.showPopup()
|
||||
|
||||
def add_widget(self, widget, alignment=None):
|
||||
raise NotImplementedError("ComboBox can contain only one QComboBox")
|
||||
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Module workflow notes
|
||||
# ---------------------------------------------------------------------------
|
||||
#
|
||||
# 1) Назначение модуля:
|
||||
# Обёртка над QComboBox, встроенная в SContainer, с поддержкой
|
||||
# централизованных стилей APP_STYLES и автоматическим
|
||||
# переключением тем (dark/light) через theme_bus.
|
||||
#
|
||||
# 2) Зависимости модуля:
|
||||
# Импорты: QComboBox, QSizePolicy (PySide6.QtWidgets),
|
||||
# Slot (PySide6.QtCore),
|
||||
# APP_STYLES (gui.styles),
|
||||
# theme_bus (gui.theme_bus),
|
||||
# SContainer (gui.containers.s_container)
|
||||
# Хост-класс / базовый класс: SContainer
|
||||
# Внешние библиотеки: PySide6 (обязательна)
|
||||
#
|
||||
# 3) Экспорт:
|
||||
# Класс ComboBox — публичный виджет выпадающего списка.
|
||||
# Методы: style(), set_theme(), set_items(), set_index(), get_index(),
|
||||
# set_placeholder_text(), set_enabled(), set_item_enabled(),
|
||||
# set_min/max_width/height(), set_fixed_size(), set_tooltip(),
|
||||
# set_size_policy().
|
||||
# Свойство: current_index_changed — сигнал currentIndexChanged.
|
||||
#
|
||||
# 4) Состояние (поля):
|
||||
# _theme: str — текущая тема ("dark" | "light")
|
||||
# _is_active: bool — признак активного состояния
|
||||
# _base_style_key: str — базовый ключ стиля (по умолчанию "FORM_WIDGET")
|
||||
# _style_key_normal: str|None — явный ключ нормального стиля
|
||||
# _style_key_active: str|None — явный ключ активного стиля
|
||||
# _combo: QComboBox — внутренний виджет
|
||||
#
|
||||
# 5) Последовательность действий и вызовов:
|
||||
# __init__(style="FORM_WIDGET", ...)
|
||||
# -> super().__init__(...)
|
||||
# -> QComboBox() -> setSizePolicy -> super().add_widget(_combo)
|
||||
# -> style() — первичное применение
|
||||
# -> theme_bus.theme_changed.connect(set_theme)
|
||||
# style(style_key?, active_key?, is_active?)
|
||||
# -> определяет ключ через комбинацию base_key + тема + active
|
||||
# -> _combo.setStyleSheet(APP_STYLES[key])
|
||||
# set_items(items)
|
||||
# -> _combo.clear() -> _combo.addItems(items)
|
||||
#
|
||||
# 6) Побочные эффекты:
|
||||
# - Устанавливает stylesheet на внутренний QComboBox.
|
||||
# - Подключается к theme_bus.theme_changed при создании.
|
||||
#
|
||||
# 7) Границы ответственности:
|
||||
# Модуль НЕ хранит бизнес-данные выбранного элемента.
|
||||
# НЕ валидирует содержимое списка.
|
||||
# add_widget() заблокирован — компонент запечатан.
|
||||
#
|
||||
# 8) Обработка ошибок:
|
||||
# add_widget() бросает NotImplementedError.
|
||||
# set_theme() молча игнорирует невалидные значения.
|
||||
# set_item_enabled() безопасно пропускает отсутствующий model/item.
|
||||
#
|
||||
# 9) Инварианты и контракты:
|
||||
# - Контейнер содержит ровно один QComboBox.
|
||||
# - _theme ∈ {"dark", "light"}.
|
||||
# - Стиль разрешается по цепочке: явный ключ → base_key + суффикс темы.
|
||||
#
|
||||
# 10) Правило сопровождения:
|
||||
# Новые стили — добавлять в APP_STYLES с суффиксами _DARK/_LIGHT/_DARK_ACTIVE/_LIGHT_ACTIVE.
|
||||
# Делегирующие методы дублировать на _combo и super().
|
||||
Reference in New Issue
Block a user