Files
radar_system/python_app/gui/app_window.py
T
2026-10-06 12:32:26 +03:00

132 lines
7.4 KiB
Python

"""Assemble GUI controllers and own the lifetime of the application window."""
from pathlib import Path
from PyQt6.QtWidgets import QMainWindow
from python_app.gui.controllers.config_controller import ConfigController
from python_app.gui.controllers.control_button_controller import ControlButtonController
from python_app.gui.controllers.history_controller import HistoryController
from python_app.gui.controllers.live_settings_controller import LiveSettingsController
from python_app.gui.controllers.log_controller import LogController
from python_app.gui.controllers.pipeline_controller import PipelineController
from python_app.gui.controllers.plot_controller import PlotController
from python_app.gui.controllers.preprocess_controller import PreprocessController
from python_app.gui.controllers.profile_controller import ProfileController, load_startup
from python_app.gui.controllers.radar_settings_controller import RadarSettingsController
from python_app.gui.controllers.recording_controller import RecordingController
from python_app.gui.controllers.snapshot_controller import SnapshotController
from python_app.gui.controllers.web_controller import WebBridge
from python_app.gui.settings_defaults import cpp_bscan_replay_window_for_config
from python_app.gui.window_view import WindowView
from python_app.orchestration.config_writer import ConfigWriter
from python_app.orchestration.live_processing_config import ProcessingLiveConfigWriter
from python_app.storage.npz.store import NpzStore
class AppWindow(QMainWindow):
def __init__(self, project_root: Path) -> None:
super().__init__()
self._closed = False
self._log = LogController(self, project_root, lambda text: self._view.status.setText(text))
state = load_startup(project_root, self._log)
runtime = project_root / "python_app/runtime"
store = NpzStore(project_root / "python_app/data")
published: dict[int, dict] = {}
self._history = HistoryController()
self._plots = PlotController(
self._history, lambda: self._config.processing_settings(), lambda: self._config.geometry(),
lambda: self._pipeline.running, self._log.warning,
)
self._plots.live_window = cpp_bscan_replay_window_for_config(state.config)
self._view = WindowView(self, state.config, state.gui,
self._plots.stack, self._log.panel, project_root=project_root)
fields = {key: widget for section in self._view.sections for key, widget in section.fields.items()}
fields["_log_level_combo"] = self._log.level
self._config = ConfigController(fields, self._view.switches.select_single.isChecked)
self._plots.bind_display_controls(self._view.switches.display_combo,
self._view.switches.next_combo)
def write_profile(path: Path, *, allow_overwrite: bool = True, dataset: bool = False):
return self._profile.write(path, allow_overwrite=allow_overwrite, dataset=dataset)
self._recording = RecordingController(
store, published, write_profile,
self._log.info, self._log.show_error,
)
self._pipeline = PipelineController(
project_root, store, ConfigWriter(runtime), self._history, self._plots, self._recording,
build_config=lambda: self._profile.build_config(), live_config=lambda: self._live.build(),
publish_live=lambda: self._live.publish(), capture_guard=lambda: self._preprocess.busy_reason(),
refresh_radar_limits=lambda: self._radar.refresh_limits(), status=self._view.status.setText,
history_changed=lambda: self._view.update_history(self._history),
poll_ui=lambda: self._web.refresh_snapshot(), show_exception=self._log.show_exception,
deferred_close=self.close, event_scope=self._log.suspend_error_capture,
)
self._preprocess = PreprocessController(
self, state.config, state.gui.preprocess_dialog, store, self._pipeline, self._plots,
build_config=lambda: self._profile.build_config(), current_radar_key=lambda: self._profile.radar_key(),
geometry=self._config.geometry, summary=self._view.update_preprocess, error=self._log.show_exception,
)
self._snapshots = SnapshotController(
self._pipeline, store, write_profile,
destination=self._config.dataset_destination, build_config=lambda: self._profile.build_config(),
processing_settings=published, allowed=lambda deletion: self._preprocess.data_action_allowed(deletion=deletion),
log=self._log.info, error=self._log.show_exception,
)
self._radar = RadarSettingsController(
self._view.radar, self._view.switches, self._view.processing, lambda: state.config, self._log,
reset_preprocess=self._preprocess.reset_selection, live_changed=lambda: self._live.changed(),
)
self._live = LiveSettingsController(
self._view.processing, self._config, self._pipeline, self._preprocess, self._radar,
ProcessingLiveConfigWriter(runtime / "processing_live.json"), published, self._log,
build_config=lambda: self._profile.build_config(),
)
self._profile = ProfileController(self, project_root, state, self._config, self._preprocess,
self._pipeline, self._radar, self._live, self._log)
self._control_button = ControlButtonController(self, lambda: state.config.control_button,
self._preprocess.capture_tmp_reference, self._log)
self._web = WebBridge(
self, project_root / "run_configs", self._pipeline, self._preprocess, self._snapshots,
self._profile, self._live, self._log,
save_path=self._view.data.save_path,
save_name=self._view.data.save_name,
record_count=self._view.data.record_count,
)
self._pipeline.bind_actions(self._view.primary)
self._snapshots.bind_actions(self._view.primary, self._view.data)
self._preprocess.bind_actions(self._view.primary, self._view.data)
self._profile.bind_actions(self._view.primary)
self._radar.sync_profile(state.gui.switches.combo_mode)
self._live.sync_controls()
self._view.update_preprocess(self._preprocess.selected_sets)
self._radar.initial_limits()
self._live.mode_changed(state.gui.processing.selected_mode)
self._live.publish()
self._pipeline.timer.start()
self._pipeline.schedule_startup()
self._control_button.start()
self._web.start_server()
self._log.info(f"Active config profile: {state.path}")
def closeEvent(self, event) -> None: # noqa: N802
if self._closed:
super().closeEvent(event)
return
if not self._pipeline.begin_close():
event.ignore()
return
self._closed = True
self._log.info("Window closing; shutting down runtime")
try:
for close in (self._web.shutdown, self._control_button.close,
self._preprocess.shutdown, self._pipeline.close, self._recording.finish):
try:
close()
except Exception as exc:
self._log.exception("Shutdown operation failed", exc)
finally:
self._log.close()
super().closeEvent(event)