add new icon, refactor live tab

This commit is contained in:
VALLONGOL 2025-06-10 14:18:30 +02:00
parent 6fa4928b0c
commit 5ff1686112
4 changed files with 142 additions and 57 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

View File

@ -18,7 +18,7 @@ from .panels.map_tools_panel import MapToolsPanel
from .panels.map_info_panel import MapInfoPanel
from .panels.selected_flight_details_panel import SelectedFlightDetailsPanel
from .panels.views_notebook_panel import ViewsNotebookPanel
from .panels.function_notebook_panel import FunctionNotebookPanel
from .panels.function_notebook_panel import FunctionNotebookPanel, DEFAULT_PROFILE_NAME
try:
from ..map.map_canvas_manager import MapCanvasManager
@ -211,6 +211,10 @@ class MainWindow:
else:
self._update_map_placeholder("Map functionality disabled (Import Error).")
# MODIFICA: Invochiamo il caricamento del profilo di default da qui,
# dopo che la mappa è stata programmata per l'inizializzazione.
self.root.after(250, lambda: self.controller.load_area_profile(DEFAULT_PROFILE_NAME))
self.function_notebook_panel._on_tab_change()
module_logger.info("MainWindow fully initialized.")

View File

@ -3,8 +3,15 @@
Panel managing the area profiles, BBox input, and the function notebooks.
"""
import tkinter as tk
from tkinter import ttk, messagebox
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
@ -13,6 +20,19 @@ 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.
@ -21,7 +41,10 @@ class FunctionNotebookPanel:
self.parent_frame = parent_frame
self.controller = controller
# --- Tkinter Variables ---
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()
@ -30,51 +53,62 @@ class FunctionNotebookPanel:
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) # Let combobox expand
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 = 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)
# --- 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)
@ -84,44 +118,81 @@ class FunctionNotebookPanel:
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)
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")
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 ---
# 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 ---
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 ---
# --- 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)
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:
@ -129,14 +200,40 @@ class FunctionNotebookPanel:
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
if self.function_notebook:
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)
if self.controller: self.controller.on_function_tab_changed(tab_text)
# --- Public Methods ---
# --- 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()
@ -144,24 +241,21 @@ class FunctionNotebookPanel:
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"]:
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):
# ... (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
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}")
@ -169,27 +263,14 @@ class FunctionNotebookPanel:
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)
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)
# Abilita/Disabilita i bottoni dei profili
for child in self.profile_combobox.master.winfo_children():
if isinstance(child, ttk.Button):
child.config(state=state)