diff --git a/flightmonitor/controller/app_controller.py b/flightmonitor/controller/app_controller.py index 821618e..a5651db 100644 --- a/flightmonitor/controller/app_controller.py +++ b/flightmonitor/controller/app_controller.py @@ -661,7 +661,6 @@ class AppController: else: module_logger.warning("Main window N/A to update BBox GUI fields.") def update_general_map_info(self): - # ... (come prima) module_logger.debug("Controller: Request to update general map info.") if not (self.main_window and hasattr(self.main_window, "map_manager_instance") and self.main_window.map_manager_instance): module_logger.debug("Skipping general map info update: MainWindow or map manager not ready.") @@ -670,30 +669,47 @@ class AppController: except Exception: pass return - map_manager: "MapCanvasManager" = self.main_window.map_manager_instance + map_manager: "MapCanvasManager" = self.main_window.map_manager_instance # type: ignore if hasattr(map_manager, "get_current_map_info"): map_info = {} try: - map_info = map_manager.get_current_map_info() - zoom, map_geo_bounds, target_bbox_input, flight_count = ( - map_info.get("zoom"), map_info.get("map_geo_bounds"), - map_info.get("target_bbox_input"), map_info.get("flight_count") - ) - map_size_km_w, map_size_km_h = map_info.get("map_size_km_w"), map_info.get("map_size_km_h") + map_info = map_manager.get_current_map_info() # Questa chiamata dovrebbe restituire il target_bbox_input + module_logger.debug(f"Fetched map info from manager for general display: {map_info}") + + zoom = map_info.get("zoom") + map_geo_bounds = map_info.get("map_geo_bounds") + # target_bbox_input viene ora da map_info, che dovrebbe averlo copiato in modo sicuro + target_bbox_input_from_map_info = map_info.get("target_bbox_input") + flight_count = map_info.get("flight_count") + map_size_km_w = map_info.get("map_size_km_w") + map_size_km_h = map_info.get("map_size_km_h") + map_size_str = "N/A" if map_size_km_w is not None and map_size_km_h is not None: try: decimals = getattr(app_config, "MAP_SIZE_KM_DECIMAL_PLACES", 1) map_size_str = f"{map_size_km_w:.{decimals}f}km x {map_size_km_h:.{decimals}f}km" - except Exception as e_format: module_logger.warning(f"Error formatting map size: {e_format}. Using N/A.", exc_info=False); map_size_str = "N/A (FormatErr)" + except Exception as e_format: + module_logger.warning(f"Error formatting map size: {e_format}. Using N/A.", exc_info=False) + map_size_str = "N/A (FormatErr)" if hasattr(self.main_window, "update_general_map_info_display"): - try: self.main_window.update_general_map_info_display(zoom=zoom, map_size_str=map_size_str, map_geo_bounds=map_geo_bounds, target_bbox_input=target_bbox_input, flight_count=flight_count) - except tk.TclError as e_tcl: module_logger.warning(f"TclError updating general map info panel: {e_tcl}. GUI closing.") - except Exception as e_update: module_logger.error(f"Error updating general map info panel: {e_update}", exc_info=False) - else: module_logger.warning("MainWindow missing 'update_general_map_info_display' method.") + try: + self.main_window.update_general_map_info_display( + zoom=zoom, + map_size_str=map_size_str, + map_geo_bounds=map_geo_bounds, + target_bbox_input=target_bbox_input_from_map_info, # Usa quello da map_info + flight_count=flight_count + ) + except tk.TclError as e_tcl: + module_logger.warning(f"TclError updating general map info panel: {e_tcl}. GUI closing.") + except Exception as e_update: + module_logger.error(f"Error updating general map info panel: {e_update}", exc_info=False) + else: + module_logger.warning("MainWindow missing 'update_general_map_info_display' method.") except Exception as e_get_info: - module_logger.error(f"Error getting map info from map manager: {e_get_info}", exc_info=False) + module_logger.error(f"Error getting map info from map manager in AppController: {e_get_info}", exc_info=True) # Logga con stacktrace if hasattr(self.main_window, "update_general_map_info_display"): try: self.main_window.update_general_map_info_display(zoom=None, map_size_str="N/A", map_geo_bounds=None, target_bbox_input=None, flight_count=None) except Exception: pass diff --git a/flightmonitor/map/map_canvas_manager.py b/flightmonitor/map/map_canvas_manager.py index 5dfe9d2..422cdaf 100644 --- a/flightmonitor/map/map_canvas_manager.py +++ b/flightmonitor/map/map_canvas_manager.py @@ -7,7 +7,8 @@ from typing import Optional, Tuple, List, Dict, Any from collections import deque import queue # For Queue and Empty import threading -import copy # For deepcopy +from . import map_utils +import copy try: from PIL import Image, ImageTk, ImageDraw, ImageFont @@ -1267,75 +1268,93 @@ class MapCanvasManager: self._is_left_button_pressed = True def _on_left_button_release(self, event: tk.Event): - if not self.canvas.winfo_exists(): + if not self.canvas.winfo_exists(): return + logger.debug(f"MCM Left Button Release: CanvasX={event.x}, CanvasY={event.y}") # Usa module_logger + + if not self._is_left_button_pressed: # Verifica se il bottone era stato premuto + logger.debug("MCM Left Button Release: Button was not previously pressed. Ignoring.") + self._drag_start_x_canvas, self._drag_start_y_canvas = None, None # Resetta per sicurezza return - logger.debug( - f"MCM _on_left_button_release: CanvasX={event.x}, CanvasY={event.y}" - ) - if not self._is_left_button_pressed: - logger.debug( - "MCM _on_left_button_release: Button was not pressed. Ignoring." - ) - return - self._is_left_button_pressed = False - if ( - self._drag_start_x_canvas is not None - and self._drag_start_y_canvas is not None - ): - drag_thresh = 5 + self._is_left_button_pressed = False # Resetta lo stato del bottone + + clicked_flight_icao: Optional[str] = None # Per memorizzare l'ICAO del volo cliccato + + if self._drag_start_x_canvas is not None and self._drag_start_y_canvas is not None: + drag_threshold_px = 10 # Soglia per distinguere click da drag dx = abs(event.x - self._drag_start_x_canvas) dy = abs(event.y - self._drag_start_y_canvas) - logger.debug( - f"MCM _on_left_button_release: Drag dx={dx}, dy={dy}. Threshold={drag_thresh}" - ) - if dx < drag_thresh and dy < drag_thresh: # È un click - logger.debug(f"MCM _on_left_button_release: Detected as a click.") - if ( - self._current_map_geo_bounds_gui is not None - and self._map_photo_image is not None - ): + + is_click = (dx < drag_threshold_px and dy < drag_threshold_px) + logger.debug(f"MCM Left Button Release: dx={dx}, dy={dy}. Is click: {is_click}") + + if is_click: + logger.debug("MCM Left Button Release: Detected as a map click.") + if self._current_map_geo_bounds_gui is not None and self._map_photo_image is not None: try: - map_pixel_shape = ( - self._map_photo_image.width(), - self._map_photo_image.height(), - ) - logger.debug( - f"MCM _on_left_button_release: Using map_pixel_shape={map_pixel_shape} and bounds={self._current_map_geo_bounds_gui} for geo conversion." - ) - clicked_lon, clicked_lat = _pixel_to_geo( - event.x, - event.y, - self._current_map_geo_bounds_gui, - map_pixel_shape, + map_pixel_shape = (self._map_photo_image.width(), self._map_photo_image.height()) + clicked_lon, clicked_lat = map_utils._pixel_to_geo( # Usa map_drawing._pixel_to_geo + event.x, event.y, + self._current_map_geo_bounds_gui, + map_pixel_shape ) + if clicked_lon is not None and clicked_lat is not None: - logger.info( - f"Map Left-Clicked at Geo ({clicked_lat:.5f}, {clicked_lon:.5f}) from Canvas ({event.x},{event.y})" - ) - if self.app_controller and hasattr( - self.app_controller, "on_map_left_click" - ): - self.app_controller.on_map_left_click( - clicked_lat, clicked_lon, event.x_root, event.y_root - ) - else: - logger.warning( - f"Failed to convert left click pixel ({event.x},{event.y}) to geo. Current map bounds: {self._current_map_geo_bounds_gui}" - ) - except Exception as e_click_convert: - logger.error( - f"Error during left click geo conversion: {e_click_convert}", - exc_info=True, - ) - else: - logger.warning( - "Map context missing for left click geo conversion (_current_map_geo_bounds_gui or _map_photo_image is None)." - ) - else: # È un drag - # La logica di panning con drag è più complessa, per ora gestiamo solo il click - logger.debug( - "MCM _on_left_button_release: Detected as a drag, not a click. (Drag Panning TBD)" - ) + logger.info(f"Map Left-Clicked at Geo ({clicked_lat:.5f}, {clicked_lon:.5f}) from Canvas ({event.x},{event.y})") + if self.app_controller and hasattr(self.app_controller, "on_map_left_click"): + self.app_controller.on_map_left_click(clicked_lat, clicked_lon, event.x_root, event.y_root) + + # --- Logica per identificare il volo cliccato --- + min_dist_sq_to_flight = float('inf') + # Soglia di click in pixel per selezionare un aereo, al quadrato per evitare sqrt + flight_click_radius_px_sq = (15 * 15) + + with self._map_data_lock: # Accedi in modo sicuro alla lista dei voli + flights_on_map = self._current_flights_to_display_gui + + if flights_on_map: # Solo se ci sono voli da controllare + for flight in flights_on_map: + if flight.latitude is not None and flight.longitude is not None: + # Converte la posizione del volo in pixel sulla mappa attuale + flight_px_coords = map_drawing._geo_to_pixel_on_unscaled_map( + flight.latitude, flight.longitude, + self._current_map_geo_bounds_gui, # Bounds della mappa visualizzata + map_pixel_shape # Dimensioni in pixel della mappa visualizzata + ) + if flight_px_coords: + # Calcola la distanza al quadrato tra il click e l'aereo + dist_sq = (event.x - flight_px_coords[0])**2 + (event.y - flight_px_coords[1])**2 + if dist_sq < flight_click_radius_px_sq and dist_sq < min_dist_sq_to_flight: + min_dist_sq_to_flight = dist_sq + clicked_flight_icao = flight.icao24 + + if clicked_flight_icao: + logger.info(f"Flight selected by click: {clicked_flight_icao}") + if self.app_controller and hasattr(self.app_controller, "request_detailed_flight_info"): + self.app_controller.request_detailed_flight_info(clicked_flight_icao) + else: + logger.info("No specific flight selected by click (click was on map but not near an aircraft). Clearing details.") + if self.app_controller and hasattr(self.app_controller, "request_detailed_flight_info"): + self.app_controller.request_detailed_flight_info("") # Invia ICAO vuoto per pulire il pannello + + else: # clicked_lon/lat is None + logger.warning(f"Failed to convert left click pixel ({event.x},{event.y}) to geo coordinates.") + if self.app_controller and hasattr(self.app_controller, "request_detailed_flight_info"): + self.app_controller.request_detailed_flight_info("") # Pulisci dettagli se il click non è valido + + except Exception as e_click_processing: + logger.error(f"Error during left click processing (geo conversion or flight selection): {e_click_processing}", exc_info=True) + if self.app_controller and hasattr(self.app_controller, "request_detailed_flight_info"): + self.app_controller.request_detailed_flight_info("") # Pulisci in caso di errore + + else: # Manca il contesto della mappa (_current_map_geo_bounds_gui o _map_photo_image) + logger.warning("Map context missing for left click geo conversion or flight selection.") + if self.app_controller and hasattr(self.app_controller, "request_detailed_flight_info"): + self.app_controller.request_detailed_flight_info("") + else: # Era un drag + logger.debug("MCM Left Button Release: Detected as a drag. Panning logic (if any) would go here.") + # Se implementi il drag-to-pan, qui non faresti nulla per la selezione dei voli. + + # Resetta le coordinate di inizio drag self._drag_start_x_canvas, self._drag_start_y_canvas = None, None def _on_right_click(self, event: tk.Event):