"""Toplevel window for adding or editing a target. This module defines the `AddTargetWindow` class, which provides a dialog for users to input the initial parameters of a new target. """ import tkinter as tk from tkinter import ttk, messagebox from target_simulator.core.models import Target, MIN_TARGET_ID, MAX_TARGET_ID, Waypoint class AddTargetWindow(tk.Toplevel): """A modal dialog window for adding a new target. This window prompts the user for the target's initial kinematic parameters, such as position, velocity, and altitude. """ def __init__(self, master, existing_ids: list[int]): """ Initialize the AddTargetWindow. Args: master (tk.Widget): The parent widget. existing_ids (list[int]): A list of target IDs that are already in use. """ super().__init__(master) self.master_view = master self.new_target: Target | None = None self.existing_ids = existing_ids self.title("Add New Target") self.transient(master) self.grab_set() self.resizable(False, False) self._create_widgets() self.protocol("WM_DELETE_WINDOW", self._on_cancel) # wait_window will be called by the caller after pre-populating data def _create_widgets(self): """Create and layout all the widgets for the dialog.""" main_frame = ttk.Frame(self, padding="10") main_frame.pack(fill=tk.BOTH, expand=True) # --- Target ID --- id_frame = ttk.LabelFrame(main_frame, text="Target Identifier") id_frame.pack(fill=tk.X, expand=True, pady=5) ttk.Label(id_frame, text="Target ID:").pack(side=tk.LEFT, padx=5, pady=5) # Propose a valid ID proposed_id = next( ( i for i in range(MIN_TARGET_ID, MAX_TARGET_ID + 1) if i not in self.existing_ids ), None, ) self.id_var = tk.IntVar( value=proposed_id if proposed_id is not None else MIN_TARGET_ID ) self.id_spinbox = ttk.Spinbox( id_frame, from_=MIN_TARGET_ID, to=MAX_TARGET_ID, textvariable=self.id_var, width=10, ) self.id_spinbox.pack(side=tk.LEFT, padx=5, pady=5) # --- Kinematics Frame --- kinematics_frame = ttk.LabelFrame(main_frame, text="Kinematics (Spherical)") kinematics_frame.pack(fill=tk.X, expand=True, pady=5) self.range_var = tk.DoubleVar(value=20.0) self.az_var = tk.DoubleVar(value=45.0) self.vel_knots_var = tk.DoubleVar(value=500.0) self.hdg_var = tk.DoubleVar(value=270.0) self.alt_var = tk.DoubleVar(value=10000.0) # Using a grid for alignment ttk.Label(kinematics_frame, text="Range (NM):").grid( row=0, column=0, sticky=tk.W, padx=5, pady=2 ) ttk.Spinbox( kinematics_frame, from_=0, to=1000, textvariable=self.range_var ).grid(row=0, column=1, sticky=tk.EW, padx=5, pady=2) ttk.Label(kinematics_frame, text="Azimuth (°):").grid( row=1, column=0, sticky=tk.W, padx=5, pady=2 ) ttk.Spinbox( kinematics_frame, from_=-180, to=180, textvariable=self.az_var ).grid(row=1, column=1, sticky=tk.EW, padx=5, pady=2) ttk.Label(kinematics_frame, text="Velocity (knots):").grid( row=2, column=0, sticky=tk.W, padx=5, pady=2 ) ttk.Spinbox( kinematics_frame, from_=0, to=2000, textvariable=self.vel_knots_var ).grid(row=2, column=1, sticky=tk.EW, padx=5, pady=2) ttk.Label(kinematics_frame, text="Heading (°):").grid( row=3, column=0, sticky=tk.W, padx=5, pady=2 ) ttk.Spinbox(kinematics_frame, from_=0, to=360, textvariable=self.hdg_var).grid( row=3, column=1, sticky=tk.EW, padx=5, pady=2 ) ttk.Label(kinematics_frame, text="Altitude (ft):").grid( row=4, column=0, sticky=tk.W, padx=5, pady=2 ) ttk.Spinbox( kinematics_frame, from_=-1000, to=80000, textvariable=self.alt_var ).grid(row=4, column=1, sticky=tk.EW, padx=5, pady=2) # --- Buttons --- button_frame = ttk.Frame(main_frame) button_frame.pack(pady=10) ttk.Button(button_frame, text="OK", command=self._on_ok).pack( side=tk.LEFT, padx=5 ) ttk.Button(button_frame, text="Cancel", command=self._on_cancel).pack( side=tk.LEFT, padx=5 ) def _on_cancel(self): """Handle the Cancel button click or window close event.""" self.new_target = None self.destroy() def _on_ok(self): """ Validate user input, create a new Target object, and close the window. If validation fails, an error message is displayed. """ target_id = self.id_var.get() if target_id in self.existing_ids: messagebox.showerror( "Invalid ID", f"Target ID {target_id} is already in use.", parent=self ) return try: # Conversion from knots to fps knots_to_fps = 1.68781 velocity_fps = self.vel_knots_var.get() * knots_to_fps default_trajectory = [ Waypoint( duration_s=3600, target_velocity_fps=velocity_fps, target_heading_deg=self.hdg_var.get(), ) ] self.new_target = Target( target_id=target_id, initial_range_nm=self.range_var.get(), initial_azimuth_deg=self.az_var.get(), initial_altitude_ft=self.alt_var.get(), trajectory=default_trajectory, ) self.destroy() except ValueError as e: messagebox.showerror("Validation Error", str(e), parent=self)