plate-config-app/app/widgets/dialogs/settings_dialog.py
Danil Iarosh 9fd10a3acd v1.3.0: simple mode backlight and UI polish
- обновлён простой режим: тест цвета и проверка подсветки кнопок объединены в раздел «Тест подсветки»

- добавлена проверка подсветки кнопок через 8-й силовой выход с автоматическим коротким тестом

- упрощены описания пользовательских проверок и убраны лишние технические подробности из простого режима

- изменена раскладка теста цвета, увеличена длительность шага до 1 секунды и убраны описания из PRO-блока

- добавлена защита выпадающих списков от случайного изменения колесом мыши

- улучшены немодальные PRO-диалоги, настройки соединения и обработка событий COM-порта

- обновлены визуальные детали приложения, пользовательские ресурсы, инструкции AGENTS и Release Notes 1.3.0
2026-05-26 12:59:21 +03:00

239 lines
8.9 KiB
Python

"""Settings dialog — accent colour and light / dark mode."""
from __future__ import annotations
from PySide6.QtCore import Qt, Signal
from PySide6.QtWidgets import (
QButtonGroup,
QCheckBox,
QHBoxLayout,
QLabel,
QPushButton,
QSpinBox,
QToolButton,
QVBoxLayout,
QWidget,
)
from ...services import settings_service
from ...ui.icons import icon
from .styled_dialog import StyledDialog
class SettingsDialog(StyledDialog):
"""Compact settings dialog: accent colour + light/dark toggle."""
theme_changed = Signal()
health_settings_changed = Signal()
_ACCENT_COLORS = {
"green": "#16a34a",
"blue": "#2563eb",
}
def __init__(self, parent: QWidget | None = None) -> None:
super().__init__(parent)
self._live_updates_enabled = False
self.setWindowTitle("Настройки приложения")
self.setMinimumWidth(300)
self.resize(340, 280)
lay = self.containerLayout()
lay.setSpacing(16)
# ── Accent colour ──────────────────────────────────────────
accent_title = QLabel("Цвет акцента")
accent_title.setObjectName("SectionTitle")
lay.addWidget(accent_title)
accent_row = QHBoxLayout()
accent_row.setSpacing(10)
accent_row.setAlignment(Qt.AlignmentFlag.AlignLeft)
self._accent_group = QButtonGroup(self)
self._accent_group.setExclusive(True)
current_accent = settings_service.theme_accent()
for name, color in self._ACCENT_COLORS.items():
swatch = QToolButton()
swatch.setObjectName("AccentSwatch")
swatch.setCheckable(True)
swatch.setChecked(name == current_accent)
swatch.setFixedSize(32, 32)
swatch.setCursor(Qt.CursorShape.PointingHandCursor)
swatch.setStyleSheet(
f"QToolButton#AccentSwatch {{ background: {color}; }}"
)
swatch.setToolTip(name.capitalize())
swatch.setProperty("accent_name", name)
self._accent_group.addButton(swatch)
accent_row.addWidget(swatch)
lay.addLayout(accent_row)
# ── Light / Dark toggle ────────────────────────────────────
mode_title = QLabel("Тема оформления")
mode_title.setObjectName("SectionTitle")
lay.addWidget(mode_title)
mode_row = QHBoxLayout()
mode_row.setSpacing(8)
mode_row.setAlignment(Qt.AlignmentFlag.AlignLeft)
current_mode = settings_service.theme_mode()
self._light_btn = QPushButton(" Светлая")
self._light_btn.setCheckable(True)
self._light_btn.setChecked(current_mode == "light")
self._light_btn.setCursor(Qt.CursorShape.PointingHandCursor)
self._light_btn.setMinimumWidth(120)
light_ico = icon("fa5s.sun", color="#f59e0b")
if not light_ico.isNull():
self._light_btn.setIcon(light_ico)
self._dark_btn = QPushButton(" Тёмная")
self._dark_btn.setCheckable(True)
self._dark_btn.setChecked(current_mode == "dark")
self._dark_btn.setCursor(Qt.CursorShape.PointingHandCursor)
self._dark_btn.setMinimumWidth(120)
dark_ico = icon("fa5s.moon", color="#6366f1")
if not dark_ico.isNull():
self._dark_btn.setIcon(dark_ico)
self._mode_group = QButtonGroup(self)
self._mode_group.setExclusive(True)
self._mode_group.addButton(self._light_btn)
self._mode_group.addButton(self._dark_btn)
mode_row.addWidget(self._light_btn)
mode_row.addWidget(self._dark_btn)
lay.addLayout(mode_row)
# ── Health-check ───────────────────────────────────────────
hc_title = QLabel("Проверка состояния платы")
hc_title.setObjectName("SectionTitle")
lay.addWidget(hc_title)
self._hc_enabled = QCheckBox("Автоматическая проверка связи")
self._hc_enabled.setChecked(settings_service.health_check_enabled())
self._hc_enabled.toggled.connect(self._on_hc_toggled)
lay.addWidget(self._hc_enabled)
hc_interval_row = QHBoxLayout()
hc_interval_row.setSpacing(8)
hc_interval_row.setAlignment(Qt.AlignmentFlag.AlignLeft)
hc_interval_label = QLabel("Интервал (сек):")
hc_interval_row.addWidget(hc_interval_label)
self._hc_interval = QSpinBox()
self._hc_interval.setRange(5, 60)
self._hc_interval.setSingleStep(5)
self._hc_interval.setValue(settings_service.health_check_interval())
self._hc_interval.setEnabled(self._hc_enabled.isChecked())
hc_interval_row.addWidget(self._hc_interval)
lay.addLayout(hc_interval_row)
# ── Apply button ───────────────────────────────────────────
lay.addSpacing(8)
self._apply_btn = QPushButton(" Готово")
self._apply_btn.setProperty("role", "primary")
self._apply_btn.setCursor(Qt.CursorShape.PointingHandCursor)
self._apply_btn.setMinimumHeight(36)
apply_ico = icon("fa5s.check", color="#ffffff")
if not apply_ico.isNull():
self._apply_btn.setIcon(apply_ico)
self._apply_btn.clicked.connect(self._on_apply)
lay.addWidget(self._apply_btn)
# ── Live updates ───────────────────────────────────────────
self._accent_group.buttonToggled.connect(self._on_live_settings_changed)
self._mode_group.buttonToggled.connect(self._on_live_settings_changed)
self._hc_interval.valueChanged.connect(self._on_live_settings_changed)
self._live_updates_enabled = True
# ── Handlers ───────────────────────────────────────────────────
def _selected_accent(self) -> str:
for btn in self._accent_group.buttons():
if btn.isChecked():
return str(btn.property("accent_name"))
return "green"
def _selected_mode(self) -> str:
return "dark" if self._dark_btn.isChecked() else "light"
def _find_main_window(self):
from ...main_window import MainWindow
parent = self.parent()
while parent is not None:
if isinstance(parent, MainWindow):
return parent
parent = parent.parent()
return None
def _apply_theme_fallback(self, mode: str, accent: str) -> None:
# Fallback: just apply stylesheet without icon refresh
from PySide6.QtWidgets import QApplication
from ...ui.theme import build_stylesheet, get_palette
app = QApplication.instance()
if isinstance(app, QApplication):
palette = get_palette(mode, accent)
app.setStyleSheet(build_stylesheet(palette))
def _apply_settings(self) -> None:
mode = self._selected_mode()
accent = self._selected_accent()
hc_enabled = self._hc_enabled.isChecked()
hc_interval = self._hc_interval.value()
theme_changed = (
mode != settings_service.theme_mode()
or accent != settings_service.theme_accent()
)
health_changed = (
hc_enabled != settings_service.health_check_enabled()
or hc_interval != settings_service.health_check_interval()
)
if theme_changed:
settings_service.put("theme_mode", mode)
settings_service.put("theme_accent", accent)
if health_changed:
settings_service.put("health_check_enabled", hc_enabled)
settings_service.put("health_check_interval", hc_interval)
if not (theme_changed or health_changed):
return
main_window = self._find_main_window()
if theme_changed:
if main_window is not None:
main_window.apply_theme(mode, accent)
else:
self._apply_theme_fallback(mode, accent)
self.theme_changed.emit()
if health_changed:
if main_window is not None:
main_window.apply_runtime_settings()
self.health_settings_changed.emit()
def _on_live_settings_changed(self, *_args) -> None:
if not self._live_updates_enabled:
return
self._apply_settings()
def _on_hc_toggled(self, checked: bool) -> None:
self._hc_interval.setEnabled(checked)
self._on_live_settings_changed()
def _on_apply(self) -> None:
self._apply_settings()
self.close()