"""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()