226 lines
8.9 KiB
Python
226 lines
8.9 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 flightmonitor.utils.logger import get_logger
|
|
from flightmonitor.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}"
|
|
)
|