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

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