from __future__ import annotations from PySide6.QtCore import Qt from PySide6.QtGui import QIntValidator from PySide6.QtWidgets import ( QComboBox, QDialog, QFormLayout, QHBoxLayout, QLabel, QLineEdit, QPushButton, QTabWidget, QVBoxLayout, QWidget, ) from ameba_control_panel.services.settings_service import Settings def _create_int_input(value: int, lo: int, hi: int, suffix: str = "") -> QLineEdit: """Plain text input with integer validation.""" display = f"{value}{suffix}" if suffix else str(value) edit = QLineEdit(str(value)) edit.setValidator(QIntValidator(lo, hi)) edit.setPlaceholderText(f"{lo} - {hi}") edit.setFixedWidth(140) return edit def _int_value(edit: QLineEdit) -> int: """Get integer from a _num input, or 0 if invalid.""" try: return int(edit.text()) except (ValueError, TypeError): return 0 class SettingsDialog(QDialog): def __init__(self, settings: Settings, parent=None) -> None: super().__init__(parent) self.setWindowTitle("Settings") self.setMinimumWidth(480) self._settings = settings layout = QVBoxLayout(self) tabs = QTabWidget() layout.addWidget(tabs) # ── Font tab ────────────────────────────────────── font_tab = QWidget() font_form = QFormLayout(font_tab) from PySide6.QtGui import QFontDatabase self._font_family = QComboBox() self._font_family.setEditable(False) self._font_family.setMaxVisibleItems(10) for fam in sorted(QFontDatabase.families()): self._font_family.addItem(fam) self._font_family.setCurrentText(settings.font_family) self._font_family.currentTextChanged.connect(lambda v: self._auto_save("font_family", v)) font_form.addRow("Log font family", self._font_family) self._font_size = _create_int_input(settings.font_size, 6, 24) self._font_size.editingFinished.connect(lambda: self._auto_save("font_size", _int_value(self._font_size))) font_form.addRow("Log font size (pt)", self._font_size) tabs.addTab(font_tab, "Font") # ── Serial & Log tab ───────────────────────────── serial_tab = QWidget() serial_form = QFormLayout(serial_tab) self._default_baud = _create_int_input(settings.default_baud, 9600, 6_000_000) self._default_baud.editingFinished.connect(lambda: self._auto_save("default_baud", _int_value(self._default_baud))) serial_form.addRow("Default baud (bps)", self._default_baud) self._partial_hold = _create_int_input(settings.partial_line_hold_ms, 50, 2000) self._partial_hold.editingFinished.connect(lambda: self._auto_save("partial_line_hold_ms", _int_value(self._partial_hold))) serial_form.addRow("Partial line hold (ms)", self._partial_hold) self._scan_interval = _create_int_input(settings.port_scan_interval_sec, 1, 60) self._scan_interval.editingFinished.connect(lambda: self._auto_save("port_scan_interval_sec", _int_value(self._scan_interval))) serial_form.addRow("Port scan interval (sec)", self._scan_interval) self._log_tail = _create_int_input(settings.log_tail_lines, 1_000, 1_000_000) self._log_tail.editingFinished.connect(lambda: self._auto_save("log_tail_lines", _int_value(self._log_tail))) serial_form.addRow("Log buffer (lines)", self._log_tail) self._log_archive = _create_int_input(settings.get("log_archive_max"), 10_000, 2_000_000) self._log_archive.editingFinished.connect(lambda: self._auto_save("log_archive_max", _int_value(self._log_archive))) serial_form.addRow("Log archive max (lines)", self._log_archive) self._flush_interval = _create_int_input(settings.get("log_flush_interval_ms"), 10, 500) self._flush_interval.editingFinished.connect(lambda: self._auto_save("log_flush_interval_ms", _int_value(self._flush_interval))) serial_form.addRow("Log flush interval (ms)", self._flush_interval) self._flush_batch = _create_int_input(settings.get("log_flush_batch_limit"), 50, 1000) self._flush_batch.editingFinished.connect(lambda: self._auto_save("log_flush_batch_limit", _int_value(self._flush_batch))) serial_form.addRow("Log flush batch (lines)", self._flush_batch) tabs.addTab(serial_tab, "Serial") # ── Flash tab ───────────────────────────────────── flash_tab = QWidget() flash_form = QFormLayout(flash_tab) for label, key in [("Boot start addr", "default_boot_start"), ("Boot end addr", "default_boot_end"), ("App start addr", "default_app_start"), ("App end addr", "default_app_end"), ("NN start addr", "default_nn_start"), ("NN end addr", "default_nn_end")]: edit = QLineEdit(settings.get(key) or "") edit.setFixedWidth(140) edit.editingFinished.connect(lambda e=edit, k=key: self._auto_save(k, e.text())) flash_form.addRow(label, edit) tabs.addTab(flash_tab, "Flash") # ── Command tab ─────────────────────────────────── cmd_tab = QWidget() cmd_form = QFormLayout(cmd_tab) self._cmd_delay = _create_int_input(settings.cmd_delay_ms, 0, 60_000) self._cmd_delay.editingFinished.connect(lambda: self._auto_save("cmd_delay_ms", _int_value(self._cmd_delay))) cmd_form.addRow("Per-command delay (ms)", self._cmd_delay) self._char_delay = _create_int_input(settings.char_delay_ms, 0, 5_000) self._char_delay.editingFinished.connect(lambda: self._auto_save("char_delay_ms", _int_value(self._char_delay))) cmd_form.addRow("Per-char delay (ms)", self._char_delay) self._history_max = _create_int_input(settings.history_max_entries, 10, 10_000) self._history_max.editingFinished.connect(lambda: self._auto_save("history_max_entries", _int_value(self._history_max))) cmd_form.addRow("History max entries", self._history_max) tabs.addTab(cmd_tab, "Command") btn_row = QHBoxLayout() apply_btn = QPushButton("Apply") apply_btn.clicked.connect(self._on_apply) close_btn = QPushButton("Close") close_btn.clicked.connect(self.accept) btn_row.addStretch() btn_row.addWidget(apply_btn) btn_row.addWidget(close_btn) layout.addLayout(btn_row) self._apply_callback = None def set_apply_callback(self, callback) -> None: self._apply_callback = callback def _on_apply(self) -> None: if self._apply_callback: self._apply_callback() def _auto_save(self, key: str, value) -> None: self._settings.set(key, value) self._settings.save()