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

249 lines
9.9 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/coordinate_input.py
"""Виджет для ввода координат"""
from PySide6.QtWidgets import QDoubleSpinBox, 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
from error_logger import log_exception
class CoordinateInput(SContainer):
"""Виджет для ввода координат с валидацией"""
def __init__(
self,
min_value: float = 0.0,
max_value: float = 100000.0,
decimals: int = 6,
step: float = 0.000001,
min_width: int = 150,
alignment: Qt.Alignment = Qt.AlignCenter,
parent=None,
style: str = "COORDINATE_INPUT",
active_style: str | None = None,
is_active: bool | None = None,
):
super().__init__(
width_percent=None,
height_percent=None,
margin=0,
style=style,
active_style=active_style,
is_active=is_active,
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._input = QDoubleSpinBox()
self._input.setRange(min_value, max_value)
self._input.setDecimals(decimals)
self._input.setSingleStep(step)
self._input.setMinimumWidth(min_width)
self._input.setAlignment(alignment)
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.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_prefix(self, prefix: str):
"""Установка префикса"""
self._input.setPrefix(prefix)
def set_range(self, min_val, max_val):
"""Установка диапазона"""
self._input.setRange(min_val, max_val)
def set_decimals(self, decimals: int):
"""Установка количества десятичных знаков"""
self._input.setDecimals(decimals)
def set_step(self, step: float) -> None:
"""Установка шага"""
self._input.setSingleStep(step)
def set_value(self, value):
"""Безопасная установка значения"""
try:
self._input.setValue(float(value))
except (ValueError, TypeError) as _exc:
log_exception(__name__, "set_value", _exc)
def get_value(self):
return self._input.value()
@property
def valueChanged(self):
"""Предоставить сигнал valueChanged из внутреннего QDoubleSpinBox."""
return self._input.valueChanged
def set_enabled(self, enabled: bool) -> None:
self._input.setEnabled(enabled)
super().setEnabled(enabled)
def set_min_width(self, width: int) -> None:
self._input.setMinimumWidth(width)
def set_min_height(self, height: int) -> None:
self._input.setMinimumHeight(height)
def set_max_width(self, width: int) -> None:
self._input.setMaximumWidth(width)
def set_max_height(self, height: int) -> None:
self._input.setMaximumHeight(height)
def set_fixed_size(self, width: int, height: int) -> None:
self._input.setMinimumSize(width, height)
self._input.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("CoordinateInput can contain only one QDoubleSpinBox")
# ---------------------------------------------------------------------------
# Module workflow notes
# ---------------------------------------------------------------------------
#
# 1) Назначение модуля:
# Виджет для ввода координат (широта, долгота и т.д.) на базе
# QDoubleSpinBox внутри SContainer, с поддержкой валидации
# диапазона, настраиваемой точностью и стилями APP_STYLES.
#
# 2) Зависимости модуля:
# Импорты: QDoubleSpinBox, QSizePolicy (PySide6.QtWidgets),
# Qt, Slot (PySide6.QtCore),
# SContainer (gui.containers.s_container),
# APP_STYLES (gui.styles),
# theme_bus (gui.theme_bus)
# Хост-класс / базовый класс: SContainer
# Внешние библиотеки: PySide6 (обязательна)
#
# 3) Экспорт:
# Класс CoordinateInput — публичный виджет ввода координат.
# Методы: style(), set_theme(), set_prefix(), set_range(),
# set_decimals(), set_step(), set_value(), get_value(),
# set_enabled(), set_min/max_width/height(), set_fixed_size(),
# set_tooltip(), set_size_policy().
# Свойство: valueChanged — сигнал QDoubleSpinBox.valueChanged.
#
# 4) Состояние (поля):
# _theme: str — текущая тема
# _is_active: bool — признак активного состояния
# _base_style_key: str — базовый ключ стиля ("COORDINATE_INPUT")
# _style_key_normal: str|None — явный ключ нормального стиля
# _style_key_active: str|None — явный ключ активного стиля
# _input: QDoubleSpinBox — внутренний виджет ввода
#
# 5) Последовательность действий и вызовов:
# __init__(min_value, max_value, decimals, step, min_width, alignment, ...)
# -> super().__init__(...)
# -> QDoubleSpinBox() с setRange, setDecimals, setSingleStep, setMinimumWidth
# -> super().add_widget(_input)
# -> style() -> theme_bus.theme_changed.connect(set_theme)
# set_value(value)
# -> try float(value) -> _input.setValue()
# -> except: pass (тихое игнорирование)
# valueChanged (property)
# -> делегирует к _input.valueChanged
#
# 6) Побочные эффекты:
# - Устанавливает stylesheet на QDoubleSpinBox.
# - Подключается к theme_bus.theme_changed.
#
# 7) Границы ответственности:
# Модуль НЕ интерпретирует значения координат семантически.
# НЕ выполняет геокодирование. add_widget() заблокирован.
#
# 8) Обработка ошибок:
# set_value() глотает ValueError/TypeError при некорректном вводе.
# add_widget() бросает NotImplementedError.
# set_theme() молча игнорирует невалидные темы.
#
# 9) Инварианты и контракты:
# - Контейнер содержит ровно один QDoubleSpinBox.
# - Значение всегда в пределах [min_value, max_value].
# - decimals определяет точность отображения.
#
# 10) Правило сопровождения:
# При добавлении суффикса/префикса — использовать set_prefix().
# Стили — через APP_STYLES с ключом COORDINATE_INPUT + суффиксы.