- обновлён простой режим: тест цвета и проверка подсветки кнопок объединены в раздел «Тест подсветки» - добавлена проверка подсветки кнопок через 8-й силовой выход с автоматическим коротким тестом - упрощены описания пользовательских проверок и убраны лишние технические подробности из простого режима - изменена раскладка теста цвета, увеличена длительность шага до 1 секунды и убраны описания из PRO-блока - добавлена защита выпадающих списков от случайного изменения колесом мыши - улучшены немодальные PRO-диалоги, настройки соединения и обработка событий COM-порта - обновлены визуальные детали приложения, пользовательские ресурсы, инструкции AGENTS и Release Notes 1.3.0
239 lines
8.9 KiB
Python
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()
|