SXXXXXXX_FlightMonitor/flightmonitor/gui/panels/map_tools_panel.py
2025-06-04 10:14:38 +02:00

207 lines
8.7 KiB
Python

# FlightMonitor/gui/panels/map_tools_panel.py
"""
Panel for controlling map interactions (zoom, pan, center & fit).
"""
import tkinter as tk
from tkinter import ttk
from typing import Any, Optional, Tuple
from ...utils.logger import get_logger
from ...data import config as app_config # Used for default patch size for center & fit
module_logger = get_logger(__name__)
# Constants for map tools, if any specific to this panel
DEFAULT_CENTER_PATCH_SIZE_KM = 100 # Default value for the "Patch (km)" entry
class MapToolsPanel:
"""
Manages the GUI elements for map control, including zoom, pan, and centering.
"""
def __init__(self, parent_frame: ttk.Frame, controller: Any):
"""
Initializes the MapToolsPanel.
Args:
parent_frame: The parent ttk.Frame where this panel will be placed.
controller: The application controller instance to delegate map actions.
"""
self.parent_frame = parent_frame
self.controller = controller
# --- Zoom Controls ---
# Crea un frame per i controlli di zoom e i relativi bottoni.
self.zoom_frame = ttk.Frame(self.parent_frame)
self.zoom_frame.pack(side=tk.TOP, pady=(0, 10))
ttk.Button(self.zoom_frame, text="Zoom In (+)", command=self._on_zoom_in).pack(
side=tk.LEFT, padx=2
)
ttk.Button(self.zoom_frame, text="Zoom Out (-)", command=self._on_zoom_out).pack(
side=tk.LEFT, padx=2
)
# --- Pan Controls ---
# Crea un frame per i controlli di pan (spostamento) e i relativi bottoni, disposti a croce.
self.pan_frame = ttk.Frame(self.parent_frame)
self.pan_frame.pack(side=tk.TOP, pady=(0, 10))
ttk.Button(self.pan_frame, text="Up", command=lambda: self._on_pan("up")).grid(
row=0, column=1, padx=2, pady=2
)
ttk.Button(self.pan_frame, text="Left", command=lambda: self._on_pan("left")).grid(
row=1, column=0, padx=2, pady=2
)
ttk.Button(
self.pan_frame, text="Right", command=lambda: self._on_pan("right")
).grid(row=1, column=2, padx=2, pady=2)
ttk.Button(self.pan_frame, text="Down", command=lambda: self._on_pan("down")).grid(
row=2, column=1, padx=2, pady=2
)
# Configura le colonne per garantire un allineamento corretto dei bottoni di pan.
self.pan_frame.columnconfigure(0, weight=1)
self.pan_frame.columnconfigure(2, weight=1)
# --- Center Map Controls ---
# Crea un LabelFrame per i controlli di centramento mappa, inclusi input per lat, lon e dimensione patch.
self.center_frame = ttk.LabelFrame(
self.parent_frame, text="Center Map", padding=5
)
self.center_frame.pack(side=tk.TOP, fill=tk.X, pady=(5, 0))
# Configura le colonne per gli input di lat/lon/patch.
self.center_frame.columnconfigure(1, weight=1)
self.center_frame.columnconfigure(3, weight=1)
ttk.Label(self.center_frame, text="Lat:").grid(
row=0, column=0, padx=(0, 2), pady=2, sticky=tk.W
)
self.center_lat_var = tk.StringVar()
self.center_lat_entry = ttk.Entry(
self.center_frame, textvariable=self.center_lat_var, width=10
)
self.center_lat_entry.grid(row=0, column=1, padx=(0, 5), pady=2, sticky=tk.EW)
ttk.Label(self.center_frame, text="Lon:").grid(
row=0, column=2, padx=(5, 2), pady=2, sticky=tk.W
)
self.center_lon_var = tk.StringVar()
self.center_lon_entry = ttk.Entry(
self.center_frame, textvariable=self.center_lon_var, width=10
)
self.center_lon_entry.grid(row=0, column=3, padx=(0, 0), pady=2, sticky=tk.EW)
ttk.Label(self.center_frame, text="Patch (km):").grid(
row=1, column=0, padx=(0, 2), pady=2, sticky=tk.W
)
self.center_patch_size_var = tk.StringVar(value=str(DEFAULT_CENTER_PATCH_SIZE_KM))
self.center_patch_size_entry = ttk.Entry(
self.center_frame, textvariable=self.center_patch_size_var, width=7
)
self.center_patch_size_entry.grid(
row=1, column=1, padx=(0, 5), pady=2, sticky=tk.W
)
self.center_map_button = ttk.Button(
self.center_frame, text="Center & Fit Patch", command=self._on_center_and_fit
)
self.center_map_button.grid(
row=1, column=2, columnspan=2, padx=5, pady=5, sticky=tk.E
)
module_logger.debug("MapToolsPanel initialized.")
def _on_zoom_in(self):
"""Delegates the 'zoom in' action to the application controller."""
if self.controller and hasattr(self.controller, "map_zoom_in"):
self.controller.map_zoom_in()
else:
module_logger.warning("Controller or map_zoom_in method not available.")
def _on_zoom_out(self):
"""Delegates the 'zoom out' action to the application controller."""
if self.controller and hasattr(self.controller, "map_zoom_out"):
self.controller.map_zoom_out()
else:
module_logger.warning("Controller or map_zoom_out method not available.")
def _on_pan(self, direction: str):
"""
Delegates a 'pan' action in a specified direction to the application controller.
Args:
direction: The direction to pan ("up", "down", "left", "right").
"""
if self.controller and hasattr(self.controller, "map_pan_direction"):
self.controller.map_pan_direction(direction)
else:
module_logger.warning(
f"Controller or map_pan_direction method not available for pan '{direction}'."
)
def _on_center_and_fit(self):
"""
Retrieves latitude, longitude, and patch size from GUI entries and
delegates the 'center and fit' action to the application controller.
Includes input validation and error reporting via MainWindow.
"""
try:
# Retrieve string values from Tkinter variables
lat_str = self.center_lat_var.get()
lon_str = self.center_lon_var.get()
patch_str = self.center_patch_size_var.get()
# Input validation: check for empty strings
if not lat_str or not lon_str or not patch_str:
self._show_error_message(
"Input Error", "Latitude, Longitude, and Patch size are required."
)
return
# Convert string values to floats
lat = float(lat_str)
lon = float(lon_str)
patch = float(patch_str)
# Validate numerical ranges for geographic coordinates and patch size
if not (-90 <= lat <= 90 and -180 <= lon <= 180 and patch > 0):
self._show_error_message(
"Input Error",
"Invalid latitude (-90 to 90), longitude (-180 to 180), or patch size (>0).",
)
return
# Delegate the action to the controller
if self.controller and hasattr(self.controller, "map_center_on_coords_and_fit_patch"):
self.controller.map_center_on_coords_and_fit_patch(lat, lon, patch)
else:
module_logger.warning("Controller or map_center_on_coords_and_fit_patch method not available.")
self._show_error_message(
"Error", "Map center function not available (controller issue)."
)
except ValueError:
# Catch errors if float conversion fails (e.g., non-numeric input)
self._show_error_message(
"Input Error",
"Latitude, Longitude, and Patch size must be valid numbers.",
)
except Exception as e:
# Catch any other unexpected errors
module_logger.error(f"Error in _on_center_and_fit: {e}", exc_info=True)
self._show_error_message("Error", f"An unexpected error occurred: {e}")
def _show_error_message(self, title: str, message: str):
"""
Helper method to show an error message, typically delegated to MainWindow.
This avoids direct dependency on tkinter.messagebox here.
"""
if self.controller and hasattr(self.controller, "main_window") and self.controller.main_window:
main_window = self.controller.main_window
if hasattr(main_window, "show_error_message"):
main_window.show_error_message(title, message)
else:
module_logger.error(f"MainWindow.show_error_message not found. Error: {title} - {message}")
else:
module_logger.error(f"Controller or MainWindow not available to show error: {title} - {message}")