132 lines
7.4 KiB
Python
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)
|