193 lines
7.5 KiB
Python
193 lines
7.5 KiB
Python
"""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)
|