739 lines
30 KiB
Python
739 lines
30 KiB
Python
# FlightMonitor/gui/main_window.py
|
|
"""
|
|
Main window of the Flight Monitor application.
|
|
Handles the layout with multiple notebooks for functions and views,
|
|
user interactions, status display including a semaphore, and logging.
|
|
"""
|
|
import tkinter as tk
|
|
from tkinter import ttk
|
|
from tkinter import messagebox
|
|
from tkinter import filedialog
|
|
from tkinter import Menu
|
|
from tkinter import font as tkFont
|
|
from typing import List, Dict, Optional, Tuple, Any
|
|
import logging
|
|
import os
|
|
from datetime import datetime, timezone
|
|
import webbrowser
|
|
|
|
from ..data import config as app_config
|
|
|
|
from ..utils.logger import (
|
|
get_logger,
|
|
add_tkinter_handler,
|
|
shutdown_logging_system,
|
|
)
|
|
from ..data.logging_config import LOGGING_CONFIG
|
|
from ..data.common_models import CanonicalFlightState
|
|
from ..utils.gui_utils import (
|
|
GUI_STATUS_OK,
|
|
GUI_STATUS_WARNING,
|
|
GUI_STATUS_ERROR,
|
|
GUI_STATUS_FETCHING,
|
|
GUI_STATUS_UNKNOWN,
|
|
GUI_STATUS_PERMANENT_FAILURE,
|
|
)
|
|
|
|
from .panels.log_status_panel import LogStatusPanel
|
|
from .panels.map_tools_panel import MapToolsPanel
|
|
from .panels.map_info_panel import MapInfoPanel
|
|
from .panels.selected_flight_details_panel import SelectedFlightDetailsPanel
|
|
from .panels.function_notebook_panel import FunctionNotebookPanel
|
|
from .panels.views_notebook_panel import ViewsNotebookPanel
|
|
|
|
try:
|
|
from ..map.map_canvas_manager import MapCanvasManager
|
|
from ..map.map_utils import _is_valid_bbox_dict
|
|
|
|
MAP_CANVAS_MANAGER_AVAILABLE = True
|
|
except ImportError as e_map_import:
|
|
MapCanvasManager = None
|
|
_is_valid_bbox_dict = lambda x: False
|
|
MAP_CANVAS_MANAGER_AVAILABLE = False
|
|
print(
|
|
f"CRITICAL ERROR in MainWindow import: Failed to import MapCanvasManager or map_utils: {e_map_import}. Map functionality will be disabled.",
|
|
flush=True,
|
|
)
|
|
|
|
try:
|
|
from .dialogs.import_progress_dialog import ImportProgressDialog
|
|
|
|
IMPORT_DIALOG_AVAILABLE = True
|
|
except ImportError as e_dialog_import:
|
|
ImportProgressDialog = None
|
|
IMPORT_DIALOG_AVAILABLE = False
|
|
print(
|
|
f"ERROR in MainWindow import: Failed to import ImportProgressDialog: {e_dialog_import}. Import progress UI will be basic.",
|
|
flush=True,
|
|
)
|
|
|
|
module_logger = get_logger(__name__)
|
|
|
|
BBOX_COLOR_INSIDE = "green4"
|
|
BBOX_COLOR_OUTSIDE = "red2"
|
|
BBOX_COLOR_PARTIAL = "darkorange"
|
|
BBOX_COLOR_NA = "gray50"
|
|
|
|
|
|
class MainWindow:
|
|
def __init__(self, root: tk.Tk, controller: Any):
|
|
self.root = root
|
|
self.controller = controller
|
|
self.root.title("Flight Monitor")
|
|
self.progress_dialog: Optional[ImportProgressDialog] = None
|
|
self.full_flight_details_window: Optional[tk.Toplevel] = None
|
|
|
|
if app_config.LAYOUT_START_MAXIMIZED:
|
|
try:
|
|
self.root.state("zoomed")
|
|
except tk.TclError:
|
|
try:
|
|
self.root.geometry(
|
|
f"{self.root.winfo_screenwidth()}x{self.root.winfo_screenheight()}+0+0"
|
|
)
|
|
except tk.TclError:
|
|
pass
|
|
|
|
min_win_w = getattr(app_config, "LAYOUT_WINDOW_MIN_WIDTH", 900)
|
|
min_win_h = getattr(app_config, "LAYOUT_WINDOW_MIN_HEIGHT", 650)
|
|
self.root.minsize(min_win_h, min_win_h) # Corrected min_win_h used twice, should be min_win_w and min_win_h
|
|
|
|
self.menubar = Menu(self.root)
|
|
self.file_menu = Menu(self.menubar, tearoff=0)
|
|
self.file_menu.add_command(
|
|
label="Import Aircraft Database (CSV)...",
|
|
command=self._import_aircraft_db_csv,
|
|
)
|
|
self.file_menu.add_separator()
|
|
self.file_menu.add_command(label="Exit", command=self._on_closing)
|
|
self.menubar.add_cascade(label="File", menu=self.file_menu)
|
|
self.root.config(menu=self.menubar)
|
|
|
|
self.main_horizontal_paned_window = ttk.PanedWindow(
|
|
self.root, orient=tk.HORIZONTAL
|
|
)
|
|
self.main_horizontal_paned_window.pack(
|
|
fill=tk.BOTH, expand=True, padx=5, pady=5
|
|
)
|
|
|
|
self.left_column_frame = ttk.Frame(self.main_horizontal_paned_window)
|
|
self.main_horizontal_paned_window.add(
|
|
self.left_column_frame,
|
|
weight=app_config.LAYOUT_MAIN_HORIZONTAL_WEIGHTS.get("left_column", 25),
|
|
)
|
|
|
|
self.left_vertical_paned_window = ttk.PanedWindow(
|
|
self.left_column_frame, orient=tk.VERTICAL
|
|
)
|
|
self.left_vertical_paned_window.pack(fill=tk.BOTH, expand=True)
|
|
|
|
self.function_notebook_frame = ttk.Frame(self.left_vertical_paned_window)
|
|
self.left_vertical_paned_window.add(
|
|
self.function_notebook_frame,
|
|
weight=app_config.LAYOUT_LEFT_VERTICAL_WEIGHTS.get("function_notebook", 65),
|
|
)
|
|
self.function_notebook_panel = FunctionNotebookPanel(
|
|
self.function_notebook_frame, self.controller
|
|
)
|
|
|
|
self.log_status_area_frame_container = ttk.Frame(self.left_vertical_paned_window)
|
|
self.left_vertical_paned_window.add(
|
|
self.log_status_area_frame_container,
|
|
weight=app_config.LAYOUT_LEFT_VERTICAL_WEIGHTS.get("log_status_area", 35),
|
|
)
|
|
self.log_status_panel = LogStatusPanel(self.log_status_area_frame_container, self.root)
|
|
|
|
self.right_column_frame = ttk.Frame(self.main_horizontal_paned_window)
|
|
self.main_horizontal_paned_window.add(
|
|
self.right_column_frame,
|
|
weight=app_config.LAYOUT_MAIN_HORIZONTAL_WEIGHTS.get("right_column", 75),
|
|
)
|
|
|
|
self.right_vertical_paned_window = ttk.PanedWindow(
|
|
self.right_column_frame, orient=tk.VERTICAL
|
|
)
|
|
self.right_vertical_paned_window.pack(fill=tk.BOTH, expand=True)
|
|
|
|
self.views_notebook_outer_frame = ttk.Frame(self.right_vertical_paned_window)
|
|
self.right_vertical_paned_window.add(
|
|
self.views_notebook_outer_frame,
|
|
weight=app_config.LAYOUT_RIGHT_VERTICAL_WEIGHTS.get("views_notebook", 80),
|
|
)
|
|
self.views_notebook_panel = ViewsNotebookPanel(self.views_notebook_outer_frame)
|
|
self.flight_canvas = self.views_notebook_panel.get_map_canvas()
|
|
self.map_manager_instance: Optional[MapCanvasManager] = None
|
|
|
|
self.map_tools_info_area_frame = ttk.Frame(
|
|
self.right_vertical_paned_window, padding=(0, 5, 0, 0)
|
|
)
|
|
self.right_vertical_paned_window.add(
|
|
self.map_tools_info_area_frame,
|
|
weight=app_config.LAYOUT_RIGHT_VERTICAL_WEIGHTS.get("map_tools_info", 20),
|
|
)
|
|
|
|
self.bottom_panel_container = ttk.Frame(self.map_tools_info_area_frame)
|
|
self.bottom_panel_container.pack(fill=tk.BOTH, expand=True)
|
|
|
|
bottom_panel_weights = getattr(
|
|
app_config,
|
|
"LAYOUT_BOTTOM_PANELS_HORIZONTAL_WEIGHTS",
|
|
{"map_tools": 1, "map_info": 2, "flight_details": 2},
|
|
)
|
|
self.bottom_panel_container.columnconfigure(
|
|
0, weight=bottom_panel_weights.get("map_tools", 1)
|
|
)
|
|
self.bottom_panel_container.columnconfigure(
|
|
1, weight=bottom_panel_weights.get("map_info", 2)
|
|
)
|
|
self.bottom_panel_container.columnconfigure(
|
|
2, weight=bottom_panel_weights.get("flight_details", 2)
|
|
)
|
|
|
|
self.map_tool_frame = ttk.LabelFrame(
|
|
self.bottom_panel_container, text="Map Tools", padding=5
|
|
)
|
|
self.map_tool_frame.grid(row=0, column=0, sticky="nsew", padx=(0, 2))
|
|
self.map_tools_panel = MapToolsPanel(self.map_tool_frame, self.controller)
|
|
|
|
self.map_info_panel_frame = ttk.LabelFrame(
|
|
self.bottom_panel_container, text="Map Information", padding=10
|
|
)
|
|
self.map_info_panel_frame.grid(row=0, column=1, sticky="nsew", padx=2)
|
|
self.map_info_panel = MapInfoPanel(self.map_info_panel_frame)
|
|
|
|
self.selected_flight_details_frame = ttk.LabelFrame(
|
|
self.bottom_panel_container, text="Selected Flight Details", padding=10
|
|
)
|
|
self.selected_flight_details_frame.grid(
|
|
row=0, column=2, sticky="nsew", padx=(2, 0)
|
|
)
|
|
self.selected_flight_details_panel = SelectedFlightDetailsPanel(
|
|
self.selected_flight_details_frame, self.controller
|
|
)
|
|
|
|
if self.log_status_panel.get_log_widget() and self.root:
|
|
add_tkinter_handler(
|
|
gui_log_widget=self.log_status_panel.get_log_widget(),
|
|
root_tk_instance_for_gui_handler=self.root,
|
|
logging_config_dict=LOGGING_CONFIG,
|
|
)
|
|
else:
|
|
module_logger.error(
|
|
"LogStatusPanel or root not available in MainWindow init for logger setup."
|
|
)
|
|
|
|
self.root.protocol("WM_DELETE_WINDOW", self._on_closing)
|
|
self.root.after(100, self._delayed_initialization)
|
|
module_logger.info(
|
|
"MainWindow basic structure initialized. Delayed init pending."
|
|
)
|
|
|
|
def _on_closing(self):
|
|
module_logger.info("Main window closing event triggered.")
|
|
user_confirmed_quit = (
|
|
messagebox.askokcancel(
|
|
"Quit", "Do you want to quit Flight Monitor?", parent=self.root
|
|
)
|
|
if self.root.winfo_exists()
|
|
else True
|
|
)
|
|
if user_confirmed_quit:
|
|
if self.controller and hasattr(self.controller, "on_application_exit"):
|
|
try:
|
|
self.controller.on_application_exit()
|
|
except Exception as e:
|
|
module_logger.error(
|
|
f"Error during controller.on_application_exit: {e}",
|
|
exc_info=True,
|
|
)
|
|
try:
|
|
shutdown_logging_system()
|
|
except Exception as e:
|
|
module_logger.error(
|
|
f"Error during shutdown_logging_system: {e}", exc_info=True
|
|
)
|
|
|
|
if hasattr(self, "root") and self.root.winfo_exists():
|
|
try:
|
|
self.root.destroy()
|
|
except Exception:
|
|
pass
|
|
else:
|
|
module_logger.info("User cancelled quit.")
|
|
|
|
def _import_aircraft_db_csv(self):
|
|
if not self.controller:
|
|
self.show_error_message(
|
|
"Controller Error", "Application controller not available."
|
|
)
|
|
module_logger.error("Controller not available for aircraft DB import.")
|
|
return
|
|
|
|
if not hasattr(
|
|
self.controller, "import_aircraft_database_from_file_with_progress"
|
|
):
|
|
self.show_error_message(
|
|
"Function Error",
|
|
"Progress import function not available in controller.",
|
|
)
|
|
module_logger.error(
|
|
"Method 'import_aircraft_database_from_file_with_progress' missing in controller."
|
|
)
|
|
return
|
|
|
|
filepath = filedialog.askopenfilename(
|
|
master=self.root,
|
|
title="Select Aircraft Database CSV File",
|
|
filetypes=(("CSV files", "*.csv"), ("All files", "*.*")),
|
|
)
|
|
if filepath:
|
|
module_logger.info(f"GUI: Selected CSV file for import: {filepath}")
|
|
|
|
if IMPORT_DIALOG_AVAILABLE and ImportProgressDialog is not None:
|
|
if self.progress_dialog and self.progress_dialog.winfo_exists():
|
|
try:
|
|
self.progress_dialog.destroy()
|
|
except tk.TclError:
|
|
module_logger.warning(
|
|
"Could not destroy previous progress dialog cleanly."
|
|
)
|
|
self.progress_dialog = ImportProgressDialog(
|
|
self.root, file_name=filepath
|
|
)
|
|
self.controller.import_aircraft_database_from_file_with_progress(
|
|
filepath, self.progress_dialog
|
|
)
|
|
else:
|
|
module_logger.warning(
|
|
"ImportProgressDialog class not available. Using basic status update for import."
|
|
)
|
|
if hasattr(
|
|
self.controller, "import_aircraft_database_from_file"
|
|
):
|
|
self.show_info_message(
|
|
"Import Started",
|
|
f"Starting import of {os.path.basename(filepath)}...\nThis might take a while. Check logs for completion.",
|
|
)
|
|
self.controller.import_aircraft_database_from_file(filepath)
|
|
else:
|
|
self.show_error_message(
|
|
"Import Error",
|
|
"Import progress UI is not available and no fallback import method found.",
|
|
)
|
|
else:
|
|
module_logger.info("GUI: CSV import cancelled by user.")
|
|
|
|
def _delayed_initialization(self):
|
|
if not self.root.winfo_exists():
|
|
module_logger.warning(
|
|
"Root window destroyed before delayed initialization."
|
|
)
|
|
return
|
|
|
|
if MAP_CANVAS_MANAGER_AVAILABLE and MapCanvasManager is not None:
|
|
default_map_bbox = {
|
|
"lat_min": app_config.DEFAULT_BBOX_LAT_MIN,
|
|
"lon_min": app_config.DEFAULT_BBOX_LON_MIN,
|
|
"lat_max": app_config.DEFAULT_BBOX_LAT_MAX,
|
|
"lon_max": app_config.DEFAULT_BBOX_LON_MAX,
|
|
}
|
|
initial_track_len = self.function_notebook_panel.get_track_length_input()
|
|
|
|
self.root.after(
|
|
200, self._initialize_map_manager, default_map_bbox, initial_track_len
|
|
)
|
|
else:
|
|
module_logger.error("MapCanvasManager class not available post-init.")
|
|
fallback_canvas_w = self.views_notebook_panel.canvas_width
|
|
fallback_canvas_h = self.views_notebook_panel.canvas_height
|
|
self.root.after(
|
|
50,
|
|
lambda w=fallback_canvas_w, h=fallback_canvas_h: self._update_map_placeholder(
|
|
"Map functionality disabled (Import Error)."
|
|
),
|
|
)
|
|
|
|
self.root.after(10, self.function_notebook_panel._on_mode_change)
|
|
module_logger.info(
|
|
"MainWindow fully initialized. Delegating initial mode setup."
|
|
)
|
|
|
|
def _initialize_map_manager(
|
|
self, initial_bbox_for_map: Dict[str, float], initial_track_length: int
|
|
):
|
|
if not MAP_CANVAS_MANAGER_AVAILABLE or MapCanvasManager is None:
|
|
self._update_map_placeholder("Map Error: Manager class missing.")
|
|
if self.controller and hasattr(self.controller, "update_general_map_info"):
|
|
self.controller.update_general_map_info()
|
|
return
|
|
|
|
current_flight_canvas = self.views_notebook_panel.get_map_canvas()
|
|
if not current_flight_canvas or not current_flight_canvas.winfo_exists():
|
|
module_logger.warning(
|
|
"Flight canvas does not exist. Cannot initialize MapCanvasManager."
|
|
)
|
|
return
|
|
|
|
canvas_w, canvas_h = (
|
|
current_flight_canvas.winfo_width(),
|
|
current_flight_canvas.winfo_height(),
|
|
)
|
|
if canvas_w <= 1:
|
|
canvas_w = self.views_notebook_panel.canvas_width
|
|
if canvas_h <= 1:
|
|
canvas_h = self.views_notebook_panel.canvas_height
|
|
|
|
if canvas_w > 1 and canvas_h > 1:
|
|
try:
|
|
self.map_manager_instance = MapCanvasManager(
|
|
app_controller=self.controller,
|
|
tk_canvas=current_flight_canvas,
|
|
initial_bbox_dict=initial_bbox_for_map,
|
|
)
|
|
if self.controller and hasattr(self.controller, "set_map_track_length"):
|
|
try:
|
|
self.controller.set_map_track_length(initial_track_length)
|
|
module_logger.info(
|
|
f"Initial map track length set to {initial_track_length} via controller."
|
|
)
|
|
except Exception as e_trk:
|
|
module_logger.error(
|
|
f"Error setting initial track length for map manager: {e_trk}"
|
|
)
|
|
else:
|
|
module_logger.warning(
|
|
"Controller or set_map_track_length not available for initial setup."
|
|
)
|
|
|
|
if self.controller and hasattr(
|
|
self.controller, "update_general_map_info"
|
|
):
|
|
self.controller.update_general_map_info()
|
|
|
|
except Exception as e_init:
|
|
module_logger.critical(
|
|
f"Failed to initialize MapCanvasManager: {e_init}", exc_info=True
|
|
)
|
|
self.show_error_message(
|
|
"Map Error", f"Could not initialize map: {e_init}"
|
|
)
|
|
self._update_map_placeholder(f"Map Error: Init failed.\n{e_init}")
|
|
if self.controller and hasattr(
|
|
self.controller, "update_general_map_info"
|
|
):
|
|
self.controller.update_general_map_info()
|
|
else:
|
|
if self.root.winfo_exists():
|
|
module_logger.info(
|
|
"Canvas not sized yet, retrying map manager initialization."
|
|
)
|
|
self.root.after(
|
|
300,
|
|
self._initialize_map_manager,
|
|
initial_bbox_for_map,
|
|
initial_track_length,
|
|
)
|
|
|
|
def _recreate_map_tools_content(self, parent_frame: ttk.Frame):
|
|
pass
|
|
|
|
def _recreate_map_info_content(self, parent_frame: ttk.Frame):
|
|
pass
|
|
|
|
def _create_selected_flight_details_content(self, parent_frame: ttk.LabelFrame):
|
|
pass
|
|
|
|
def update_semaphore_and_status(self, status_level: str, message: str):
|
|
if hasattr(self, "log_status_panel") and self.log_status_panel:
|
|
self.log_status_panel.update_status_display(status_level, message)
|
|
else:
|
|
print(f"Status update (LogStatusPanel not ready): {status_level}: {message}", flush=True)
|
|
|
|
log_level_to_use = logging.INFO
|
|
if status_level == GUI_STATUS_ERROR:
|
|
log_level_to_use = logging.ERROR
|
|
elif status_level == GUI_STATUS_PERMANENT_FAILURE:
|
|
log_level_to_use = logging.CRITICAL
|
|
elif status_level == GUI_STATUS_WARNING:
|
|
log_level_to_use = logging.WARNING
|
|
elif status_level in [GUI_STATUS_FETCHING, GUI_STATUS_OK, GUI_STATUS_UNKNOWN]:
|
|
log_level_to_use = logging.DEBUG
|
|
module_logger.log(
|
|
log_level_to_use,
|
|
f"GUI Status Update: Level='{status_level}', Message='{message}'",
|
|
)
|
|
|
|
def _reset_gui_to_stopped_state(
|
|
self, status_message: Optional[str] = "Monitoring stopped."
|
|
):
|
|
if hasattr(self, "function_notebook_panel") and self.function_notebook_panel:
|
|
self.function_notebook_panel.set_monitoring_button_states(False)
|
|
else:
|
|
if hasattr(self, "start_button") and self.start_button.winfo_exists():
|
|
self.start_button.config(state=tk.NORMAL)
|
|
if hasattr(self, "stop_button") and self.stop_button.winfo_exists():
|
|
self.stop_button.config(state=tk.DISABLED)
|
|
if hasattr(self, "live_radio") and self.live_radio.winfo_exists():
|
|
self.live_radio.config(state=tk.NORMAL)
|
|
if hasattr(self, "history_radio") and self.history_radio.winfo_exists():
|
|
self.history_radio.config(state=tk.NORMAL)
|
|
|
|
if hasattr(self, "function_notebook_panel") and self.function_notebook_panel:
|
|
self.function_notebook_panel._update_internal_controls_state()
|
|
|
|
status_level = GUI_STATUS_OK
|
|
if status_message and (
|
|
"failed" in status_message.lower() or "error" in status_message.lower()
|
|
):
|
|
status_level = GUI_STATUS_ERROR
|
|
elif status_message and "warning" in status_message.lower():
|
|
status_level = GUI_STATUS_WARNING
|
|
|
|
if hasattr(self, "root") and self.root.winfo_exists():
|
|
self.update_semaphore_and_status(
|
|
status_level, status_message if status_message else "System Ready."
|
|
)
|
|
|
|
def _should_show_main_placeholder(self) -> bool:
|
|
current_flight_canvas = self.views_notebook_panel.get_map_canvas() if hasattr(self, "views_notebook_panel") else None
|
|
return not (
|
|
current_flight_canvas
|
|
and hasattr(self, "map_manager_instance")
|
|
and self.map_manager_instance is not None
|
|
and MAP_CANVAS_MANAGER_AVAILABLE
|
|
)
|
|
|
|
def _update_map_placeholder(self, text_to_display: str):
|
|
current_flight_canvas = self.views_notebook_panel.get_map_canvas() if hasattr(self, "views_notebook_panel") else None
|
|
|
|
if not (
|
|
current_flight_canvas
|
|
and current_flight_canvas.winfo_exists()
|
|
):
|
|
return
|
|
|
|
if (
|
|
not self._should_show_main_placeholder()
|
|
):
|
|
try:
|
|
current_flight_canvas.delete("placeholder_text")
|
|
except:
|
|
pass
|
|
return
|
|
|
|
try:
|
|
current_flight_canvas.delete("placeholder_text")
|
|
canvas_w, canvas_h = (
|
|
current_flight_canvas.winfo_width(),
|
|
current_flight_canvas.winfo_height(),
|
|
)
|
|
if canvas_w <= 1: canvas_w = self.views_notebook_panel.canvas_width
|
|
if canvas_h <= 1: canvas_h = self.views_notebook_panel.canvas_height
|
|
|
|
if canvas_w > 1 and canvas_h > 1:
|
|
current_flight_canvas.create_text(
|
|
canvas_w / 2,
|
|
canvas_h / 2,
|
|
text=text_to_display,
|
|
tags="placeholder_text",
|
|
fill="gray50",
|
|
font=("Arial", 12, "italic"),
|
|
justify=tk.CENTER,
|
|
width=canvas_w - 40,
|
|
)
|
|
except tk.TclError:
|
|
pass
|
|
except Exception as e_placeholder_draw:
|
|
module_logger.error(f"Error drawing placeholder: {e_placeholder_draw}")
|
|
|
|
def _on_mode_change(self):
|
|
pass
|
|
|
|
def _on_function_tab_change(self, event: Optional[tk.Event] = None):
|
|
pass
|
|
|
|
def _update_controls_state_based_on_mode_and_tab(self):
|
|
pass
|
|
|
|
def _set_bbox_entries_state(self, state: str):
|
|
pass
|
|
|
|
def _start_monitoring(self):
|
|
pass
|
|
|
|
def _stop_monitoring(self):
|
|
pass
|
|
|
|
def get_bounding_box_from_gui(self) -> Optional[Dict[str, float]]:
|
|
if hasattr(self, "function_notebook_panel") and self.function_notebook_panel:
|
|
return self.function_notebook_panel.get_bounding_box_input()
|
|
else:
|
|
module_logger.error("FunctionNotebookPanel not initialized for get_bounding_box_from_gui.")
|
|
return None
|
|
|
|
def update_bbox_gui_fields(self, bbox_dict: Dict[str, float]):
|
|
if hasattr(self, "function_notebook_panel") and self.function_notebook_panel:
|
|
self.function_notebook_panel.update_bbox_gui_fields(bbox_dict)
|
|
else:
|
|
module_logger.warning("FunctionNotebookPanel not initialized for update_bbox_gui_fields.")
|
|
|
|
def display_flights_on_canvas(
|
|
self,
|
|
flight_states: List[CanonicalFlightState],
|
|
_active_bbox_context: Optional[
|
|
Dict[str, float]
|
|
],
|
|
):
|
|
current_flight_canvas = self.views_notebook_panel.get_map_canvas() if hasattr(self, "views_notebook_panel") else None
|
|
|
|
if not (
|
|
current_flight_canvas
|
|
and hasattr(self, "map_manager_instance")
|
|
and self.map_manager_instance is not None
|
|
and MAP_CANVAS_MANAGER_AVAILABLE
|
|
):
|
|
if hasattr(self, "root") and self.root.winfo_exists():
|
|
self._update_map_placeholder("Map N/A to display flights.")
|
|
return
|
|
|
|
try:
|
|
self.map_manager_instance.update_flights_on_map(flight_states)
|
|
if (
|
|
not flight_states and not self._should_show_main_placeholder()
|
|
):
|
|
pass
|
|
elif (
|
|
not flight_states and self._should_show_main_placeholder()
|
|
):
|
|
self._update_map_placeholder("No flights in the selected area.")
|
|
|
|
except Exception as e:
|
|
module_logger.error(
|
|
f"Error in display_flights_on_canvas: {e}", exc_info=True
|
|
)
|
|
self.show_error_message(
|
|
"Map Display Error", "Could not update flights on map."
|
|
)
|
|
|
|
def clear_all_views_data(self):
|
|
module_logger.info("GUI: Clearing all views data.")
|
|
if (
|
|
hasattr(self, "map_manager_instance")
|
|
and self.map_manager_instance
|
|
and MAP_CANVAS_MANAGER_AVAILABLE
|
|
):
|
|
try:
|
|
self.map_manager_instance.clear_map_display()
|
|
except Exception as e:
|
|
module_logger.warning(f"Error clearing map via map manager: {e}")
|
|
elif (
|
|
self._should_show_main_placeholder()
|
|
):
|
|
mode = self.function_notebook_panel.get_selected_mode() if hasattr(self, "function_notebook_panel") else "Unknown"
|
|
active_func_tab_text = self.function_notebook_panel.get_active_function_tab_text() if hasattr(self, "function_notebook_panel") else ""
|
|
|
|
text = f"Map - {mode}. Data cleared."
|
|
if mode == "Live" and "Live: Area Monitor" in active_func_tab_text:
|
|
text = "Map - Live. Define area and Start."
|
|
elif mode == "History" and "History" in active_func_tab_text:
|
|
text = "Map - History. (Coming Soon)"
|
|
elif mode == "Live" and "Live: Airport" in active_func_tab_text:
|
|
text = "Map - Live Airport. (Coming Soon)"
|
|
self._update_map_placeholder(text)
|
|
|
|
if hasattr(self, "selected_flight_details_panel"):
|
|
self.selected_flight_details_panel.update_details(None)
|
|
else:
|
|
module_logger.warning("SelectedFlightDetailsPanel not initialized for clearing.")
|
|
|
|
|
|
# MODIFIED: Corrected parameter passing
|
|
# WHY: The 'flight_data' parameter received by this method was not being passed
|
|
# to the delegated call on `selected_flight_details_panel.update_details`.
|
|
# HOW: Changed `self.selected_flight_details_panel.update_details(None)` to
|
|
# `self.selected_flight_details_panel.update_details(flight_data)`.
|
|
def update_selected_flight_details(self, flight_data: Optional[Dict[str, Any]]):
|
|
"""
|
|
Delegates the update of selected flight details to the SelectedFlightDetailsPanel.
|
|
|
|
Args:
|
|
flight_data: A dictionary containing flight information.
|
|
"""
|
|
if hasattr(self, "selected_flight_details_panel") and self.selected_flight_details_panel:
|
|
self.selected_flight_details_panel.update_details(flight_data) # FIX APPLIED HERE
|
|
else:
|
|
module_logger.warning("SelectedFlightDetailsPanel not initialized for updating.")
|
|
|
|
|
|
def show_error_message(self, title: str, message: str):
|
|
status_msg = f"Error: {message[:70]}{'...' if len(message)>70 else ''}"
|
|
if hasattr(self, "root") and self.root.winfo_exists():
|
|
self.update_semaphore_and_status(GUI_STATUS_ERROR, status_msg)
|
|
try:
|
|
messagebox.showerror(title, message, parent=self.root)
|
|
except tk.TclError:
|
|
pass
|
|
else:
|
|
print(f"ERROR (No GUI messagebox): {title} - {message}", flush=True)
|
|
|
|
def show_info_message(self, title: str, message: str):
|
|
if hasattr(self, "root") and self.root.winfo_exists():
|
|
try:
|
|
messagebox.showinfo(title, message, parent=self.root)
|
|
except tk.TclError:
|
|
pass
|
|
else:
|
|
print(f"INFO (No GUI messagebox): {title} - {message}", flush=True)
|
|
|
|
def show_map_context_menu(
|
|
self, latitude: float, longitude: float, screen_x: int, screen_y: int
|
|
):
|
|
if (
|
|
hasattr(self, "map_manager_instance")
|
|
and self.map_manager_instance
|
|
and hasattr(self.map_manager_instance, "show_map_context_menu_from_gui")
|
|
and MAP_CANVAS_MANAGER_AVAILABLE
|
|
):
|
|
try:
|
|
self.map_manager_instance.show_map_context_menu_from_gui(
|
|
latitude, longitude, screen_x, screen_y
|
|
)
|
|
except Exception as e:
|
|
module_logger.error(
|
|
f"Error delegating context menu to MapCanvasManager: {e}",
|
|
exc_info=True,
|
|
)
|
|
else:
|
|
module_logger.warning(
|
|
"Map manager or context menu method not available for GUI."
|
|
)
|
|
|
|
def update_clicked_map_info(
|
|
self,
|
|
lat_deg: Optional[float],
|
|
lon_deg: Optional[float],
|
|
lat_dms: str,
|
|
lon_dms: str,
|
|
):
|
|
if hasattr(self, "map_info_panel") and self.map_info_panel:
|
|
self.map_info_panel.update_clicked_map_info(lat_deg, lon_deg, lat_dms, lon_dms)
|
|
else:
|
|
module_logger.warning("MapInfoPanel not initialized for update_clicked_map_info.")
|
|
|
|
def update_general_map_info_display(
|
|
self,
|
|
zoom: Optional[int],
|
|
map_size_str: str,
|
|
map_geo_bounds: Optional[Tuple[float, float, float, float]],
|
|
target_bbox_input: Optional[Dict[str, float]],
|
|
flight_count: Optional[int],
|
|
):
|
|
if hasattr(self, "map_info_panel") and self.map_info_panel:
|
|
self.map_info_panel.update_general_map_info_display(
|
|
zoom, map_size_str, map_geo_bounds, target_bbox_input, flight_count
|
|
)
|
|
else:
|
|
module_logger.warning("MapInfoPanel not initialized for update_general_map_info_display.")
|
|
|
|
def _on_track_length_change(self):
|
|
pass |