276 lines
13 KiB
Python
276 lines
13 KiB
Python
# 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, font as tkFont
|
|
from typing import Dict, Any, Optional
|
|
import os
|
|
|
|
try:
|
|
from PIL import Image, ImageTk
|
|
PIL_AVAILABLE = True
|
|
except ImportError:
|
|
PIL_AVAILABLE = False
|
|
|
|
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__)
|
|
|
|
ICON_PATH = os.path.join(os.path.dirname(__file__), '..', '..', 'assets', 'icons')
|
|
PLAY_ICON_PATH = os.path.join(ICON_PATH, 'play_icon.png')
|
|
STOP_ICON_PATH = os.path.join(ICON_PATH, 'stop_icon.png')
|
|
|
|
# Definiamo i colori per i bottoni
|
|
COLOR_START_BG = "#28a745" # Verde
|
|
COLOR_START_ACTIVE = "#218838"
|
|
COLOR_STOP_BG = "#dc3545" # Rosso
|
|
COLOR_STOP_ACTIVE = "#c82333"
|
|
COLOR_DISABLED_BG = "#e0e0e0" # Grigio chiaro
|
|
COLOR_DISABLED_FG = "#a0a0a0" # Grigio scuro per il testo
|
|
COLOR_TEXT = "black"
|
|
|
|
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
|
|
|
|
self.play_icon: Optional[tk.PhotoImage] = None
|
|
self.stop_icon: Optional[tk.PhotoImage] = None
|
|
|
|
# ... (il resto del costruttore rimane uguale) ...
|
|
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()
|
|
|
|
# Seleziona il valore nella combobox, ma non carica ancora i dati
|
|
self.set_selected_profile(DEFAULT_PROFILE_NAME)
|
|
|
|
# RIMOSSA: La chiamata a _on_profile_selected() viene ora gestita da MainWindow
|
|
|
|
module_logger.debug("FunctionNotebookPanel (unified) initialized.")
|
|
|
|
def _load_icons(self):
|
|
if not PIL_AVAILABLE: return
|
|
try:
|
|
icon_size = (32, 32) # Icone più grandi
|
|
play_img = Image.open(PLAY_ICON_PATH).resize(icon_size, Image.Resampling.LANCZOS)
|
|
self.play_icon = ImageTk.PhotoImage(play_img)
|
|
|
|
stop_img = Image.open(STOP_ICON_PATH).resize(icon_size, Image.Resampling.LANCZOS)
|
|
self.stop_icon = ImageTk.PhotoImage(stop_img)
|
|
except Exception as e:
|
|
module_logger.warning(f"Could not load icons: {e}. Buttons will be text-only.")
|
|
|
|
def _build_ui(self):
|
|
self._load_icons()
|
|
button_font = tkFont.Font(family="Helvetica", size=10, weight="bold")
|
|
|
|
# --- 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)
|
|
# MODIFICA: Assegna lo stesso peso anche alla quarta colonna
|
|
area_frame.columnconfigure(3, 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)
|
|
|
|
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("<<ComboboxSelected>>", 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)
|
|
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=(0, 10, 0, 0)) # Aggiunto padding
|
|
self.function_notebook.add(live_tab_frame, text="Live Monitor")
|
|
|
|
# Usiamo pack per un controllo migliore sull'allineamento verticale
|
|
button_container = ttk.Frame(live_tab_frame)
|
|
button_container.pack(side=tk.TOP, fill=tk.X, expand=False, anchor='n')
|
|
|
|
# Frame interni per centrare i bottoni orizzontalmente
|
|
left_spacer = ttk.Frame(button_container)
|
|
left_spacer.pack(side=tk.LEFT, expand=True)
|
|
|
|
self.start_live_button = tk.Button(
|
|
button_container,
|
|
text="Start Live",
|
|
image=self.play_icon,
|
|
compound=tk.TOP,
|
|
command=self._on_start_live_monitoring,
|
|
font=button_font,
|
|
bg=COLOR_START_BG,
|
|
fg=COLOR_TEXT,
|
|
activebackground=COLOR_START_ACTIVE,
|
|
activeforeground=COLOR_TEXT,
|
|
relief=tk.RAISED,
|
|
borderwidth=2,
|
|
padx=10,
|
|
pady=5
|
|
)
|
|
self.start_live_button.pack(side=tk.LEFT, padx=15, pady=5)
|
|
|
|
self.stop_live_button = tk.Button(
|
|
button_container,
|
|
text="Stop Live",
|
|
image=self.stop_icon,
|
|
compound=tk.TOP,
|
|
command=self._on_stop_live_monitoring,
|
|
font=button_font,
|
|
bg=COLOR_DISABLED_BG,
|
|
fg=COLOR_DISABLED_FG,
|
|
activebackground=COLOR_DISABLED_BG,
|
|
activeforeground=COLOR_DISABLED_FG,
|
|
state=tk.DISABLED,
|
|
relief=tk.RAISED,
|
|
borderwidth=2,
|
|
padx=10,
|
|
pady=5
|
|
)
|
|
self.stop_live_button.pack(side=tk.LEFT, padx=15, pady=5)
|
|
|
|
right_spacer = ttk.Frame(button_container)
|
|
right_spacer.pack(side=tk.LEFT, expand=True)
|
|
|
|
# --- Altri Tab (invariati) ---
|
|
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_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("<<NotebookTabChanged>>", self._on_tab_change)
|
|
|
|
# --- Event Handlers (invariati) ---
|
|
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 self.function_notebook:
|
|
tab_text = self.function_notebook.tab(self.function_notebook.index("current"), "text")
|
|
if self.controller: self.controller.on_function_tab_changed(tab_text)
|
|
|
|
# --- Public Methods (modificata set_monitoring_button_states) ---
|
|
def set_monitoring_button_states(self, is_monitoring_active: bool):
|
|
"""Sets the state and style of the Start/Stop buttons."""
|
|
if is_monitoring_active:
|
|
# Monitoring ATTIVO
|
|
if self.start_live_button:
|
|
self.start_live_button.config(
|
|
state=tk.DISABLED, bg=COLOR_DISABLED_BG, fg=COLOR_DISABLED_FG,
|
|
activebackground=COLOR_DISABLED_BG, activeforeground=COLOR_DISABLED_FG
|
|
)
|
|
if self.stop_live_button:
|
|
self.stop_live_button.config(
|
|
state=tk.NORMAL, bg=COLOR_STOP_BG, fg=COLOR_TEXT,
|
|
activebackground=COLOR_STOP_ACTIVE, activeforeground=COLOR_TEXT
|
|
)
|
|
else:
|
|
# Monitoring FERMO
|
|
if self.start_live_button:
|
|
self.start_live_button.config(
|
|
state=tk.NORMAL, bg=COLOR_START_BG, fg=COLOR_TEXT,
|
|
activebackground=COLOR_START_ACTIVE, activeforeground=COLOR_TEXT
|
|
)
|
|
if self.stop_live_button:
|
|
self.stop_live_button.config(
|
|
state=tk.DISABLED, bg=COLOR_DISABLED_BG, fg=COLOR_DISABLED_FG,
|
|
activebackground=COLOR_DISABLED_BG, activeforeground=COLOR_DISABLED_FG
|
|
)
|
|
|
|
# --- Altri metodi pubblici (invariati) ---
|
|
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 self.profile_combobox["values"] and 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):
|
|
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]):
|
|
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_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)
|
|
for child in self.profile_combobox.master.winfo_children():
|
|
if isinstance(child, ttk.Button):
|
|
child.config(state=state) |