493 lines
23 KiB
Python
493 lines
23 KiB
Python
# 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, messagebox
|
|
from queue import Queue, Empty
|
|
from typing import Optional, Dict, Any, List
|
|
|
|
# Use absolute imports for robustness and clarity
|
|
from target_simulator.gui.ppi_display import PPIDisplay
|
|
from target_simulator.gui.connection_settings_window import ConnectionSettingsWindow
|
|
from target_simulator.gui.radar_config_window import RadarConfigWindow
|
|
from target_simulator.gui.scenario_controls_frame import ScenarioControlsFrame
|
|
from target_simulator.gui.target_list_frame import TargetListFrame
|
|
|
|
from target_simulator.core.communicator_interface import CommunicatorInterface
|
|
from target_simulator.core.serial_communicator import SerialCommunicator
|
|
from target_simulator.core.tftp_communicator import TFTPCommunicator
|
|
from target_simulator.core.simulation_engine import SimulationEngine
|
|
from target_simulator.core.models import Scenario, Target
|
|
|
|
from target_simulator.utils.logger import get_logger, shutdown_logging_system
|
|
from target_simulator.utils.config_manager import ConfigManager
|
|
|
|
|
|
GUI_QUEUE_POLL_INTERVAL_MS = 100
|
|
|
|
class MainView(tk.Tk):
|
|
"""The main application window."""
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
self.logger = get_logger(__name__)
|
|
self.config_manager = ConfigManager()
|
|
|
|
# --- Load Settings ---
|
|
settings = self.config_manager.get_general_settings()
|
|
self.scan_limit = settings.get('scan_limit', 60)
|
|
self.max_range = settings.get('max_range', 100)
|
|
self.connection_config = self.config_manager.get_connection_settings()
|
|
|
|
# --- Core Logic Handlers ---
|
|
self.target_communicator: Optional[CommunicatorInterface] = None
|
|
self.lru_communicator: Optional[CommunicatorInterface] = None
|
|
self.scenario = Scenario()
|
|
self.current_scenario_name: Optional[str] = None
|
|
|
|
# --- Simulation Engine ---
|
|
self.simulation_engine: Optional[SimulationEngine] = None
|
|
self.gui_update_queue = Queue()
|
|
self.is_simulation_running = tk.BooleanVar(value=False)
|
|
self.time_multiplier = 1.0
|
|
|
|
# --- Window and UI Setup ---
|
|
self.title("Radar Target Simulator")
|
|
self.geometry(settings.get('geometry', '1200x1024'))
|
|
self.minsize(1024, 768)
|
|
|
|
self._create_menubar()
|
|
self._create_main_layout()
|
|
self._create_statusbar()
|
|
|
|
# --- Post-UI Initialization ---
|
|
self._initialize_communicators()
|
|
self._load_scenarios_into_ui()
|
|
last_scenario = settings.get("last_selected_scenario")
|
|
if last_scenario and last_scenario in self.config_manager.get_scenario_names():
|
|
self._on_load_scenario(last_scenario)
|
|
|
|
self._update_window_title()
|
|
self.protocol("WM_DELETE_WINDOW", self._on_closing)
|
|
self.logger.info("MainView initialized successfully.")
|
|
|
|
def _create_main_layout(self):
|
|
v_pane = ttk.PanedWindow(self, orient=tk.VERTICAL)
|
|
v_pane.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
|
|
|
|
self.h_pane = ttk.PanedWindow(v_pane, orient=tk.HORIZONTAL)
|
|
v_pane.add(self.h_pane, weight=4)
|
|
|
|
# --- Right Pane (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)
|
|
|
|
# Add Connect button to the PPI's own control frame for better layout
|
|
if hasattr(self.ppi_widget, 'controls_frame'):
|
|
connect_btn = ttk.Button(self.ppi_widget.controls_frame, text="Connect", command=self._on_connect_button)
|
|
connect_btn.pack(side=tk.RIGHT, padx=10)
|
|
|
|
# --- Left Pane ---
|
|
left_pane_container = ttk.Frame(self.h_pane)
|
|
self.h_pane.add(left_pane_container, weight=1)
|
|
|
|
left_notebook = ttk.Notebook(left_pane_container)
|
|
left_notebook.pack(fill=tk.BOTH, expand=True)
|
|
|
|
# --- TAB 1: SCENARIO CONFIG ---
|
|
scenario_tab = ttk.Frame(left_notebook)
|
|
left_notebook.add(scenario_tab, text="Scenario Config")
|
|
|
|
self.scenario_controls = ScenarioControlsFrame(
|
|
scenario_tab,
|
|
main_view=self,
|
|
load_scenario_command=self._on_load_scenario,
|
|
save_as_command=self._on_save_scenario_as,
|
|
delete_command=self._on_delete_scenario,
|
|
new_scenario_command=self._on_new_scenario
|
|
)
|
|
self.scenario_controls.pack(fill=tk.X, expand=False, padx=5, pady=(5, 5))
|
|
|
|
self.target_list = TargetListFrame(scenario_tab, targets_changed_callback=self._on_targets_changed)
|
|
self.target_list.pack(fill=tk.BOTH, expand=True, padx=5)
|
|
|
|
# --- TAB 2: SIMULATION ---
|
|
simulation_tab = ttk.Frame(left_notebook)
|
|
left_notebook.add(simulation_tab, text="Simulation")
|
|
|
|
sim_scenario_frame = ttk.LabelFrame(simulation_tab, text="Scenario Control")
|
|
sim_scenario_frame.pack(fill=tk.X, padx=5, pady=5, anchor='n')
|
|
|
|
ttk.Label(sim_scenario_frame, text="Scenario:").pack(side=tk.LEFT, padx=(5, 5), pady=5)
|
|
self.sim_scenario_combobox = ttk.Combobox(
|
|
sim_scenario_frame,
|
|
textvariable=self.scenario_controls.current_scenario, # Share the variable
|
|
state="readonly"
|
|
)
|
|
self.sim_scenario_combobox.pack(side=tk.LEFT, expand=True, fill=tk.X, pady=5)
|
|
self.sim_scenario_combobox.bind("<<ComboboxSelected>>", lambda event: self._on_load_scenario(self.sim_scenario_combobox.get()))
|
|
|
|
engine_frame = ttk.LabelFrame(simulation_tab, text="Live Simulation Engine")
|
|
engine_frame.pack(fill=tk.X, padx=5, pady=10, anchor='n')
|
|
|
|
self.start_button = ttk.Button(engine_frame, text="Start Live", command=self._on_start_simulation)
|
|
self.start_button.pack(side=tk.LEFT, padx=5, pady=5)
|
|
|
|
self.stop_button = ttk.Button(engine_frame, text="Stop Live", command=self._on_stop_simulation, state=tk.DISABLED)
|
|
self.stop_button.pack(side=tk.LEFT, padx=5, pady=5)
|
|
|
|
self.reset_button = ttk.Button(engine_frame, text="Reset State", command=self._on_reset_simulation)
|
|
self.reset_button.pack(side=tk.RIGHT, padx=5, pady=5)
|
|
|
|
ttk.Label(engine_frame, text="Speed:").pack(side=tk.LEFT, padx=(10, 2), pady=5)
|
|
self.time_multiplier_var = tk.StringVar(value="1x")
|
|
self.multiplier_combo = ttk.Combobox(engine_frame, textvariable=self.time_multiplier_var, values=["1x", "2x", "4x", "10x"], state="readonly", width=4)
|
|
self.multiplier_combo.pack(side=tk.LEFT, padx=(0, 5), pady=5)
|
|
self.multiplier_combo.bind("<<ComboboxSelected>>", self._on_time_multiplier_changed)
|
|
|
|
# --- TAB 3: LRU SIMULATION ---
|
|
lru_tab = ttk.Frame(left_notebook)
|
|
left_notebook.add(lru_tab, text="LRU Simulation")
|
|
|
|
cooling_frame = ttk.LabelFrame(lru_tab, text="Cooling Unit Status")
|
|
cooling_frame.pack(fill=tk.X, padx=5, pady=5, anchor='n')
|
|
ttk.Label(cooling_frame, text="Status:").pack(side=tk.LEFT, padx=5, pady=5)
|
|
self.cooling_status_var = tk.StringVar(value="OK")
|
|
cooling_combo = ttk.Combobox(cooling_frame, textvariable=self.cooling_status_var, values=["OK", "OVERHEATING", "FAULT"], state="readonly")
|
|
cooling_combo.pack(side=tk.LEFT, expand=True, fill=tk.X, padx=5, pady=5)
|
|
|
|
power_frame = ttk.LabelFrame(lru_tab, text="Power Supply Unit Status")
|
|
power_frame.pack(fill=tk.X, padx=5, pady=5, anchor='n')
|
|
ttk.Label(power_frame, text="Status:").pack(side=tk.LEFT, padx=5, pady=5)
|
|
self.power_status_var = tk.StringVar(value="OK")
|
|
power_combo = ttk.Combobox(power_frame, textvariable=self.power_status_var, values=["OK", "LOW_VOLTAGE", "FAULT"], state="readonly")
|
|
power_combo.pack(side=tk.LEFT, expand=True, fill=tk.X, padx=5, pady=5)
|
|
|
|
lru_action_frame = ttk.Frame(lru_tab)
|
|
lru_action_frame.pack(fill=tk.X, padx=5, pady=10, anchor='n')
|
|
send_lru_button = ttk.Button(lru_action_frame, text="Send LRU Status", command=self._on_send_lru_status)
|
|
send_lru_button.pack(side=tk.RIGHT)
|
|
|
|
# --- Bottom Pane (Logs) ---
|
|
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):
|
|
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):
|
|
status_bar = ttk.Frame(self, relief=tk.SUNKEN)
|
|
status_bar.pack(side=tk.BOTTOM, fill=tk.X)
|
|
ttk.Label(status_bar, text="Target:").pack(side=tk.LEFT, padx=(5, 2))
|
|
self.target_status_canvas = tk.Canvas(status_bar, width=16, height=16, highlightthickness=0)
|
|
self.target_status_canvas.pack(side=tk.LEFT, padx=(0, 10))
|
|
self._draw_status_indicator(self.target_status_canvas, "#e74c3c")
|
|
ttk.Label(status_bar, text="LRU:").pack(side=tk.LEFT, padx=(5, 2))
|
|
self.lru_status_canvas = tk.Canvas(status_bar, width=16, height=16, highlightthickness=0)
|
|
self.lru_status_canvas.pack(side=tk.LEFT, padx=(0, 10))
|
|
self._draw_status_indicator(self.lru_status_canvas, "#e74c3c")
|
|
self.status_var = tk.StringVar(value="Ready")
|
|
ttk.Label(status_bar, textvariable=self.status_var, anchor=tk.W).pack(side=tk.LEFT, fill=tk.X, expand=True, padx=5)
|
|
|
|
def _draw_status_indicator(self, canvas, color):
|
|
canvas.delete("all")
|
|
canvas.create_oval(2, 2, 14, 14, fill=color, outline="black")
|
|
|
|
def _update_window_title(self):
|
|
"""Updates the window title based on the current scenario."""
|
|
base_title = "Radar Target Simulator"
|
|
if self.current_scenario_name:
|
|
self.title(f"{base_title} - {self.current_scenario_name}")
|
|
else:
|
|
self.title(base_title)
|
|
|
|
def _update_communicator_status(self, comm_name: str, is_connected: bool):
|
|
canvas = self.target_status_canvas if comm_name == 'Target' else self.lru_status_canvas
|
|
color = "#2ecc40" if is_connected else "#e74c3c"
|
|
self._draw_status_indicator(canvas, color)
|
|
|
|
def _initialize_communicators(self):
|
|
# Disconnect any existing connections
|
|
if self.target_communicator and self.target_communicator.is_open: self.target_communicator.disconnect()
|
|
if self.lru_communicator and self.lru_communicator.is_open: self.lru_communicator.disconnect()
|
|
|
|
target_cfg = self.connection_config.get("target", {})
|
|
lru_cfg = self.connection_config.get("lru", {})
|
|
|
|
# Initialize Target Communicator
|
|
self.target_communicator, target_connected = self._setup_communicator(target_cfg, "Target")
|
|
self._update_communicator_status("Target", target_connected)
|
|
|
|
# Initialize LRU Communicator
|
|
self.lru_communicator, lru_connected = self._setup_communicator(lru_cfg, "LRU")
|
|
self._update_communicator_status("LRU", lru_connected)
|
|
|
|
def _setup_communicator(self, config: dict, name: str) -> (Optional[CommunicatorInterface], bool):
|
|
comm_type = config.get("type")
|
|
self.logger.info(f"Initializing {name} communicator of type: {comm_type}")
|
|
|
|
communicator = None
|
|
config_data = None
|
|
|
|
if comm_type == "serial":
|
|
communicator = SerialCommunicator()
|
|
config_data = config.get("serial", {})
|
|
elif comm_type == "tftp":
|
|
communicator = TFTPCommunicator()
|
|
config_data = config.get("tftp", {})
|
|
|
|
if communicator and config_data:
|
|
if communicator.connect(config_data):
|
|
return communicator, True
|
|
|
|
self.logger.warning(f"Failed to initialize or connect {name} communicator.")
|
|
return None, False
|
|
|
|
def update_connection_settings(self, new_config: Dict[str, Any]):
|
|
self.logger.info(f"Updating connection settings: {new_config}")
|
|
self.connection_config = new_config
|
|
self.config_manager.save_connection_settings(new_config)
|
|
self._initialize_communicators()
|
|
|
|
def _open_settings(self):
|
|
self.logger.info("Opening connection settings window.")
|
|
ConnectionSettingsWindow(self, self.config_manager, self.connection_config)
|
|
|
|
def _on_connect_button(self):
|
|
self.logger.info("Connection requested by user.")
|
|
self._initialize_communicators()
|
|
|
|
def _on_start_simulation(self):
|
|
if self.is_simulation_running.get():
|
|
self.logger.info("Simulation is already running.")
|
|
return
|
|
if not self.target_communicator or not self.target_communicator.is_open:
|
|
messagebox.showerror("Not Connected", "Target communicator is not connected. Please check settings and connect.")
|
|
return
|
|
if not self.scenario or not self.scenario.get_all_targets():
|
|
messagebox.showinfo("Empty Scenario", "Cannot start simulation with an empty scenario.")
|
|
return
|
|
|
|
self.logger.info("Starting live simulation...")
|
|
self.is_simulation_running.set(True)
|
|
self._update_button_states()
|
|
|
|
self.scenario.reset_simulation()
|
|
self.simulation_engine = SimulationEngine(self.target_communicator, self.gui_update_queue)
|
|
self.simulation_engine.set_time_multiplier(self.time_multiplier)
|
|
self.simulation_engine.load_scenario(self.scenario)
|
|
self.simulation_engine.start()
|
|
|
|
self.after(GUI_QUEUE_POLL_INTERVAL_MS, self._process_gui_queue)
|
|
|
|
def _on_stop_simulation(self):
|
|
if not self.is_simulation_running.get() or not self.simulation_engine:
|
|
return
|
|
|
|
self.logger.info("Stopping live simulation...")
|
|
self.simulation_engine.stop()
|
|
self.simulation_engine = None
|
|
self.is_simulation_running.set(False)
|
|
self._update_button_states()
|
|
|
|
def _on_reset_simulation(self):
|
|
self.logger.info("Resetting scenario to initial state.")
|
|
if self.is_simulation_running.get():
|
|
self._on_stop_simulation()
|
|
|
|
self.scenario.reset_simulation()
|
|
self._update_all_views()
|
|
|
|
def _process_gui_queue(self):
|
|
try:
|
|
while not self.gui_update_queue.empty():
|
|
updated_targets: List[Target] = self.gui_update_queue.get_nowait()
|
|
self._update_all_views(updated_targets)
|
|
finally:
|
|
if self.is_simulation_running.get():
|
|
self.after(GUI_QUEUE_POLL_INTERVAL_MS, self._process_gui_queue)
|
|
|
|
def _update_button_states(self):
|
|
is_running = self.is_simulation_running.get()
|
|
state = tk.DISABLED if is_running else tk.NORMAL
|
|
|
|
self.start_button.config(state=tk.DISABLED if is_running else tk.NORMAL)
|
|
self.stop_button.config(state=tk.NORMAL if is_running else tk.DISABLED)
|
|
self.multiplier_combo.config(state="readonly" if not is_running else tk.DISABLED)
|
|
|
|
self.scenario_controls.new_button.config(state=state)
|
|
self.scenario_controls.save_button.config(state=state)
|
|
self.scenario_controls.save_as_button.config(state=state)
|
|
self.scenario_controls.delete_button.config(state=state)
|
|
self.scenario_controls.scenario_combobox.config(state="readonly" if not is_running else tk.DISABLED)
|
|
|
|
self.target_list.add_button.config(state=state)
|
|
self.target_list.remove_button.config(state=state)
|
|
self.target_list.edit_button.config(state=state)
|
|
self.target_list.tree.config(selectmode="browse" if not is_running else "none")
|
|
|
|
def _on_time_multiplier_changed(self, event=None):
|
|
"""Handles changes to the time multiplier selection."""
|
|
try:
|
|
multiplier_str = self.time_multiplier_var.get().replace('x', '')
|
|
self.time_multiplier = float(multiplier_str)
|
|
if self.simulation_engine and self.simulation_engine.is_running():
|
|
self.simulation_engine.set_time_multiplier(self.time_multiplier)
|
|
except ValueError:
|
|
self.logger.error(f"Invalid time multiplier value: {self.time_multiplier_var.get()}")
|
|
self.time_multiplier = 1.0
|
|
|
|
def _on_targets_changed(self, targets: List[Target]):
|
|
"""Callback executed when the target list is modified by the user."""
|
|
# 1. Update the internal scenario object
|
|
self.scenario.targets = {t.target_id: t for t in targets}
|
|
|
|
# 2. Update the PPI display with the latest target list
|
|
self.ppi_widget.update_targets(targets)
|
|
|
|
# 3. Automatically save the changes to the current scenario file
|
|
if self.current_scenario_name:
|
|
self.logger.info(f"Targets changed for scenario '{self.current_scenario_name}'. Saving changes.")
|
|
self.config_manager.save_scenario(self.current_scenario_name, self.scenario.to_dict())
|
|
else:
|
|
self.logger.warning("Targets changed, but no scenario is currently loaded. Changes are not saved.")
|
|
|
|
def _update_all_views(self, targets_to_display: Optional[List[Target]] = None):
|
|
self._update_window_title()
|
|
|
|
if targets_to_display is None:
|
|
targets_to_display = self.scenario.get_all_targets()
|
|
|
|
self.target_list.update_target_list(targets_to_display)
|
|
self.ppi_widget.update_targets(targets_to_display)
|
|
|
|
def _load_scenarios_into_ui(self):
|
|
scenario_names = self.config_manager.get_scenario_names()
|
|
self.scenario_controls.update_scenario_list(scenario_names, self.current_scenario_name)
|
|
self.sim_scenario_combobox['values'] = scenario_names
|
|
|
|
def _on_load_scenario(self, scenario_name: str):
|
|
if self.is_simulation_running.get(): self._on_stop_simulation()
|
|
self.logger.info(f"Loading scenario: {scenario_name}")
|
|
scenario_data = self.config_manager.get_scenario(scenario_name)
|
|
if scenario_data:
|
|
try:
|
|
self.scenario = Scenario.from_dict(scenario_data)
|
|
self.current_scenario_name = scenario_name
|
|
self._update_all_views()
|
|
self.sim_scenario_combobox.set(scenario_name)
|
|
except Exception as e:
|
|
self.logger.error(f"Failed to parse scenario data for '{scenario_name}': {e}", exc_info=True)
|
|
messagebox.showerror("Load Error", f"Could not load scenario '{scenario_name}'.\n{e}")
|
|
else:
|
|
self.logger.warning(f"Attempted to load a non-existent scenario: {scenario_name}")
|
|
|
|
def _on_save_scenario(self, scenario_name: str):
|
|
self.logger.info(f"Saving scenario: {scenario_name}")
|
|
self.scenario.targets = {t.target_id: t for t in self.target_list.get_targets()}
|
|
self.config_manager.save_scenario(scenario_name, self.scenario.to_dict())
|
|
self.current_scenario_name = scenario_name
|
|
self._load_scenarios_into_ui()
|
|
self._update_window_title()
|
|
messagebox.showinfo("Success", f"Scenario '{scenario_name}' saved successfully.", parent=self)
|
|
|
|
def _on_save_scenario_as(self, scenario_name: str):
|
|
self.scenario.targets = {t.target_id: t for t in self.target_list.get_targets()}
|
|
self._on_save_scenario(scenario_name)
|
|
|
|
def _on_new_scenario(self, scenario_name: str):
|
|
scenario_names = self.config_manager.get_scenario_names()
|
|
if scenario_name in scenario_names:
|
|
messagebox.showinfo("Duplicate Scenario", f"Scenario '{scenario_name}' already exists. Loading it instead.", parent=self)
|
|
self._on_load_scenario(scenario_name)
|
|
return
|
|
|
|
self.logger.info(f"Creating new scenario: {scenario_name}")
|
|
self.scenario = Scenario(name=scenario_name)
|
|
self.current_scenario_name = scenario_name
|
|
|
|
# Save the new empty scenario immediately so it's persisted
|
|
self.config_manager.save_scenario(scenario_name, self.scenario.to_dict())
|
|
|
|
# Update all UI elements
|
|
self._update_all_views() # Clears targets, updates title
|
|
self._load_scenarios_into_ui() # Reloads scenario lists in comboboxes
|
|
|
|
# After reloading, explicitly set the current selection to the new scenario
|
|
self.scenario_controls.current_scenario.set(scenario_name)
|
|
self.sim_scenario_combobox.set(scenario_name)
|
|
|
|
def _on_delete_scenario(self, scenario_name: str):
|
|
self.logger.info(f"Deleting scenario: {scenario_name}")
|
|
self.config_manager.delete_scenario(scenario_name)
|
|
if self.current_scenario_name == scenario_name:
|
|
self.current_scenario_name = None
|
|
self.scenario = Scenario()
|
|
self._update_all_views()
|
|
self._load_scenarios_into_ui()
|
|
|
|
def _on_send_lru_status(self):
|
|
# Implementation from your code
|
|
pass
|
|
|
|
def _on_reset_targets(self):
|
|
# Implementation from your code
|
|
pass
|
|
|
|
def _open_radar_config(self):
|
|
self.logger.info("Opening radar config window.")
|
|
dialog = RadarConfigWindow(self, current_scan_limit=self.scan_limit, current_max_range=self.max_range)
|
|
|
|
# wait_window è già gestito all'interno di RadarConfigWindow,
|
|
# quindi il codice prosegue solo dopo la sua chiusura.
|
|
|
|
if dialog.scan_limit is not None and dialog.max_range is not None:
|
|
# Check if values have actually changed to avoid unnecessary redraws
|
|
if self.scan_limit != dialog.scan_limit or self.max_range != dialog.max_range:
|
|
self.logger.info("Radar configuration changed. Applying new settings.")
|
|
self.scan_limit = dialog.scan_limit
|
|
self.max_range = dialog.max_range
|
|
|
|
# --- LOGICA MODIFICATA ---
|
|
# Non distruggere il widget, ma riconfiguralo.
|
|
self.ppi_widget.reconfigure_radar(
|
|
max_range_nm=self.max_range,
|
|
scan_limit_deg=self.scan_limit
|
|
)
|
|
|
|
self.logger.info(f"Scan limit set to: ±{self.scan_limit} degrees")
|
|
self.logger.info(f"Max range set to: {self.max_range} NM")
|
|
|
|
# Non è necessario chiamare _update_all_views() perché
|
|
# reconfigure_radar forza già un ridisegno completo.
|
|
else:
|
|
self.logger.info("Radar configuration confirmed, but no changes were made.")
|
|
|
|
def _on_closing(self):
|
|
self.logger.info("Application shutting down.")
|
|
if self.is_simulation_running.get():
|
|
self._on_stop_simulation()
|
|
|
|
settings_to_save = {
|
|
'scan_limit': self.scan_limit,
|
|
'max_range': self.max_range,
|
|
'geometry': self.winfo_geometry(),
|
|
'last_selected_scenario': self.current_scenario_name
|
|
}
|
|
self.config_manager.save_general_settings(settings_to_save)
|
|
self.config_manager.save_connection_settings(self.connection_config)
|
|
|
|
if self.target_communicator and self.target_communicator.is_open: self.target_communicator.disconnect()
|
|
if self.lru_communicator and self.lru_communicator.is_open: self.lru_communicator.disconnect()
|
|
|
|
shutdown_logging_system()
|
|
self.destroy() |