# FlightMonitor/gui/panels/function_notebook_panel.py """ Panel managing the area profiles, BBox input, and the function notebooks. """ import tkinter as tk from tkinter import ttk, messagebox from typing import Dict, Any, Optional from ...utils.logger import get_logger from ...data import config as app_config from ...map.map_utils import _is_valid_bbox_dict from ...data.area_profile_manager import DEFAULT_PROFILE_NAME module_logger = get_logger(__name__) class FunctionNotebookPanel: """ Manages the combined Area Profiles, BBox, and Function Notebooks panel. """ def __init__(self, parent_frame: ttk.Frame, controller: Any): self.parent_frame = parent_frame self.controller = controller # --- Tkinter Variables --- self.lat_min_var = tk.StringVar() self.lon_min_var = tk.StringVar() self.lat_max_var = tk.StringVar() self.lon_max_var = tk.StringVar() self.selected_profile_var = tk.StringVar() self._build_ui() self.update_profile_list() self.set_selected_profile(DEFAULT_PROFILE_NAME) module_logger.debug("FunctionNotebookPanel (unified) initialized.") def _build_ui(self): # --- Main Container for Area Management --- area_frame = ttk.LabelFrame(self.parent_frame, text="Area Profiles & BBox", padding=10) area_frame.pack(side=tk.TOP, fill=tk.X, padx=2, pady=(0, 5)) # Configure grid for the area frame area_frame.columnconfigure(1, weight=1) # --- Row 0: Profile Selection and Buttons --- profile_controls_frame = ttk.Frame(area_frame) profile_controls_frame.grid(row=0, column=0, columnspan=4, sticky="ew", pady=(0, 10)) profile_controls_frame.columnconfigure(0, weight=1) # Let combobox expand self.profile_combobox = ttk.Combobox( profile_controls_frame, textvariable=self.selected_profile_var, state="readonly" ) self.profile_combobox.grid(row=0, column=0, sticky="ew", padx=(0, 5)) self.profile_combobox.bind("<>", self._on_profile_selected) new_button = ttk.Button(profile_controls_frame, text="New", command=self._on_new_profile, width=5) new_button.grid(row=0, column=1, padx=(0, 2)) save_button = ttk.Button(profile_controls_frame, text="Save", command=self._on_save_profile, width=5) save_button.grid(row=0, column=2, padx=(0, 2)) delete_button = ttk.Button(profile_controls_frame, text="Delete", command=self._on_delete_profile, width=7) delete_button.grid(row=0, column=3) # --- Row 1 & 2: Bounding Box Entries --- ttk.Label(area_frame, text="Lat Min:").grid(row=1, column=0, padx=(0, 2), pady=2, sticky=tk.W) self.lat_min_entry = ttk.Entry(area_frame, textvariable=self.lat_min_var) self.lat_min_entry.grid(row=1, column=1, padx=(0, 5), pady=2, sticky=tk.EW) ttk.Label(area_frame, text="Lon Min:").grid(row=1, column=2, padx=(5, 2), pady=2, sticky=tk.W) self.lon_min_entry = ttk.Entry(area_frame, textvariable=self.lon_min_var) self.lon_min_entry.grid(row=1, column=3, padx=(0, 0), pady=2, sticky=tk.EW) ttk.Label(area_frame, text="Lat Max:").grid(row=2, column=0, padx=(0, 2), pady=2, sticky=tk.W) self.lat_max_entry = ttk.Entry(area_frame, textvariable=self.lat_max_var) self.lat_max_entry.grid(row=2, column=1, padx=(0, 5), pady=2, sticky=tk.EW) ttk.Label(area_frame, text="Lon Max:").grid(row=2, column=2, padx=(5, 2), pady=2, sticky=tk.W) self.lon_max_entry = ttk.Entry(area_frame, textvariable=self.lon_max_var) self.lon_max_entry.grid(row=2, column=3, padx=(0, 0), pady=2, sticky=tk.EW) # --- Function Notebook --- self.function_notebook = ttk.Notebook(self.parent_frame) self.function_notebook.pack(side=tk.TOP, fill=tk.BOTH, expand=True, padx=2, pady=2) # --- Live Tab --- live_tab_frame = ttk.Frame(self.function_notebook, padding=10) self.function_notebook.add(live_tab_frame, text="Live Monitor") self.start_live_button = ttk.Button(live_tab_frame, text="Start Live Monitoring", command=self._on_start_live_monitoring) self.start_live_button.pack(side=tk.LEFT, padx=5, pady=5) self.stop_live_button = ttk.Button(live_tab_frame, text="Stop Live Monitoring", command=self._on_stop_live_monitoring, state=tk.DISABLED) self.stop_live_button.pack(side=tk.LEFT, padx=5, pady=5) # --- Historical Download Tab --- download_tab_frame = ttk.Frame(self.function_notebook, padding=10) self.function_notebook.add(download_tab_frame, text="Historical Download") ttk.Label(download_tab_frame, text="Historical download controls...").pack() # --- Playback Tab --- playback_tab_frame = ttk.Frame(self.function_notebook, padding=10) self.function_notebook.add(playback_tab_frame, text="Playback") ttk.Label(playback_tab_frame, text="Playback controls...").pack() self.function_notebook.bind("<>", self._on_tab_change) # --- Event Handlers --- def _on_start_live_monitoring(self): if self.controller: self.controller.start_live_monitoring() def _on_stop_live_monitoring(self): if self.controller: self.controller.stop_live_monitoring() def _on_profile_selected(self, event=None): selected_name = self.selected_profile_var.get() if selected_name and self.controller: self.controller.load_area_profile(selected_name) def _on_new_profile(self): self.selected_profile_var.set("") self.update_bbox_gui_fields({}) def _on_save_profile(self): if self.controller: self.controller.save_current_area_as_profile() def _on_delete_profile(self): profile_to_delete = self.selected_profile_var.get() if not profile_to_delete or profile_to_delete == DEFAULT_PROFILE_NAME: messagebox.showerror("Delete Error", "Cannot delete the default profile or no profile selected.", parent=self.parent_frame) return if messagebox.askyesno("Confirm Delete", f"Delete profile '{profile_to_delete}'?", parent=self.parent_frame): if self.controller: self.controller.delete_area_profile(profile_to_delete) def _on_tab_change(self, event=None): if not self.function_notebook: return tab_text = self.function_notebook.tab(self.function_notebook.index("current"), "text") if self.controller and hasattr(self.controller, "on_function_tab_changed"): self.controller.on_function_tab_changed(tab_text) # --- Public Methods --- def update_profile_list(self): if self.controller: profile_names = self.controller.get_profile_names() self.profile_combobox["values"] = profile_names self.set_selected_profile(self.selected_profile_var.get() or DEFAULT_PROFILE_NAME) def set_selected_profile(self, profile_name: str): if profile_name in self.profile_combobox["values"]: self.selected_profile_var.set(profile_name) elif self.profile_combobox["values"]: self.selected_profile_var.set(self.profile_combobox["values"][0]) def get_bounding_box_input(self): # ... (metodo rimane invariato) try: bbox_candidate = { "lat_min": float(self.lat_min_var.get()), "lon_min": float(self.lon_min_var.get()), "lat_max": float(self.lat_max_var.get()), "lon_max": float(self.lon_max_var.get()), } return bbox_candidate if _is_valid_bbox_dict(bbox_candidate) else None except (ValueError, TypeError): return None def update_bbox_gui_fields(self, bbox_dict: Dict[str, float]): # ... (metodo rimane invariato) if bbox_dict and _is_valid_bbox_dict(bbox_dict): decimals = getattr(app_config, "COORDINATE_DECIMAL_PLACES", 5) self.lat_min_var.set(f"{bbox_dict['lat_min']:.{decimals}f}") self.lon_min_var.set(f"{bbox_dict['lon_min']:.{decimals}f}") self.lat_max_var.set(f"{bbox_dict['lat_max']:.{decimals}f}") self.lon_max_var.set(f"{bbox_dict['lon_max']:.{decimals}f}") else: self.lat_min_var.set("") self.lon_min_var.set("") self.lat_max_var.set("") self.lon_max_var.set("") def set_monitoring_button_states(self, is_monitoring_active: bool): # ... (metodo rimane invariato) if self.start_live_button and self.start_live_button.winfo_exists(): self.start_live_button.config(state=tk.DISABLED if is_monitoring_active else tk.NORMAL) if self.stop_live_button and self.stop_live_button.winfo_exists(): self.stop_live_button.config(state=tk.NORMAL if is_monitoring_active else tk.DISABLED) def set_controls_state(self, enabled: bool): state = tk.NORMAL if enabled else tk.DISABLED readonly_state = "readonly" if enabled else tk.DISABLED self.profile_combobox.config(state=readonly_state) for entry in [self.lat_min_entry, self.lon_min_entry, self.lat_max_entry, self.lon_max_entry]: if entry.winfo_exists(): entry.config(state=state) # Abilita/Disabilita i bottoni dei profili for child in self.profile_combobox.master.winfo_children(): if isinstance(child, ttk.Button): child.config(state=state)