# target_simulator/gui/main_view.py """ Main view of the application, containing the primary window and widgets. """ import tkinter as tk from tkinter import ttk, scrolledtext from .ppi_display import PPIDisplay from .settings_window import SettingsWindow from .radar_config_window import RadarConfigWindow from .scenario_controls_frame import ScenarioControlsFrame from .target_list_frame import TargetListFrame from .add_target_window import AddTargetWindow from core.serial_communicator import SerialCommunicator from core.models import Scenario, Target from utils.logger import get_logger, shutdown_logging_system class MainView(tk.Tk): """The main application window.""" def __init__(self): super().__init__() self.logger = get_logger(__name__) self.communicator = SerialCommunicator() self.scenario = Scenario() self.connection_port: str | None = None self.scan_limit = 60 self.max_range = 100 self.title("Radar Target Simulator") self.geometry("1200x900") self.minsize(900, 700) self._create_menubar() self._create_main_layout() self._create_statusbar() self.logger.info("MainView initialized successfully.") def _create_main_layout(self): """Creates the main paned layout based on the new design.""" # Main vertical pane: Top (Content) / Bottom (Logs) v_pane = ttk.PanedWindow(self, orient=tk.VERTICAL) v_pane.pack(fill=tk.BOTH, expand=True, padx=5, pady=5) # Top horizontal pane: Left (Controls) / Right (PPI) self.h_pane = ttk.PanedWindow(v_pane, orient=tk.HORIZONTAL) v_pane.add(self.h_pane, weight=4) # --- Left Side Frame (for controls) --- left_frame = ttk.Frame(self.h_pane) self.h_pane.add(left_frame, weight=1) # --- Right Side Frame (for PPI) --- self.ppi_widget = PPIDisplay(self.h_pane, max_range_nm=self.max_range, scan_limit_deg=self.scan_limit) self.h_pane.add(self.ppi_widget, weight=2) # --- Populate Left Frame --- self.scenario_controls = ScenarioControlsFrame(left_frame) self.scenario_controls.pack(fill=tk.X, expand=False, padx=5, pady=(0, 5)) self.target_list = TargetListFrame(left_frame) self.target_list.pack(fill=tk.BOTH, expand=True, padx=5) # Connect button command self.target_list.add_button.config(command=self._on_add_target) # --- Logger (Bottom) --- log_frame_container = ttk.LabelFrame(v_pane, text="Logs") v_pane.add(log_frame_container, weight=1) self.log_text_widget = scrolledtext.ScrolledText( log_frame_container, state=tk.DISABLED, wrap=tk.WORD, font=("Consolas", 9) ) self.log_text_widget.pack(fill=tk.BOTH, expand=True, padx=5, pady=5) def _create_menubar(self): # (This method remains unchanged) menubar = tk.Menu(self) self.config(menu=menubar) settings_menu = tk.Menu(menubar, tearoff=0) menubar.add_cascade(label="Settings", menu=settings_menu) settings_menu.add_command(label="Connection...", command=self._open_settings) settings_menu.add_command(label="Radar Config...", command=self._open_radar_config) def _create_statusbar(self): # (This method remains unchanged) self.status_var = tk.StringVar(value="Status: Disconnected") status_bar = ttk.Label(self, textvariable=self.status_var, anchor=tk.W, relief=tk.SUNKEN) status_bar.pack(side=tk.BOTTOM, fill=tk.X) def _update_all_views(self): """Refreshes all GUI components based on the current scenario.""" all_targets = self.scenario.get_all_targets() self.target_list.update_target_list(all_targets) self.ppi_widget.update_targets(all_targets) self.logger.info(f"Views updated. Scenario now has {len(all_targets)} target(s).") def _on_add_target(self): """Handles the 'Add Target' button click.""" self.logger.debug("'Add Target' button clicked. Opening dialog.") existing_ids = list(self.scenario.targets.keys()) # This will block until the dialog is closed dialog = AddTargetWindow(self, existing_ids) new_target = dialog.new_target if new_target: self.logger.info(f"New target created with ID {new_target.target_id}.") self.scenario.add_target(new_target) self._update_all_views() else: self.logger.debug("Add target dialog was cancelled.") def _open_settings(self): self.logger.info("Opening settings window.") SettingsWindow(self, self.communicator) def _open_radar_config(self): self.logger.info("Opening radar config window.") dialog = RadarConfigWindow(self) if dialog.scan_limit is not None and dialog.max_range is not None: self.scan_limit = dialog.scan_limit self.max_range = dialog.max_range self.logger.info(f"Scan limit set to: ±{self.scan_limit} degrees") self.logger.info(f"Max range set to: {self.max_range} NM") self.ppi_widget.destroy() self.ppi_widget = PPIDisplay(self.h_pane, max_range_nm=self.max_range, scan_limit_deg=self.scan_limit) self.h_pane.add(self.ppi_widget, weight=2) self._update_all_views() def update_connection_settings(self, port: str): self.connection_port = port self.logger.info(f"Connection port set to: {self.connection_port}") self.status_var.set(f"Status: Configured for {port}. Ready to connect.") def _on_closing(self): self.logger.info("Application shutting down.") if self.communicator.is_open: self.communicator.disconnect() shutdown_logging_system() self.destroy()