733 lines
32 KiB
Python
733 lines
32 KiB
Python
# FlightMonitor/controller/app_controller.py
|
|
from queue import Queue, Empty as QueueEmpty
|
|
|
|
import time
|
|
import os
|
|
import csv
|
|
import copy
|
|
from datetime import datetime, timezone
|
|
|
|
from ..data.opensky_live_adapter import (
|
|
OpenSkyLiveAdapter,
|
|
AdapterMessage,
|
|
)
|
|
from ..data import config as app_config
|
|
from ..utils.logger import get_logger
|
|
from ..data.storage import DataStorage
|
|
from ..data.common_models import CanonicalFlightState
|
|
from ..data.aircraft_database_manager import AircraftDatabaseManager
|
|
from typing import List, Optional, Dict, Any, TYPE_CHECKING, Callable
|
|
|
|
from ..utils.gui_utils import (
|
|
GUI_STATUS_OK,
|
|
GUI_STATUS_WARNING,
|
|
GUI_STATUS_ERROR,
|
|
GUI_STATUS_FETCHING,
|
|
GUI_STATUS_UNKNOWN,
|
|
)
|
|
|
|
from .aircraft_db_importer import AircraftDBImporter
|
|
from .live_data_processor import LiveDataProcessor
|
|
|
|
# MODIFIED: Import MapCommandHandler
|
|
# WHY: To delegate all map-related commands and information updates.
|
|
# HOW: Added import statement.
|
|
from .map_command_handler import MapCommandHandler
|
|
|
|
|
|
from ..map.map_utils import _is_valid_bbox_dict
|
|
|
|
|
|
if TYPE_CHECKING:
|
|
from ..gui.main_window import MainWindow
|
|
from ..gui.dialogs.import_progress_dialog import ImportProgressDialog
|
|
from ..gui.dialogs.full_flight_details_window import FullFlightDetailsWindow
|
|
from ..map.map_canvas_manager import MapCanvasManager
|
|
|
|
module_logger = get_logger(__name__)
|
|
|
|
ADAPTER_JOIN_TIMEOUT_SECONDS = 5.0
|
|
DEFAULT_CLICK_AREA_SIZE_KM = 50.0
|
|
|
|
|
|
class AppController:
|
|
def __init__(self):
|
|
self.main_window: Optional["MainWindow"] = None
|
|
self.live_adapter_thread: Optional[OpenSkyLiveAdapter] = None
|
|
self.is_live_monitoring_active: bool = False
|
|
|
|
self.flight_data_queue: Optional[Queue[AdapterMessage]] = None
|
|
|
|
self._active_bounding_box: Optional[Dict[str, float]] = None
|
|
self.data_storage: Optional[DataStorage] = None
|
|
self.aircraft_db_manager: Optional[AircraftDatabaseManager] = None
|
|
self.aircraft_db_importer: Optional[AircraftDBImporter] = None
|
|
|
|
self.live_data_processor: Optional[LiveDataProcessor] = None
|
|
|
|
# MODIFIED: Declare map_command_handler
|
|
# WHY: This will hold an instance of our new map command processing class.
|
|
# HOW: Added declaration, initialized to None.
|
|
self.map_command_handler: Optional[MapCommandHandler] = None
|
|
|
|
|
|
self.active_detail_window_icao: Optional[str] = None
|
|
self.active_detail_window_ref: Optional["FullFlightDetailsWindow"] = None
|
|
|
|
try:
|
|
self.data_storage = DataStorage()
|
|
module_logger.info("DataStorage initialized successfully by AppController.")
|
|
except Exception as e:
|
|
module_logger.critical(
|
|
f"CRITICAL: Failed to initialize DataStorage: {e}", exc_info=True
|
|
)
|
|
self.data_storage = None
|
|
|
|
try:
|
|
self.aircraft_db_manager = AircraftDatabaseManager()
|
|
module_logger.info(
|
|
"AircraftDatabaseManager initialized successfully by AppController."
|
|
)
|
|
except Exception as e:
|
|
module_logger.critical(
|
|
f"CRITICAL: Failed to initialize AircraftDatabaseManager: {e}",
|
|
exc_info=True,
|
|
)
|
|
self.aircraft_db_manager = None
|
|
module_logger.info("AppController initialized.")
|
|
|
|
def set_main_window(self, main_window_instance: "MainWindow"):
|
|
self.main_window = main_window_instance
|
|
module_logger.debug(
|
|
f"Main window instance ({type(main_window_instance)}) set in AppController."
|
|
)
|
|
|
|
if self.aircraft_db_manager and self.main_window:
|
|
self.aircraft_db_importer = AircraftDBImporter(
|
|
self.aircraft_db_manager, self.main_window
|
|
)
|
|
module_logger.info("AircraftDBImporter initialized successfully by AppController.")
|
|
else:
|
|
module_logger.warning("AircraftDBImporter could not be initialized due to missing dependencies.")
|
|
|
|
self.flight_data_queue = Queue(maxsize=200)
|
|
self.live_data_processor = LiveDataProcessor(self, self.flight_data_queue)
|
|
module_logger.info("LiveDataProcessor initialized successfully by AppController.")
|
|
|
|
# MODIFIED: Instantiate MapCommandHandler here
|
|
# WHY: MapCommandHandler needs access to the AppController instance (self) to delegate to MainWindow/MapCanvasManager.
|
|
# HOW: Created an instance, passing 'self'.
|
|
self.map_command_handler = MapCommandHandler(self)
|
|
module_logger.info("MapCommandHandler initialized successfully by AppController.")
|
|
|
|
|
|
initial_status_msg = "System Initialized. Ready."
|
|
initial_status_level = GUI_STATUS_OK
|
|
if not self.data_storage:
|
|
err_msg_ds = "Data storage init failed. History will not be saved."
|
|
module_logger.error(err_msg_ds)
|
|
initial_status_msg = err_msg_ds
|
|
initial_status_level = GUI_STATUS_ERROR
|
|
if not self.aircraft_db_manager:
|
|
err_msg_adb = "Aircraft DB init failed. Static details may be unavailable."
|
|
module_logger.error(err_msg_adb)
|
|
if initial_status_level == GUI_STATUS_OK:
|
|
initial_status_msg = err_msg_adb
|
|
initial_status_level = GUI_STATUS_WARNING
|
|
else:
|
|
initial_status_msg += f" {err_msg_adb}"
|
|
initial_status_level = GUI_STATUS_ERROR
|
|
|
|
if (
|
|
self.main_window
|
|
and hasattr(self.main_window, "root")
|
|
and self.main_window.root.winfo_exists()
|
|
and hasattr(self.main_window, "update_semaphore_and_status")
|
|
):
|
|
self.main_window.update_semaphore_and_status(
|
|
initial_status_level, initial_status_msg
|
|
)
|
|
else:
|
|
module_logger.error(
|
|
"Main window not set or lacks update_semaphore_and_status during set_main_window."
|
|
)
|
|
|
|
def start_live_monitoring(self, bounding_box: Dict[str, float]):
|
|
if not self.main_window:
|
|
module_logger.error("Controller: Main window not set for live monitoring.")
|
|
return
|
|
if not bounding_box or not _is_valid_bbox_dict(bounding_box):
|
|
err_msg = "Controller: Bounding box is required and must be valid."
|
|
module_logger.error(err_msg)
|
|
if hasattr(self.main_window, "_reset_gui_to_stopped_state"):
|
|
self.main_window._reset_gui_to_stopped_state(f"Start failed: {err_msg}")
|
|
return
|
|
|
|
if not self.data_storage and hasattr(
|
|
self.main_window, "update_semaphore_and_status"
|
|
):
|
|
self.main_window.update_semaphore_and_status(
|
|
GUI_STATUS_WARNING, "DataStorage N/A. History will not be saved."
|
|
)
|
|
|
|
if self.is_live_monitoring_active:
|
|
module_logger.warning(
|
|
"Live monitoring requested but already active or stop in progress."
|
|
)
|
|
if hasattr(self.main_window, "_reset_gui_to_stopped_state"):
|
|
self.main_window._reset_gui_to_stopped_state(
|
|
"Monitoring stop in progress or already active."
|
|
)
|
|
return
|
|
|
|
module_logger.info(
|
|
f"Controller: Starting live monitoring for bbox: {bounding_box}"
|
|
)
|
|
self._active_bounding_box = bounding_box
|
|
|
|
if (
|
|
hasattr(self.main_window, "map_manager_instance")
|
|
and self.main_window.map_manager_instance
|
|
and hasattr(self.main_window.map_manager_instance, "set_target_bbox")
|
|
):
|
|
try:
|
|
self.main_window.map_manager_instance.set_target_bbox(bounding_box)
|
|
except Exception as e_map:
|
|
module_logger.error(f"Error setting map BBox: {e_map}", exc_info=True)
|
|
else:
|
|
if hasattr(self.main_window, "clear_all_views_data"):
|
|
self.main_window.clear_all_views_data()
|
|
|
|
if self.flight_data_queue:
|
|
while not self.flight_data_queue.empty():
|
|
try:
|
|
self.flight_data_queue.get_nowait()
|
|
self.flight_data_queue.task_done()
|
|
except QueueEmpty:
|
|
break
|
|
except Exception:
|
|
break
|
|
|
|
if not self.live_data_processor:
|
|
module_logger.critical("Controller: LiveDataProcessor not initialized. Cannot start monitoring.")
|
|
if hasattr(self.main_window, "_reset_gui_to_stopped_state"):
|
|
self.main_window._reset_gui_to_stopped_state("Start failed: Internal error (Processor N/A).")
|
|
return
|
|
|
|
self.live_adapter_thread = OpenSkyLiveAdapter(
|
|
output_queue=self.flight_data_queue,
|
|
bounding_box=self._active_bounding_box,
|
|
polling_interval=app_config.LIVE_POLLING_INTERVAL_SECONDS,
|
|
)
|
|
self.is_live_monitoring_active = True
|
|
self.live_adapter_thread.start()
|
|
|
|
self.live_data_processor.start_processing_queue()
|
|
|
|
def stop_live_monitoring(self, from_error: bool = False):
|
|
if not self.is_live_monitoring_active and not (
|
|
from_error
|
|
and self.live_adapter_thread
|
|
and self.live_adapter_thread.is_alive()
|
|
):
|
|
if (
|
|
hasattr(self.main_window, "_reset_gui_to_stopped_state")
|
|
and not from_error
|
|
):
|
|
self.main_window._reset_gui_to_stopped_state(
|
|
"Monitoring already stopped."
|
|
)
|
|
return
|
|
|
|
module_logger.info(
|
|
f"Controller: Stopping live monitoring (from_error={from_error})."
|
|
)
|
|
self.is_live_monitoring_active = False
|
|
|
|
if self.live_data_processor:
|
|
self.live_data_processor.stop_processing_queue()
|
|
else:
|
|
module_logger.warning("Controller: LiveDataProcessor not initialized during stop_live_monitoring.")
|
|
|
|
if self.live_adapter_thread and self.live_adapter_thread.is_alive():
|
|
try:
|
|
self.live_adapter_thread.stop()
|
|
if self.main_window and self.main_window.root.winfo_exists():
|
|
self.main_window.root.update_idletasks()
|
|
self.live_adapter_thread.join(timeout=ADAPTER_JOIN_TIMEOUT_SECONDS)
|
|
if self.live_adapter_thread.is_alive():
|
|
module_logger.warning(
|
|
"Adapter thread did not stop in time on stop_live_monitoring."
|
|
)
|
|
except Exception as e_join:
|
|
module_logger.error(f"Error stopping adapter: {e_join}", exc_info=True)
|
|
finally:
|
|
self.live_adapter_thread = None
|
|
|
|
if hasattr(self.main_window, "clear_all_views_data"):
|
|
self.main_window.clear_all_views_data()
|
|
|
|
if hasattr(self.main_window, "_reset_gui_to_stopped_state"):
|
|
msg = (
|
|
"Monitoring stopped due to an error."
|
|
if from_error
|
|
else "Monitoring stopped."
|
|
)
|
|
try:
|
|
self.main_window._reset_gui_to_stopped_state(msg)
|
|
except Exception as e_reset:
|
|
module_logger.error(f"Error resetting GUI: {e_reset}", exc_info=False)
|
|
|
|
self._active_bounding_box = None
|
|
|
|
def on_application_exit(self):
|
|
module_logger.info(
|
|
"Controller: Application exit requested. Cleaning up resources."
|
|
)
|
|
|
|
if (
|
|
self.active_detail_window_ref
|
|
and self.active_detail_window_ref.winfo_exists()
|
|
):
|
|
try:
|
|
module_logger.info(
|
|
f"Closing active detail window for {self.active_detail_window_icao} on app exit."
|
|
)
|
|
self.active_detail_window_ref.destroy()
|
|
except Exception as e_close_detail:
|
|
module_logger.error(
|
|
f"Error closing detail window on app exit: {e_close_detail}"
|
|
)
|
|
finally:
|
|
self.active_detail_window_ref = None
|
|
self.active_detail_window_icao = None
|
|
|
|
if (
|
|
self.main_window
|
|
and hasattr(self.main_window, "map_manager_instance")
|
|
and self.main_window.map_manager_instance is not None
|
|
):
|
|
map_manager = self.main_window.map_manager_instance
|
|
if hasattr(map_manager, "shutdown_worker") and callable(
|
|
map_manager.shutdown_worker
|
|
):
|
|
try:
|
|
map_manager.shutdown_worker()
|
|
module_logger.info(
|
|
"Controller: Main MapCanvasManager worker shutdown requested."
|
|
)
|
|
except Exception as e_map_shutdown:
|
|
module_logger.error(
|
|
f"Controller: Error during Main MapCanvasManager worker shutdown: {e_map_shutdown}",
|
|
exc_info=True,
|
|
)
|
|
|
|
if self.live_data_processor and self.is_live_monitoring_active:
|
|
module_logger.info("Controller: Stopping LiveDataProcessor explicitly on app exit.")
|
|
self.live_data_processor.stop_processing_queue()
|
|
|
|
is_adapter_considered_running = (
|
|
self.live_adapter_thread and self.live_adapter_thread.is_alive()
|
|
) or self.is_live_monitoring_active
|
|
if is_adapter_considered_running:
|
|
self.stop_live_monitoring(from_error=False)
|
|
|
|
if self.data_storage:
|
|
try:
|
|
self.data_storage.close_connection()
|
|
module_logger.info("DataStorage connection closed.")
|
|
except Exception as e_db_close:
|
|
module_logger.error(
|
|
f"Error closing DataStorage: {e_db_close}", exc_info=True
|
|
)
|
|
finally:
|
|
self.data_storage = None
|
|
|
|
if self.aircraft_db_manager:
|
|
try:
|
|
self.aircraft_db_manager.close_connection()
|
|
module_logger.info("AircraftDatabaseManager connection closed.")
|
|
except Exception as e_ac_db_close:
|
|
module_logger.error(
|
|
f"Error closing AircraftDatabaseManager: {e_ac_db_close}",
|
|
exc_info=True,
|
|
)
|
|
finally:
|
|
self.aircraft_db_manager = None
|
|
|
|
module_logger.info("Controller: Cleanup on application exit finished.")
|
|
|
|
def start_history_monitoring(self):
|
|
if not self.main_window:
|
|
module_logger.error("Main window not set for history monitoring.")
|
|
return
|
|
if not self.data_storage:
|
|
err_msg = "DataStorage not initialized. Cannot use history features."
|
|
if hasattr(self.main_window, "update_semaphore_and_status"):
|
|
self.main_window.update_semaphore_and_status(GUI_STATUS_ERROR, err_msg)
|
|
if hasattr(self.main_window, "_reset_gui_to_stopped_state"):
|
|
self.main_window._reset_gui_to_stopped_state(
|
|
f"History start failed: {err_msg}"
|
|
)
|
|
return
|
|
|
|
if hasattr(self.main_window, "update_semaphore_and_status"):
|
|
self.main_window.update_semaphore_and_status(
|
|
GUI_STATUS_OK, "History mode active (placeholder)."
|
|
)
|
|
module_logger.info("History monitoring started (placeholder).")
|
|
|
|
def stop_history_monitoring(self):
|
|
if not self.main_window:
|
|
return
|
|
if hasattr(self.main_window, "update_semaphore_and_status"):
|
|
self.main_window.update_semaphore_and_status(
|
|
GUI_STATUS_OK, "History monitoring stopped."
|
|
)
|
|
module_logger.info("History monitoring stopped (placeholder).")
|
|
if hasattr(self.main_window, "_reset_gui_to_stopped_state"):
|
|
self.main_window._reset_gui_to_stopped_state("History monitoring stopped.")
|
|
|
|
# MODIFIED: Delegated on_map_left_click to MapCommandHandler
|
|
# WHY: This method's logic is now handled by MapCommandHandler.
|
|
# HOW: Changed implementation to delegate to self.map_command_handler.
|
|
def on_map_left_click(
|
|
self, latitude: float, longitude: float, screen_x: int, screen_y: int
|
|
):
|
|
if self.map_command_handler:
|
|
self.map_command_handler.on_map_left_click(latitude, longitude, screen_x, screen_y)
|
|
else:
|
|
module_logger.warning("Controller: MapCommandHandler not initialized for on_map_left_click.")
|
|
|
|
def import_aircraft_database_from_file_with_progress(
|
|
self, csv_filepath: str, progress_dialog_ref: "ImportProgressDialog"
|
|
):
|
|
if not self.aircraft_db_importer:
|
|
module_logger.error(
|
|
"AppController: AircraftDBImporter not initialized. Cannot perform import."
|
|
)
|
|
if progress_dialog_ref and progress_dialog_ref.winfo_exists():
|
|
if self.main_window and self.main_window.root.winfo_exists():
|
|
self.main_window.root.after(0, lambda: progress_dialog_ref.import_finished(
|
|
False, "Error: Import function not initialized correctly (controller issue)."
|
|
))
|
|
elif self.main_window and hasattr(self.main_window, "show_error_message"):
|
|
self.main_window.show_error_message(
|
|
"Import Error", "Aircraft database importer not ready."
|
|
)
|
|
return
|
|
|
|
self.aircraft_db_importer.import_aircraft_database_with_progress(
|
|
csv_filepath, progress_dialog_ref
|
|
)
|
|
|
|
# MODIFIED: Re-added request_detailed_flight_info (ensuring it's here and correct)
|
|
# WHY: This function is crucial for displaying selected flight details in the main window panel.
|
|
# HOW: Re-inserted the function with its corrected logic for combining live and static data.
|
|
def request_detailed_flight_info(self, icao24: str):
|
|
normalized_icao24 = icao24.lower().strip()
|
|
module_logger.info(
|
|
f"Controller: Detailed info request for ICAO24: {normalized_icao24}"
|
|
)
|
|
if not self.main_window:
|
|
module_logger.error(
|
|
"Controller: MainWindow not set, cannot update flight details panel."
|
|
)
|
|
return
|
|
|
|
if not normalized_icao24:
|
|
if hasattr(self.main_window, "update_selected_flight_details"):
|
|
self.main_window.update_selected_flight_details(None)
|
|
return
|
|
|
|
live_data_for_panel: Optional[Dict[str, Any]] = None
|
|
static_data_for_panel: Optional[Dict[str, Any]] = None
|
|
|
|
combined_details_for_panel: Dict[str, Any] = {"icao24": normalized_icao24}
|
|
|
|
if (
|
|
self.main_window
|
|
and hasattr(self.main_window, "map_manager_instance")
|
|
and self.main_window.map_manager_instance
|
|
and hasattr(
|
|
self.main_window.map_manager_instance, "_current_flights_to_display_gui"
|
|
)
|
|
and hasattr(self.main_window.map_manager_instance, "_map_data_lock")
|
|
):
|
|
map_mgr = self.main_window.map_manager_instance
|
|
with map_mgr._map_data_lock:
|
|
for state in map_mgr._current_flights_to_display_gui:
|
|
if state.icao24 == normalized_icao24:
|
|
live_data_for_panel = state.to_dict()
|
|
break
|
|
|
|
if live_data_for_panel:
|
|
combined_details_for_panel.update(live_data_for_panel)
|
|
module_logger.debug(f"AppController: Added live data to details for {normalized_icao24}.")
|
|
|
|
if self.aircraft_db_manager:
|
|
static_data_for_panel = self.aircraft_db_manager.get_aircraft_details(
|
|
normalized_icao24
|
|
)
|
|
if static_data_for_panel:
|
|
for k, v in static_data_for_panel.items():
|
|
if k not in combined_details_for_panel or combined_details_for_panel[k] is None:
|
|
combined_details_for_panel[k] = v
|
|
module_logger.debug(f"AppController: Added static data to details for {normalized_icao24}.")
|
|
|
|
if hasattr(self.main_window, "update_selected_flight_details"):
|
|
self.main_window.update_selected_flight_details(combined_details_for_panel)
|
|
module_logger.debug(f"AppController: Called update_selected_flight_details with combined data for {normalized_icao24}.")
|
|
|
|
def request_and_show_full_flight_details(self, icao24: str):
|
|
normalized_icao24 = icao24.lower().strip()
|
|
module_logger.info(
|
|
f"Controller: Requesting to show full details for ICAO24: {normalized_icao24}"
|
|
)
|
|
|
|
if (
|
|
not self.main_window
|
|
or not hasattr(self.main_window, "root")
|
|
or not self.main_window.root.winfo_exists()
|
|
):
|
|
module_logger.error(
|
|
"Controller: MainWindow not available to show full flight details."
|
|
)
|
|
return
|
|
|
|
if not normalized_icao24:
|
|
module_logger.warning("Controller: Empty ICAO24 for full details request.")
|
|
if hasattr(self.main_window, "show_info_message"):
|
|
self.main_window.show_info_message(
|
|
"Flight Details", "No ICAO24 provided for full details."
|
|
)
|
|
return
|
|
|
|
if (
|
|
self.active_detail_window_ref
|
|
and self.active_detail_window_ref.winfo_exists()
|
|
):
|
|
if self.active_detail_window_icao == normalized_icao24:
|
|
module_logger.info(
|
|
f"Detail window for {normalized_icao24} already open. Re-focusing/Re-populating."
|
|
)
|
|
self.active_detail_window_ref.lift()
|
|
self.active_detail_window_ref.focus_set()
|
|
else:
|
|
module_logger.info(
|
|
f"Closing existing detail window for {self.active_detail_window_icao} before opening new one for {normalized_icao24}."
|
|
)
|
|
try:
|
|
self.active_detail_window_ref.destroy()
|
|
except tk.TclError:
|
|
pass
|
|
|
|
static_data: Optional[Dict[str, Any]] = None
|
|
if self.aircraft_db_manager:
|
|
static_data = self.aircraft_db_manager.get_aircraft_details(
|
|
normalized_icao24
|
|
)
|
|
|
|
live_data: Optional[Dict[str, Any]] = None
|
|
if (
|
|
self.main_window
|
|
and hasattr(self.main_window, "map_manager_instance")
|
|
and self.main_window.map_manager_instance
|
|
and hasattr(
|
|
self.main_window.map_manager_instance, "_current_flights_to_display_gui"
|
|
)
|
|
and hasattr(self.main_window.map_manager_instance, "_map_data_lock")
|
|
):
|
|
map_mgr = self.main_window.map_manager_instance
|
|
with map_mgr._map_data_lock:
|
|
for state in map_mgr._current_flights_to_display_gui:
|
|
if state.icao24 == normalized_icao24:
|
|
live_data = state.to_dict()
|
|
break
|
|
|
|
full_track_data_list: List[Dict[str, Any]] = []
|
|
if self.data_storage:
|
|
try:
|
|
current_utc_date = datetime.now(timezone.utc)
|
|
track_states = self.data_storage.get_flight_track_for_icao_on_date(
|
|
normalized_icao24, current_utc_date
|
|
)
|
|
if track_states:
|
|
full_track_data_list = [state.to_dict() for state in track_states]
|
|
except Exception as e_track:
|
|
module_logger.error(
|
|
f"FullDetails: Error retrieving historical track for {normalized_icao24}: {e_track}",
|
|
exc_info=True,
|
|
)
|
|
|
|
try:
|
|
from ..gui.dialogs.full_flight_details_window import FullFlightDetailsWindow
|
|
|
|
if (
|
|
self.active_detail_window_ref
|
|
and self.active_detail_window_icao == normalized_icao24
|
|
and self.active_detail_window_ref.winfo_exists()
|
|
):
|
|
details_win = self.active_detail_window_ref
|
|
else:
|
|
details_win = FullFlightDetailsWindow(
|
|
self.main_window.root, normalized_icao24, self
|
|
)
|
|
self.active_detail_window_ref = details_win
|
|
self.active_detail_window_icao = normalized_icao24
|
|
if self.main_window:
|
|
self.main_window.full_flight_details_window = details_win
|
|
|
|
details_win.update_details(static_data, live_data, full_track_data_list)
|
|
|
|
except ImportError:
|
|
module_logger.error(
|
|
"FullFlightDetailsWindow class not found. Cannot display full details."
|
|
)
|
|
if hasattr(self.main_window, "show_error_message"):
|
|
self.main_window.show_error_message(
|
|
"UI Error", "Could not open full details window (import error)."
|
|
)
|
|
except Exception as e_show_details:
|
|
module_logger.error(
|
|
f"Error showing full flight details window for {normalized_icao24}: {e_show_details}",
|
|
exc_info=True,
|
|
)
|
|
if hasattr(self.main_window, "show_error_message"):
|
|
self.main_window.show_error_message(
|
|
"Error", f"Could not display full details: {e_show_details}"
|
|
)
|
|
self.active_detail_window_ref = None
|
|
self.active_detail_window_icao = None
|
|
if self.main_window:
|
|
self.main_window.full_flight_details_window = None
|
|
|
|
def details_window_closed(self, closed_icao24: str):
|
|
normalized_closed_icao24 = closed_icao24.lower().strip()
|
|
if self.active_detail_window_icao == normalized_closed_icao24:
|
|
module_logger.info(
|
|
f"AppController: Detail window for {normalized_closed_icao24} reported closed. Clearing references."
|
|
)
|
|
self.active_detail_window_ref = None
|
|
self.active_detail_window_icao = None
|
|
if (
|
|
self.main_window
|
|
and hasattr(self.main_window, "full_flight_details_window")
|
|
and self.main_window.full_flight_details_window
|
|
and not self.main_window.full_flight_details_window.winfo_exists()
|
|
):
|
|
self.main_window.full_flight_details_window = None
|
|
else:
|
|
module_logger.debug(
|
|
f"AppController: A detail window for {normalized_closed_icao24} closed, but it was not the currently tracked active one ({self.active_detail_window_icao}). No action on active_detail references."
|
|
)
|
|
|
|
# MODIFIED: Delegated on_map_right_click to MapCommandHandler
|
|
# WHY: This method's logic is now handled by MapCommandHandler.
|
|
# HOW: Changed implementation to delegate to self.map_command_handler.
|
|
def on_map_right_click(
|
|
self, latitude: float, longitude: float, screen_x: int, screen_y: int
|
|
):
|
|
if self.map_command_handler:
|
|
self.map_command_handler.on_map_right_click(latitude, longitude, screen_x, screen_y)
|
|
else:
|
|
module_logger.warning("Controller: MapCommandHandler not initialized for on_map_right_click.")
|
|
|
|
# MODIFIED: Delegated on_map_context_menu_request to MapCommandHandler
|
|
# WHY: This method's logic is now handled by MapCommandHandler.
|
|
# HOW: Changed implementation to delegate to self.map_command_handler.
|
|
def on_map_context_menu_request(
|
|
self, latitude: float, longitude: float, screen_x: int, screen_y: int
|
|
):
|
|
if self.map_command_handler:
|
|
self.map_command_handler.on_map_context_menu_request(latitude, longitude, screen_x, screen_y)
|
|
else:
|
|
module_logger.warning("Controller: MapCommandHandler not initialized for on_map_context_menu_request.")
|
|
|
|
# MODIFIED: Delegated recenter_map_at_coords to MapCommandHandler
|
|
# WHY: This method's logic is now handled by MapCommandHandler.
|
|
# HOW: Changed implementation to delegate to self.map_command_handler.
|
|
def recenter_map_at_coords(self, lat: float, lon: float):
|
|
if self.map_command_handler:
|
|
self.map_command_handler.recenter_map_at_coords(lat, lon)
|
|
else:
|
|
module_logger.warning("Controller: MapCommandHandler not initialized for recenter_map_at_coords.")
|
|
|
|
# MODIFIED: Delegated set_bbox_around_coords to MapCommandHandler
|
|
# WHY: This method's logic is now handled by MapCommandHandler.
|
|
# HOW: Changed implementation to delegate to self.map_command_handler.
|
|
def set_bbox_around_coords(
|
|
self,
|
|
center_lat: float,
|
|
center_lon: float,
|
|
area_size_km: float = DEFAULT_CLICK_AREA_SIZE_KM,
|
|
):
|
|
if self.map_command_handler:
|
|
self.map_command_handler.set_bbox_around_coords(center_lat, center_lon, area_size_km)
|
|
else:
|
|
module_logger.warning("Controller: MapCommandHandler not initialized for set_bbox_around_coords.")
|
|
|
|
# MODIFIED: Delegated update_bbox_gui_fields to MapCommandHandler
|
|
# WHY: This method's logic is now handled by MapCommandHandler.
|
|
# HOW: Changed implementation to delegate to self.map_command_handler.
|
|
def update_bbox_gui_fields(self, bbox_dict: Dict[str, float]):
|
|
if self.map_command_handler:
|
|
self.map_command_handler.update_bbox_gui_fields(bbox_dict)
|
|
else:
|
|
module_logger.warning("Controller: MapCommandHandler not initialized for update_bbox_gui_fields.")
|
|
|
|
# MODIFIED: Delegated update_general_map_info to MapCommandHandler
|
|
# WHY: This method's logic is now handled by MapCommandHandler.
|
|
# HOW: Changed implementation to delegate to self.map_command_handler.
|
|
def update_general_map_info(self):
|
|
if self.map_command_handler:
|
|
self.map_command_handler.update_general_map_info()
|
|
else:
|
|
module_logger.warning("Controller: MapCommandHandler not initialized for update_general_map_info.")
|
|
|
|
# MODIFIED: Delegated map_zoom_in to MapCommandHandler
|
|
# WHY: This method's logic is now handled by MapCommandHandler.
|
|
# HOW: Changed implementation to delegate to self.map_command_handler.
|
|
def map_zoom_in(self):
|
|
if self.map_command_handler:
|
|
self.map_command_handler.map_zoom_in()
|
|
else:
|
|
module_logger.warning("Controller: MapCommandHandler not initialized for map_zoom_in.")
|
|
|
|
# MODIFIED: Delegated map_zoom_out to MapCommandHandler
|
|
# WHY: This method's logic is now handled by MapCommandHandler.
|
|
# HOW: Changed implementation to delegate to self.map_command_handler.
|
|
def map_zoom_out(self):
|
|
if self.map_command_handler:
|
|
self.map_command_handler.map_zoom_out()
|
|
else:
|
|
module_logger.warning("Controller: MapCommandHandler not initialized for map_zoom_out.")
|
|
|
|
# MODIFIED: Delegated map_pan_direction to MapCommandHandler
|
|
# WHY: This method's logic is now handled by MapCommandHandler.
|
|
# HOW: Changed implementation to delegate to self.map_command_handler.
|
|
def map_pan_direction(self, direction: str):
|
|
if self.map_command_handler:
|
|
self.map_command_handler.map_pan_direction(direction)
|
|
else:
|
|
module_logger.warning("Controller: MapCommandHandler not initialized for map_pan_direction.")
|
|
|
|
# MODIFIED: Delegated map_center_on_coords_and_fit_patch to MapCommandHandler
|
|
# WHY: This method's logic is now handled by MapCommandHandler.
|
|
# HOW: Changed implementation to delegate to self.map_command_handler.
|
|
def map_center_on_coords_and_fit_patch(
|
|
self, lat: float, lon: float, patch_size_km: float
|
|
):
|
|
if self.map_command_handler:
|
|
self.map_command_handler.map_center_on_coords_and_fit_patch(lat, lon, patch_size_km)
|
|
else:
|
|
module_logger.warning("Controller: MapCommandHandler not initialized for map_center_on_coords_and_fit_patch.")
|
|
|
|
# MODIFIED: Delegated set_map_track_length to MapCommandHandler
|
|
# WHY: This method's logic is now handled by MapCommandHandler.
|
|
# HOW: Changed implementation to delegate to self.map_command_handler.
|
|
def set_map_track_length(self, length: int):
|
|
if self.map_command_handler:
|
|
self.map_command_handler.set_map_track_length(length)
|
|
else:
|
|
module_logger.warning("Controller: MapCommandHandler not initialized for set_map_track_length.") |