"""UI construction mixin for the main radar control window.""" from __future__ import annotations from PyQt6.QtWidgets import ( QComboBox, QFrame, QGroupBox, QHBoxLayout, QLabel, QPlainTextEdit, QPushButton, QScrollArea, QStackedWidget, QVBoxLayout, QWidget, ) import pyqtgraph as pg from python_app.gui.controllers.sections import ( build_data_actions_group, build_hardware_actions_group, build_pipeline_group, build_preprocess_summary_group, build_processing_group, build_radar_group, build_switch_group, ) class AppWindowUiMixin: """Builds and wires all static UI widgets.""" def _build_ui(self) -> None: """Build main window widgets, plot area, and settings panel.""" self.setWindowTitle("Radar System Control") root = QWidget(self) self.setCentralWidget(root) layout = QHBoxLayout(root) layout.setContentsMargins(12, 12, 12, 12) layout.setSpacing(14) self._plot_stack = QStackedWidget(root) self._plot = pg.PlotWidget(background="#0f141c") self._plot.showGrid(x=True, y=True, alpha=0.2) self._plot.setLabel("bottom", "Frequency", units="Hz") self._plot.setLabel("left", "Magnitude", units="dB") self._plot_stack.addWidget(self._plot) self._trace_plots_container = QWidget(root) trace_layout = QVBoxLayout(self._trace_plots_container) trace_layout.setContentsMargins(0, 0, 0, 0) trace_layout.setSpacing(6) self._trace_magnitude_plot = pg.PlotWidget(background="#0f141c") self._trace_magnitude_plot.showGrid(x=True, y=True, alpha=0.2) self._trace_magnitude_plot.setLabel("left", "Magnitude", units="dB") self._trace_magnitude_plot.getPlotItem().showAxis("bottom", show=False) self._trace_magnitude_plot.getPlotItem().setDownsampling(mode="peak") self._trace_magnitude_plot.getPlotItem().setClipToView(True) trace_layout.addWidget(self._trace_magnitude_plot, stretch=1) self._trace_phase_plot = pg.PlotWidget(background="#0f141c") self._trace_phase_plot.showGrid(x=True, y=True, alpha=0.2) self._trace_phase_plot.setLabel("left", "Phase", units="deg") self._trace_phase_plot.setLabel("bottom", "Frequency", units="Hz") self._trace_phase_plot.getPlotItem().setDownsampling(mode="peak") self._trace_phase_plot.getPlotItem().setClipToView(True) trace_layout.addWidget(self._trace_phase_plot, stretch=1) self._plot_stack.addWidget(self._trace_plots_container) self._plot_stack.setCurrentWidget(self._trace_plots_container) layout.addWidget(self._plot_stack, stretch=11) self._settings_toggle_button = QPushButton("<") self._settings_toggle_button.setObjectName("settingsToggleButton") self._settings_toggle_button.setFixedWidth(26) self._settings_toggle_button.clicked.connect(self._toggle_settings_panel) layout.addWidget(self._settings_toggle_button, stretch=0) self._settings_panel = QWidget(root) self._settings_panel.setMinimumWidth(659) right_layout = QVBoxLayout(self._settings_panel) right_layout.setContentsMargins(0, 0, 0, 0) right_layout.setSpacing(10) # Build log widget early so error handlers can safely write during UI construction. self._log_box = QPlainTextEdit(self._settings_panel) self._log_box.setReadOnly(True) self._log_box.setMinimumHeight(170) pipeline_group = self._build_pipeline_group() hardware_actions_group = self._build_hardware_actions_group() data_actions_group = self._build_data_actions_group() preprocess_summary_group = self._build_preprocess_summary_group() radar_group = self._build_radar_group() processing_group = self._build_processing_group() switch_group = self._build_switch_group() controls = QWidget(self._settings_panel) controls_layout = QVBoxLayout(controls) controls_layout.setContentsMargins(0, 0, 0, 0) controls_layout.setSpacing(10) controls_layout.addWidget(pipeline_group) controls_layout.addWidget(hardware_actions_group) controls_layout.addWidget(data_actions_group) controls_layout.addWidget(preprocess_summary_group) controls_layout.addWidget(processing_group) controls_layout.addWidget(radar_group) controls_layout.addWidget(switch_group) controls_layout.addStretch(1) scroll = QScrollArea(self._settings_panel) scroll.setWidgetResizable(True) scroll.setFrameShape(QFrame.Shape.NoFrame) scroll.setWidget(controls) right_layout.addWidget(scroll, stretch=1) self._status_label = QLabel("Status: idle", self._settings_panel) self._status_label.setObjectName("statusLabel") right_layout.addWidget(self._status_label) self._history_label = QLabel("History: raw=0, preprocessed=0, results=0", self._settings_panel) self._history_label.setObjectName("hintLabel") right_layout.addWidget(self._history_label) right_layout.addWidget(self._log_box, stretch=0) layout.addWidget(self._settings_panel, stretch=8) self._set_settings_panel_visible(True) self.resize(1650, 940) def _toggle_settings_panel(self) -> None: """Toggle settings panel visibility.""" self._set_settings_panel_visible(not self._settings_panel.isVisible()) def _set_settings_panel_visible(self, visible: bool) -> None: """Set settings panel visibility and update toggle button glyph.""" self._settings_panel.setVisible(visible) if visible: self._settings_toggle_button.setText(">") self._settings_toggle_button.setToolTip("Hide settings panel") else: self._settings_toggle_button.setText("<") self._settings_toggle_button.setToolTip("Show settings panel") def _set_plot_mode(self, mode: str) -> None: """Switch visible plot surface based on processing mode.""" if mode == "bscan": self._plot_stack.setCurrentWidget(self._plot) return self._plot_stack.setCurrentWidget(self._trace_plots_container) def _build_pipeline_group(self) -> QGroupBox: """Build pipeline controls section.""" return build_pipeline_group(self) def _build_hardware_actions_group(self) -> QGroupBox: """Build hardware actions section.""" return build_hardware_actions_group(self) def _build_data_actions_group(self) -> QGroupBox: """Build data actions section.""" return build_data_actions_group(self) def _build_preprocess_summary_group(self) -> QGroupBox: """Build selected preprocess sets summary section.""" return build_preprocess_summary_group(self) def _build_processing_group(self) -> QGroupBox: """Build processing mode section.""" return build_processing_group(self) def _build_radar_group(self) -> QGroupBox: """Build radar settings section.""" return build_radar_group(self) def _build_switch_group(self) -> QGroupBox: """Build switch settings section.""" return build_switch_group(self) @staticmethod def _set_combo_current_text(combo: QComboBox, value: str) -> None: """Select combo item by text, appending it when missing.""" index = combo.findText(value) if index >= 0: combo.setCurrentIndex(index) return combo.addItem(value) combo.setCurrentIndex(combo.count() - 1)