"""Typed section widgets and the scalar Qt boundary shared by GUI settings.""" from collections.abc import Callable from dataclasses import dataclass, field from typing import TypeAlias, TypeVar from PyQt6.QtWidgets import ( QCheckBox, QComboBox, QDoubleSpinBox, QGroupBox, QLineEdit, QPlainTextEdit, QSpinBox, QWidget, QLabel, QPushButton, QStackedWidget, ) from python_app.gui.antenna_geometry_editor import AntennaGeometryEditor from python_app.models.setting_fields import Scalar ScalarWidget: TypeAlias = QLineEdit | QPlainTextEdit | QComboBox | QCheckBox | QSpinBox | QDoubleSpinBox SettingWidget: TypeAlias = ScalarWidget | AntennaGeometryEditor _Widget = TypeVar("_Widget", bound=SettingWidget) @dataclass(slots=True) class SectionView: group: QGroupBox fields: dict[str, SettingWidget] = field(default_factory=dict) def add(self, name: str, widget: _Widget) -> _Widget: self.fields[name] = widget return widget @dataclass(slots=True, kw_only=True) class PreprocessView(SectionView): labels: dict[str, QLabel] @dataclass(slots=True, kw_only=True) class PrimaryView(SectionView): start: QPushButton single_capture: QPushButton stop: QPushButton apply_radar: QPushButton load: QPushButton save: QPushButton preprocess: QPushButton clear: QPushButton @dataclass(slots=True, kw_only=True) class DataView(SectionView): save: QPushButton save_json: QPushButton remove_last: QPushButton capture_tmp: QPushButton record: QPushButton browse: QPushButton save_path: QLineEdit save_name: QLineEdit record_count: QSpinBox @dataclass(slots=True, kw_only=True) class SwitchView(SectionView): select_text: QPushButton select_single: QPushButton combos: QLineEdit single_input: QLineEdit single_output: QLineEdit display_combo: QComboBox next_combo: QPushButton @dataclass(slots=True, kw_only=True) class ProcessingView(SectionView): mode: QComboBox pages: QStackedWidget geometry_page: QWidget geometries: tuple[AntennaGeometryEditor, AntennaGeometryEditor] fixed_y: QCheckBox y_bounds: tuple[QDoubleSpinBox, QDoubleSpinBox] speed_controls: tuple[tuple[QCheckBox, QDoubleSpinBox], ...] trace_controls: tuple[ScalarWidget, ...] visual_controls: tuple[ScalarWidget, ...] locator_controls: tuple[ScalarWidget, ...] frequency_controls: tuple[QDoubleSpinBox, ...] @dataclass(slots=True, kw_only=True) class RadarView(SectionView): sweep_edits: tuple[QLineEdit, ...] sweep_labels: tuple[QLabel, ...] limits_hint: QLabel vna_panel: QWidget adc_panel: QWidget optical_enabled: QCheckBox optical_mode: QComboBox optical_common: tuple[ScalarWidget, ...] optical_manual: tuple[ScalarWidget, ...] optical_variation: tuple[ScalarWidget, ...] manual_panel: QWidget variation_panel: QWidget def read_setting(widget: ScalarWidget) -> Scalar: if isinstance(widget, QCheckBox): return widget.isChecked() if isinstance(widget, (QSpinBox, QDoubleSpinBox)): return widget.value() if isinstance(widget, QComboBox): return widget.currentText() if isinstance(widget, QPlainTextEdit): return widget.toPlainText() return widget.text() def write_setting(widget: ScalarWidget, value: Scalar) -> None: if isinstance(widget, QCheckBox): widget.setChecked(bool(value)) elif isinstance(widget, QSpinBox): widget.setValue(int(value)) elif isinstance(widget, QDoubleSpinBox): widget.setValue(float(value)) elif isinstance(widget, QComboBox): text = str(value) index = widget.findText(text) if index < 0: widget.addItem(text) index = widget.count() - 1 widget.setCurrentIndex(index) elif isinstance(widget, QPlainTextEdit): widget.setPlainText(str(value)) else: widget.setText(str(value)) def connect_setting(widget: ScalarWidget, callback: Callable[..., object]) -> None: if isinstance(widget, QCheckBox): signal = widget.toggled elif isinstance(widget, (QSpinBox, QDoubleSpinBox)): signal = widget.valueChanged elif isinstance(widget, QComboBox): signal = widget.currentTextChanged elif isinstance(widget, QPlainTextEdit): signal = widget.textChanged else: signal = widget.editingFinished signal.connect(callback) def double_spin(minimum: float, maximum: float, decimals: int, step: float) -> QDoubleSpinBox: widget = QDoubleSpinBox() widget.setDecimals(decimals) widget.setRange(minimum, maximum) widget.setSingleStep(step) return widget def integer_spin(minimum: int, maximum: int) -> QSpinBox: widget = QSpinBox() widget.setRange(minimum, maximum) return widget def choice(options: list[str]) -> QComboBox: widget = QComboBox() widget.addItems(options) return widget