add profiling area bbox
This commit is contained in:
parent
953c02d879
commit
6fa4928b0c
1
config/area_profiles.json
Normal file
1
config/area_profiles.json
Normal file
@ -0,0 +1 @@
|
||||
{}
|
||||
@ -1,6 +1,7 @@
|
||||
# FlightMonitor/controller/app_controller.py
|
||||
from queue import Queue, Empty as QueueEmpty
|
||||
from typing import List, Optional, Dict, Any, TYPE_CHECKING
|
||||
from tkinter import simpledialog
|
||||
|
||||
from ..data.opensky_live_adapter import OpenSkyLiveAdapter, AdapterMessage
|
||||
from ..data import config as app_config
|
||||
@ -12,6 +13,7 @@ from .aircraft_db_importer import AircraftDBImporter
|
||||
from .live_data_processor import LiveDataProcessor
|
||||
from .map_command_handler import MapCommandHandler
|
||||
from .cleanup_manager import CleanupManager
|
||||
from ..data.area_profile_manager import AreaProfileManager
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..gui.main_window import MainWindow
|
||||
@ -41,6 +43,7 @@ class AppController:
|
||||
self.live_data_processor: Optional[LiveDataProcessor] = None
|
||||
self.map_command_handler: Optional[MapCommandHandler] = None
|
||||
self.cleanup_manager: Optional[CleanupManager] = None
|
||||
self.profile_manager: Optional[AreaProfileManager] = None
|
||||
|
||||
self.active_detail_window_icao: Optional[str] = None
|
||||
self.active_detail_window_ref: Optional["FullFlightDetailsWindow"] = None # type: ignore
|
||||
@ -59,6 +62,13 @@ class AppController:
|
||||
module_logger.critical(f"CRITICAL: Failed to initialize AircraftDatabaseManager: {e}", exc_info=True)
|
||||
self.aircraft_db_manager = None
|
||||
|
||||
try:
|
||||
self.profile_manager = AreaProfileManager() # NUOVA RIGA
|
||||
module_logger.info("AreaProfileManager initialized successfully.")
|
||||
except Exception as e:
|
||||
module_logger.error(f"Failed to initialize AreaProfileManager: {e}", exc_info=True)
|
||||
|
||||
|
||||
module_logger.info("AppController initialized.")
|
||||
|
||||
def set_main_window(self, main_window_instance: "MainWindow"):
|
||||
@ -450,3 +460,76 @@ class AppController:
|
||||
self.main_window.map_manager_instance.set_max_track_points(length)
|
||||
else:
|
||||
module_logger.warning("Controller: Cannot set track length, map manager not available.")
|
||||
|
||||
def get_profile_names(self) -> List[str]:
|
||||
"""Returns the list of available profile names."""
|
||||
if self.profile_manager:
|
||||
return self.profile_manager.get_profile_names()
|
||||
return []
|
||||
|
||||
def load_area_profile(self, profile_name: str):
|
||||
"""Loads a profile and updates the GUI and map."""
|
||||
if not self.profile_manager:
|
||||
module_logger.error("Profile manager not available.")
|
||||
return
|
||||
|
||||
profile_data = self.profile_manager.get_profile_data(profile_name)
|
||||
if profile_data:
|
||||
module_logger.info(f"Loading profile: '{profile_name}'")
|
||||
# Aggiorna i campi della GUI
|
||||
if self.main_window:
|
||||
self.main_window.update_bbox_gui_fields(profile_data)
|
||||
|
||||
# Aggiorna la mappa
|
||||
if self.main_window and self.main_window.map_manager_instance:
|
||||
self.main_window.map_manager_instance.set_target_bbox(profile_data)
|
||||
else:
|
||||
module_logger.warning(f"Profile '{profile_name}' not found.")
|
||||
|
||||
def save_current_area_as_profile(self):
|
||||
"""Saves the current BBox values as a new profile."""
|
||||
if not self.profile_manager or not self.main_window:
|
||||
module_logger.error("Profile manager or main window not available.")
|
||||
return
|
||||
|
||||
current_bbox = self.main_window.get_bounding_box_from_gui()
|
||||
if not current_bbox:
|
||||
self.main_window.show_error_message("Save Error", "The current Bounding Box values are invalid.")
|
||||
return
|
||||
|
||||
# Chiedi all'utente un nome per il nuovo profilo
|
||||
profile_name = simpledialog.askstring(
|
||||
"Save Profile", "Enter a name for the new area profile:",
|
||||
parent=self.main_window.root
|
||||
)
|
||||
|
||||
if not profile_name or not profile_name.strip():
|
||||
module_logger.info("Profile save cancelled by user.")
|
||||
return
|
||||
|
||||
profile_name = profile_name.strip()
|
||||
|
||||
if self.profile_manager.save_profile(profile_name, current_bbox):
|
||||
module_logger.info(f"Successfully saved profile '{profile_name}'.")
|
||||
# Aggiorna la ComboBox nella GUI
|
||||
if hasattr(self.main_window, "area_management_panel"):
|
||||
self.main_window.area_management_panel.update_profile_list()
|
||||
self.main_window.area_management_panel.set_selected_profile(profile_name)
|
||||
self.main_window.show_info_message("Success", f"Profile '{profile_name}' saved successfully.")
|
||||
else:
|
||||
self.main_window.show_error_message("Save Error", f"Could not save profile '{profile_name}'. The name might be invalid or reserved.")
|
||||
|
||||
def delete_area_profile(self, profile_name: str):
|
||||
"""Deletes a user-defined profile."""
|
||||
if not self.profile_manager or not self.main_window:
|
||||
module_logger.error("Profile manager or main window not available.")
|
||||
return
|
||||
|
||||
if self.profile_manager.delete_profile(profile_name):
|
||||
module_logger.info(f"Successfully deleted profile '{profile_name}'.")
|
||||
# Aggiorna la ComboBox nella GUI
|
||||
if hasattr(self.main_window, "area_management_panel"):
|
||||
self.main_window.area_management_panel.update_profile_list()
|
||||
self.main_window.show_info_message("Success", f"Profile '{profile_name}' deleted.")
|
||||
else:
|
||||
self.main_window.show_error_message("Delete Error", f"Could not delete profile '{profile_name}'. It might be the default profile or not exist.")
|
||||
114
flightmonitor/data/area_profile_manager.py
Normal file
114
flightmonitor/data/area_profile_manager.py
Normal file
@ -0,0 +1,114 @@
|
||||
# FlightMonitor/data/area_profile_manager.py
|
||||
"""
|
||||
Manages loading, saving, and handling of geographic area profiles.
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
from typing import Dict, Optional, List
|
||||
|
||||
from ..utils.logger import get_logger
|
||||
from . import config as app_config
|
||||
|
||||
module_logger = get_logger(__name__)
|
||||
|
||||
PROFILE_FILENAME = "area_profiles.json"
|
||||
DEFAULT_PROFILE_NAME = "Default Zone"
|
||||
|
||||
class AreaProfileManager:
|
||||
"""
|
||||
Handles reading from and writing to the area profiles JSON file.
|
||||
"""
|
||||
def __init__(self, profiles_directory: str = "config"):
|
||||
"""
|
||||
Initializes the AreaProfileManager.
|
||||
|
||||
Args:
|
||||
profiles_directory: The directory where the profiles file will be stored.
|
||||
"""
|
||||
self.profiles_path = os.path.join(os.path.abspath(profiles_directory), PROFILE_FILENAME)
|
||||
self._profiles: Dict[str, Dict[str, float]] = {}
|
||||
self._load_profiles()
|
||||
|
||||
def _create_default_profile(self) -> Dict[str, float]:
|
||||
"""Creates the default profile from the application config."""
|
||||
return {
|
||||
"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,
|
||||
}
|
||||
|
||||
def _load_profiles(self):
|
||||
"""Loads profiles from the JSON file and ensures the default profile exists."""
|
||||
# Start with the mandatory default profile
|
||||
self._profiles = {DEFAULT_PROFILE_NAME: self._create_default_profile()}
|
||||
|
||||
if os.path.exists(self.profiles_path):
|
||||
try:
|
||||
with open(self.profiles_path, "r") as f:
|
||||
user_profiles = json.load(f)
|
||||
if isinstance(user_profiles, dict):
|
||||
# Merge user profiles, default profile cannot be overwritten
|
||||
for name, data in user_profiles.items():
|
||||
if name != DEFAULT_PROFILE_NAME:
|
||||
self._profiles[name] = data
|
||||
module_logger.info(f"Loaded {len(user_profiles)} user profiles from {self.profiles_path}")
|
||||
except (json.JSONDecodeError, IOError) as e:
|
||||
module_logger.error(f"Error loading profiles from {self.profiles_path}: {e}")
|
||||
else:
|
||||
module_logger.info("Area profiles file not found. Starting with default profile.")
|
||||
# We can optionally save the default profile to create the file on first run
|
||||
self._save_profiles()
|
||||
|
||||
def _save_profiles(self):
|
||||
"""Saves the current user-defined profiles to the JSON file."""
|
||||
# Ensure the directory exists
|
||||
try:
|
||||
os.makedirs(os.path.dirname(self.profiles_path), exist_ok=True)
|
||||
except OSError as e:
|
||||
module_logger.error(f"Could not create directory for profiles file: {e}")
|
||||
return
|
||||
|
||||
# Prepare user-defined profiles for saving (exclude the default)
|
||||
profiles_to_save = {
|
||||
name: data for name, data in self._profiles.items() if name != DEFAULT_PROFILE_NAME
|
||||
}
|
||||
|
||||
try:
|
||||
with open(self.profiles_path, "w") as f:
|
||||
json.dump(profiles_to_save, f, indent=4)
|
||||
module_logger.info(f"Successfully saved {len(profiles_to_save)} profiles to {self.profiles_path}")
|
||||
except IOError as e:
|
||||
module_logger.error(f"Error saving profiles to {self.profiles_path}: {e}")
|
||||
|
||||
def get_profile_names(self) -> List[str]:
|
||||
"""Returns a sorted list of all available profile names."""
|
||||
return sorted(self._profiles.keys())
|
||||
|
||||
def get_profile_data(self, name: str) -> Optional[Dict[str, float]]:
|
||||
"""Returns the BBox data for a given profile name."""
|
||||
return self._profiles.get(name)
|
||||
|
||||
def save_profile(self, name: str, data: Dict[str, float]) -> bool:
|
||||
"""
|
||||
Saves a new or updated profile. Returns False if name is invalid or default.
|
||||
"""
|
||||
if not name or name == DEFAULT_PROFILE_NAME:
|
||||
module_logger.warning(f"Attempt to save invalid or default profile name: '{name}'")
|
||||
return False
|
||||
|
||||
self._profiles[name] = data
|
||||
self._save_profiles()
|
||||
return True
|
||||
|
||||
def delete_profile(self, name: str) -> bool:
|
||||
"""Deletes a profile. Returns False if the profile is default or not found."""
|
||||
if name == DEFAULT_PROFILE_NAME:
|
||||
module_logger.warning("Cannot delete the default profile.")
|
||||
return False
|
||||
if name in self._profiles:
|
||||
del self._profiles[name]
|
||||
self._save_profiles()
|
||||
return True
|
||||
module_logger.warning(f"Profile '{name}' not found for deletion.")
|
||||
return False
|
||||
@ -5,10 +5,8 @@ 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, messagebox, filedialog, Menu, font as tkFont
|
||||
from typing import List, Dict, Optional, Any, Tuple, TYPE_CHECKING
|
||||
|
||||
import os
|
||||
from tkinter import ttk, messagebox, filedialog, Menu
|
||||
from typing import List, Dict, Optional, Any, Tuple
|
||||
|
||||
from ..data import config as app_config
|
||||
from ..utils.logger import get_logger, add_tkinter_handler, shutdown_logging_system
|
||||
@ -20,25 +18,23 @@ from .panels.map_tools_panel import MapToolsPanel
|
||||
from .panels.map_info_panel import MapInfoPanel
|
||||
from .panels.selected_flight_details_panel import SelectedFlightDetailsPanel
|
||||
from .panels.views_notebook_panel import ViewsNotebookPanel
|
||||
# New and updated imports
|
||||
from .panels.function_notebook_panel import FunctionNotebookPanel
|
||||
from .panels.area_management_panel import AreaManagementPanel # New panel
|
||||
|
||||
try:
|
||||
from ..map.map_canvas_manager import MapCanvasManager
|
||||
MAP_CANVAS_MANAGER_AVAILABLE = True
|
||||
except ImportError:
|
||||
except ImportError as e_map_import:
|
||||
MapCanvasManager = None
|
||||
MAP_CANVAS_MANAGER_AVAILABLE = False
|
||||
print("CRITICAL ERROR in MainWindow: Failed to import MapCanvasManager.", flush=True)
|
||||
print(f"CRITICAL ERROR in MainWindow: Failed to import MapCanvasManager: {e_map_import}.", flush=True)
|
||||
|
||||
try:
|
||||
from .dialogs.import_progress_dialog import ImportProgressDialog
|
||||
IMPORT_DIALOG_AVAILABLE = True
|
||||
except ImportError:
|
||||
except ImportError as e_dialog_import:
|
||||
ImportProgressDialog = None
|
||||
IMPORT_DIALOG_AVAILABLE = False
|
||||
print("ERROR in MainWindow: Failed to import ImportProgressDialog.", flush=True)
|
||||
print(f"ERROR in MainWindow: Failed to import ImportProgressDialog: {e_dialog_import}.", flush=True)
|
||||
|
||||
module_logger = get_logger(__name__)
|
||||
|
||||
@ -59,15 +55,14 @@ class MainWindow:
|
||||
except tk.TclError:
|
||||
pass
|
||||
self.root.minsize(
|
||||
getattr(app_config, "LAYOUT_WINDOW_MIN_WIDTH", 900),
|
||||
getattr(app_config, "LAYOUT_WINDOW_MIN_HEIGHT", 650)
|
||||
app_config.LAYOUT_WINDOW_MIN_WIDTH,
|
||||
app_config.LAYOUT_WINDOW_MIN_HEIGHT
|
||||
)
|
||||
|
||||
self._create_menu()
|
||||
self._create_main_layout()
|
||||
self._create_panels()
|
||||
|
||||
# Setup logging to the GUI widget
|
||||
if self.log_status_panel.get_log_widget() and self.root:
|
||||
add_tkinter_handler(
|
||||
gui_log_widget=self.log_status_panel.get_log_widget(),
|
||||
@ -94,53 +89,37 @@ class MainWindow:
|
||||
self.root.config(menu=menubar)
|
||||
|
||||
def _create_main_layout(self):
|
||||
# Main horizontal division
|
||||
main_hpane = ttk.PanedWindow(self.root, orient=tk.HORIZONTAL)
|
||||
main_hpane.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
|
||||
|
||||
# Left column
|
||||
left_column_frame = ttk.Frame(main_hpane)
|
||||
main_hpane.add(left_column_frame, weight=app_config.LAYOUT_MAIN_HORIZONTAL_WEIGHTS.get("left_column", 25))
|
||||
main_hpane.add(left_column_frame, weight=app_config.LAYOUT_MAIN_HORIZONTAL_WEIGHTS["left_column"])
|
||||
|
||||
# Right column
|
||||
right_column_frame = ttk.Frame(main_hpane)
|
||||
main_hpane.add(right_column_frame, weight=app_config.LAYOUT_MAIN_HORIZONTAL_WEIGHTS.get("right_column", 75))
|
||||
main_hpane.add(right_column_frame, weight=app_config.LAYOUT_MAIN_HORIZONTAL_WEIGHTS["right_column"])
|
||||
|
||||
# --- Configure Left Column ---
|
||||
left_column_frame.rowconfigure(0, weight=3) # Area for BBox and Profiles
|
||||
left_column_frame.rowconfigure(1, weight=3) # Area for Function Notebook
|
||||
left_column_frame.rowconfigure(2, weight=4) # Area for Log/Status
|
||||
left_column_frame.columnconfigure(0, weight=1)
|
||||
left_vpane = ttk.PanedWindow(left_column_frame, orient=tk.VERTICAL)
|
||||
left_vpane.pack(fill=tk.BOTH, expand=True)
|
||||
|
||||
self.area_management_frame = ttk.Frame(left_column_frame)
|
||||
self.area_management_frame.grid(row=0, column=0, sticky="nsew", pady=(0, 5))
|
||||
self.function_and_area_frame = ttk.Frame(left_vpane)
|
||||
left_vpane.add(self.function_and_area_frame, weight=app_config.LAYOUT_LEFT_VERTICAL_WEIGHTS["function_notebook"])
|
||||
|
||||
self.function_notebook_frame = ttk.Frame(left_column_frame)
|
||||
self.function_notebook_frame.grid(row=1, column=0, sticky="nsew")
|
||||
self.log_status_area_frame_container = ttk.Frame(left_vpane)
|
||||
left_vpane.add(self.log_status_area_frame_container, weight=app_config.LAYOUT_LEFT_VERTICAL_WEIGHTS["log_status_area"])
|
||||
|
||||
self.log_status_area_frame_container = ttk.Frame(left_column_frame, padding=(0,5,0,0))
|
||||
self.log_status_area_frame_container.grid(row=2, column=0, sticky="nsew")
|
||||
|
||||
# --- Configure Right Column ---
|
||||
right_vpane = ttk.PanedWindow(right_column_frame, orient=tk.VERTICAL)
|
||||
right_vpane.pack(fill=tk.BOTH, expand=True)
|
||||
|
||||
self.views_notebook_outer_frame = ttk.Frame(right_vpane)
|
||||
right_vpane.add(self.views_notebook_outer_frame, weight=app_config.LAYOUT_RIGHT_VERTICAL_WEIGHTS.get("views_notebook", 80))
|
||||
|
||||
self.map_tools_info_area_frame = ttk.Frame(right_vpane, padding=(0, 5, 0, 0))
|
||||
right_vpane.add(self.map_tools_info_area_frame, weight=app_config.LAYOUT_RIGHT_VERTICAL_WEIGHTS.get("map_tools_info", 20))
|
||||
right_vpane.add(self.views_notebook_outer_frame, weight=app_config.LAYOUT_RIGHT_VERTICAL_WEIGHTS["views_notebook"])
|
||||
self.map_tools_info_area_frame = ttk.Frame(right_vpane)
|
||||
right_vpane.add(self.map_tools_info_area_frame, weight=app_config.LAYOUT_RIGHT_VERTICAL_WEIGHTS["map_tools_info"])
|
||||
|
||||
def _create_panels(self):
|
||||
# Left column panels
|
||||
self.area_management_panel = AreaManagementPanel(self.area_management_frame, self.controller)
|
||||
self.function_notebook_panel = FunctionNotebookPanel(self.function_notebook_frame, self.controller)
|
||||
self.function_notebook_panel = FunctionNotebookPanel(self.function_and_area_frame, self.controller)
|
||||
self.log_status_panel = LogStatusPanel(self.log_status_area_frame_container, self.root)
|
||||
|
||||
# Right column panels
|
||||
self.views_notebook_panel = ViewsNotebookPanel(self.views_notebook_outer_frame)
|
||||
|
||||
# Bottom panels container
|
||||
bottom_panel_container = ttk.Frame(self.map_tools_info_area_frame)
|
||||
bottom_panel_container.pack(fill=tk.BOTH, expand=True)
|
||||
bottom_weights = app_config.LAYOUT_BOTTOM_PANELS_HORIZONTAL_WEIGHTS
|
||||
@ -160,37 +139,29 @@ class MainWindow:
|
||||
selected_flight_details_frame.grid(row=0, column=2, sticky="nsew", padx=(2, 0))
|
||||
self.selected_flight_details_panel = SelectedFlightDetailsPanel(selected_flight_details_frame, self.controller)
|
||||
|
||||
# --- Public Methods and Callbacks ---
|
||||
|
||||
def get_bounding_box_from_gui(self) -> Optional[Dict[str, float]]:
|
||||
"""Retrieves the bounding box from the dedicated AreaManagementPanel."""
|
||||
return self.area_management_panel.get_bounding_box_input()
|
||||
return self.function_notebook_panel.get_bounding_box_input()
|
||||
|
||||
def update_bbox_gui_fields(self, bbox_dict: Dict[str, float]):
|
||||
"""Delegates updating BBox GUI fields to the AreaManagementPanel."""
|
||||
self.area_management_panel.update_bbox_gui_fields(bbox_dict)
|
||||
self.function_notebook_panel.update_bbox_gui_fields(bbox_dict)
|
||||
|
||||
def set_monitoring_button_states(self, is_monitoring_active: bool):
|
||||
"""Delegates setting the state of monitoring buttons."""
|
||||
self.function_notebook_panel.set_monitoring_button_states(is_monitoring_active)
|
||||
self.area_management_panel.set_controls_state(not is_monitoring_active)
|
||||
self.function_notebook_panel.set_controls_state(not is_monitoring_active)
|
||||
|
||||
def update_semaphore_and_status(self, status_level: str, message: str):
|
||||
self.log_status_panel.update_status_display(status_level, message)
|
||||
|
||||
def _reset_gui_to_stopped_state(self, status_message: Optional[str] = "Monitoring stopped."):
|
||||
"""Resets the GUI to its initial, non-monitoring state."""
|
||||
self.set_monitoring_button_states(False)
|
||||
status_level = GUI_STATUS_ERROR if "error" in status_message.lower() else GUI_STATUS_OK
|
||||
self.update_semaphore_and_status(status_level, status_message)
|
||||
|
||||
def _on_closing(self):
|
||||
if messagebox.askokcancel("Quit", "Do you want to quit Flight Monitor?", parent=self.root):
|
||||
if self.controller and hasattr(self.controller, "on_application_exit"):
|
||||
self.controller.on_application_exit()
|
||||
if self.controller: self.controller.on_application_exit()
|
||||
shutdown_logging_system()
|
||||
if self.root and self.root.winfo_exists():
|
||||
self.root.destroy()
|
||||
if self.root: self.root.destroy()
|
||||
|
||||
def _import_aircraft_db_csv(self):
|
||||
if not (self.controller and hasattr(self.controller, "import_aircraft_database_from_file_with_progress")):
|
||||
@ -203,22 +174,18 @@ class MainWindow:
|
||||
)
|
||||
if filepath:
|
||||
if IMPORT_DIALOG_AVAILABLE and ImportProgressDialog:
|
||||
if self.progress_dialog and self.progress_dialog.winfo_exists():
|
||||
self.progress_dialog.destroy()
|
||||
self.progress_dialog = ImportProgressDialog(self.root, file_name=filepath)
|
||||
self.controller.import_aircraft_database_from_file_with_progress(
|
||||
filepath, self.progress_dialog
|
||||
)
|
||||
else:
|
||||
self.show_info_message("Import Started", "Importing in background. Check logs for completion.")
|
||||
# Fallback to a non-progress version if you have one
|
||||
# self.controller.import_aircraft_database_from_file(filepath)
|
||||
|
||||
# Other methods (show_error_message, update_selected_flight_details, etc.) remain mostly the same
|
||||
# But now they delegate to the appropriate sub-panel if necessary.
|
||||
|
||||
def show_error_message(self, title: str, message: str):
|
||||
if self.root.winfo_exists():
|
||||
messagebox.showerror(title, message, parent=self.root)
|
||||
self.update_semaphore_and_status(GUI_STATUS_ERROR, message)
|
||||
|
||||
def show_info_message(self, title: str, message: str):
|
||||
if self.root.winfo_exists():
|
||||
@ -238,20 +205,18 @@ class MainWindow:
|
||||
def _delayed_initialization(self):
|
||||
if not self.root.winfo_exists(): return
|
||||
|
||||
if MAP_CANVAS_MANAGER_AVAILABLE and MapCanvasManager:
|
||||
default_map_bbox = self.get_bounding_box_from_gui()
|
||||
# Initial track length is now handled by map_canvas_manager default
|
||||
self.root.after(200, self._initialize_map_manager, default_map_bbox)
|
||||
if MAP_CANVAS_MANAGER_AVAILABLE:
|
||||
initial_bbox = self.get_bounding_box_from_gui()
|
||||
self.root.after(200, self._initialize_map_manager, initial_bbox)
|
||||
else:
|
||||
self._update_map_placeholder("Map functionality disabled (Import Error).")
|
||||
|
||||
self.function_notebook_panel._on_tab_change() # Trigger initial state
|
||||
self.function_notebook_panel._on_tab_change()
|
||||
module_logger.info("MainWindow fully initialized.")
|
||||
|
||||
def _initialize_map_manager(self, initial_bbox_for_map: Optional[Dict[str, float]]):
|
||||
flight_canvas = self.views_notebook_panel.get_map_canvas()
|
||||
if not (flight_canvas and flight_canvas.winfo_exists()):
|
||||
module_logger.warning("Flight canvas not available for map manager init.")
|
||||
return
|
||||
|
||||
canvas_w, canvas_h = flight_canvas.winfo_width(), flight_canvas.winfo_height()
|
||||
@ -275,8 +240,7 @@ class MainWindow:
|
||||
flight_canvas = self.views_notebook_panel.get_map_canvas()
|
||||
if not (flight_canvas and flight_canvas.winfo_exists()): return
|
||||
|
||||
# Check if map manager is active. If so, don't show placeholder.
|
||||
if self.map_manager_instance:
|
||||
if self.map_manager_instance and not self.map_manager_instance.is_detail_map:
|
||||
flight_canvas.delete("placeholder_text")
|
||||
return
|
||||
|
||||
@ -290,24 +254,11 @@ class MainWindow:
|
||||
except tk.TclError:
|
||||
pass
|
||||
|
||||
# Pass-through methods for map info updates
|
||||
def update_clicked_map_info(self, lat_deg, lon_deg, lat_dms, lon_dms):
|
||||
self.map_info_panel.update_clicked_map_info(lat_deg, lon_deg, lat_dms, lon_dms)
|
||||
|
||||
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]], # Aggiunto il parametro mancante
|
||||
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 update_general_map_info_display(self, zoom, map_size_str, map_geo_bounds, target_bbox_input, flight_count):
|
||||
self.map_info_panel.update_general_map_info_display(zoom, map_size_str, map_geo_bounds, target_bbox_input, flight_count)
|
||||
|
||||
def show_map_context_menu(self, latitude, longitude, screen_x, screen_y):
|
||||
if self.map_manager_instance:
|
||||
|
||||
@ -1,123 +0,0 @@
|
||||
# FlightMonitor/gui/panels/area_management_panel.py
|
||||
"""
|
||||
Panel for managing the geographic area of interest (Bounding Box)
|
||||
and area profiles.
|
||||
"""
|
||||
import tkinter as tk
|
||||
from tkinter import ttk
|
||||
from typing import Dict, Any, Optional
|
||||
|
||||
from ...utils.logger import get_logger
|
||||
from ...data import config as app_config
|
||||
from ...map.map_utils import _is_valid_bbox_dict
|
||||
|
||||
module_logger = get_logger(__name__)
|
||||
|
||||
class AreaManagementPanel:
|
||||
"""
|
||||
Manages the GUI elements for Bounding Box input and area profiles.
|
||||
"""
|
||||
def __init__(self, parent_frame: ttk.Frame, controller: Any):
|
||||
"""
|
||||
Initializes the AreaManagementPanel.
|
||||
|
||||
Args:
|
||||
parent_frame: The parent ttk.Frame where this panel will be placed.
|
||||
controller: The application controller instance.
|
||||
"""
|
||||
self.parent_frame = parent_frame
|
||||
self.controller = controller
|
||||
|
||||
# --- Tkinter Variables ---
|
||||
# Creiamo le variabili, ma le popoliamo subito dopo.
|
||||
self.lat_min_var = tk.StringVar()
|
||||
self.lon_min_var = tk.StringVar()
|
||||
self.lat_max_var = tk.StringVar()
|
||||
self.lon_max_var = tk.StringVar()
|
||||
|
||||
# Popoliamo le variabili con i valori di default dal config
|
||||
self.lat_min_var.set(str(app_config.DEFAULT_BBOX_LAT_MIN))
|
||||
self.lon_min_var.set(str(app_config.DEFAULT_BBOX_LON_MIN))
|
||||
self.lat_max_var.set(str(app_config.DEFAULT_BBOX_LAT_MAX))
|
||||
self.lon_max_var.set(str(app_config.DEFAULT_BBOX_LON_MAX))
|
||||
|
||||
self._build_ui()
|
||||
module_logger.debug("AreaManagementPanel initialized.")
|
||||
|
||||
def _build_ui(self):
|
||||
"""Builds the UI elements for area management."""
|
||||
# Bounding Box Input Frame
|
||||
self.bbox_frame = ttk.LabelFrame(
|
||||
self.parent_frame,
|
||||
text="Geographic Area (Bounding Box)",
|
||||
padding=(10, 5)
|
||||
)
|
||||
self.bbox_frame.pack(side=tk.TOP, fill=tk.X, expand=True, padx=2, pady=(0,5))
|
||||
self.bbox_frame.columnconfigure(1, weight=1)
|
||||
self.bbox_frame.columnconfigure(3, weight=1)
|
||||
|
||||
ttk.Label(self.bbox_frame, text="Lat Min:").grid(row=0, column=0, padx=(0, 2), pady=2, sticky=tk.W)
|
||||
self.lat_min_entry = ttk.Entry(self.bbox_frame, textvariable=self.lat_min_var)
|
||||
self.lat_min_entry.grid(row=0, column=1, padx=(0, 5), pady=2, sticky=tk.EW)
|
||||
|
||||
ttk.Label(self.bbox_frame, text="Lon Min:").grid(row=0, column=2, padx=(5, 2), pady=2, sticky=tk.W)
|
||||
self.lon_min_entry = ttk.Entry(self.bbox_frame, textvariable=self.lon_min_var)
|
||||
self.lon_min_entry.grid(row=0, column=3, padx=(0, 0), pady=2, sticky=tk.EW)
|
||||
|
||||
ttk.Label(self.bbox_frame, text="Lat Max:").grid(row=1, column=0, padx=(0, 2), pady=2, sticky=tk.W)
|
||||
self.lat_max_entry = ttk.Entry(self.bbox_frame, textvariable=self.lat_max_var)
|
||||
self.lat_max_entry.grid(row=1, column=1, padx=(0, 5), pady=2, sticky=tk.EW)
|
||||
|
||||
ttk.Label(self.bbox_frame, text="Lon Max:").grid(row=1, column=2, padx=(5, 2), pady=2, sticky=tk.W)
|
||||
self.lon_max_entry = ttk.Entry(self.bbox_frame, textvariable=self.lon_max_var)
|
||||
self.lon_max_entry.grid(row=1, column=3, padx=(0, 0), pady=2, sticky=tk.EW)
|
||||
|
||||
# Placeholder for Profile Management
|
||||
self.profiles_frame = ttk.LabelFrame(
|
||||
self.parent_frame, text="Area Profiles", padding=(10, 5)
|
||||
)
|
||||
self.profiles_frame.pack(side=tk.TOP, fill=tk.X, expand=True, padx=2, pady=5)
|
||||
ttk.Label(self.profiles_frame, text="Profile management coming soon...").pack()
|
||||
|
||||
def get_bounding_box_input(self) -> Optional[Dict[str, float]]:
|
||||
"""
|
||||
Retrieves and validates the bounding box coordinates from the GUI input fields.
|
||||
Returns a dictionary {lat_min, lon_min, lat_max, lon_max} or None if invalid.
|
||||
"""
|
||||
try:
|
||||
bbox_candidate = {
|
||||
"lat_min": float(self.lat_min_var.get()),
|
||||
"lon_min": float(self.lon_min_var.get()),
|
||||
"lat_max": float(self.lat_max_var.get()),
|
||||
"lon_max": float(self.lon_max_var.get()),
|
||||
}
|
||||
if _is_valid_bbox_dict(bbox_candidate):
|
||||
return bbox_candidate
|
||||
else:
|
||||
module_logger.warning(f"BBox from GUI failed validation: {bbox_candidate}")
|
||||
return None
|
||||
except (ValueError, TypeError):
|
||||
module_logger.warning("Invalid numeric format in BBox GUI fields.")
|
||||
return None
|
||||
|
||||
def update_bbox_gui_fields(self, bbox_dict: Dict[str, float]):
|
||||
"""Updates the bounding box input fields in the GUI."""
|
||||
if bbox_dict and _is_valid_bbox_dict(bbox_dict):
|
||||
decimals = getattr(app_config, "COORDINATE_DECIMAL_PLACES", 5)
|
||||
self.lat_min_var.set(f"{bbox_dict['lat_min']:.{decimals}f}")
|
||||
self.lon_min_var.set(f"{bbox_dict['lon_min']:.{decimals}f}")
|
||||
self.lat_max_var.set(f"{bbox_dict['lat_max']:.{decimals}f}")
|
||||
self.lon_max_var.set(f"{bbox_dict['lon_max']:.{decimals}f}")
|
||||
else:
|
||||
self.lat_min_var.set("")
|
||||
self.lon_min_var.set("")
|
||||
self.lat_max_var.set("")
|
||||
self.lon_max_var.set("")
|
||||
|
||||
def set_controls_state(self, enabled: bool):
|
||||
"""Enables or disables all controls in this panel."""
|
||||
state = tk.NORMAL if enabled else tk.DISABLED
|
||||
for entry in [self.lat_min_entry, self.lon_min_entry, self.lat_max_entry, self.lon_max_entry]:
|
||||
if entry.winfo_exists():
|
||||
entry.config(state=state)
|
||||
# Future: Add profile buttons here
|
||||
@ -1,136 +1,195 @@
|
||||
# FlightMonitor/gui/panels/function_notebook_panel.py
|
||||
"""
|
||||
Panel managing the function-related notebooks (Live, Historical Download, Playback)
|
||||
and their associated controls.
|
||||
Panel managing the area profiles, BBox input, and the function notebooks.
|
||||
"""
|
||||
import tkinter as tk
|
||||
from tkinter import ttk
|
||||
from tkinter import ttk, messagebox
|
||||
from typing import Dict, Any, Optional
|
||||
|
||||
from ...utils.logger import get_logger
|
||||
from ...data import config as app_config
|
||||
from ...map.map_utils import _is_valid_bbox_dict
|
||||
from ...data.area_profile_manager import DEFAULT_PROFILE_NAME
|
||||
|
||||
module_logger = get_logger(__name__)
|
||||
|
||||
class FunctionNotebookPanel:
|
||||
"""
|
||||
Manages the function-selection notebook (Live, Historical Download, Playback).
|
||||
Manages the combined Area Profiles, BBox, and Function Notebooks panel.
|
||||
"""
|
||||
|
||||
def __init__(self, parent_frame: ttk.Frame, controller: Any):
|
||||
"""
|
||||
Initializes the FunctionNotebookPanel.
|
||||
|
||||
Args:
|
||||
parent_frame: The parent ttk.Frame where this notebook will be placed.
|
||||
controller: The application controller instance to delegate actions.
|
||||
"""
|
||||
self.parent_frame = parent_frame
|
||||
self.controller = controller
|
||||
|
||||
# --- UI Elements ---
|
||||
self.function_notebook: Optional[ttk.Notebook] = None
|
||||
self.start_live_button: Optional[ttk.Button] = None
|
||||
self.stop_live_button: Optional[ttk.Button] = None
|
||||
|
||||
# Placeholder for future controls
|
||||
self.start_download_button: Optional[ttk.Button] = None
|
||||
# --- Tkinter Variables ---
|
||||
self.lat_min_var = tk.StringVar()
|
||||
self.lon_min_var = tk.StringVar()
|
||||
self.lat_max_var = tk.StringVar()
|
||||
self.lon_max_var = tk.StringVar()
|
||||
self.selected_profile_var = tk.StringVar()
|
||||
|
||||
self._build_ui()
|
||||
module_logger.debug("FunctionNotebookPanel initialized.")
|
||||
self.update_profile_list()
|
||||
self.set_selected_profile(DEFAULT_PROFILE_NAME)
|
||||
|
||||
module_logger.debug("FunctionNotebookPanel (unified) initialized.")
|
||||
|
||||
def _build_ui(self):
|
||||
"""
|
||||
Builds the main notebook structure and all its tabs and controls.
|
||||
"""
|
||||
# --- Main Container for Area Management ---
|
||||
area_frame = ttk.LabelFrame(self.parent_frame, text="Area Profiles & BBox", padding=10)
|
||||
area_frame.pack(side=tk.TOP, fill=tk.X, padx=2, pady=(0, 5))
|
||||
|
||||
# Configure grid for the area frame
|
||||
area_frame.columnconfigure(1, weight=1)
|
||||
|
||||
# --- Row 0: Profile Selection and Buttons ---
|
||||
profile_controls_frame = ttk.Frame(area_frame)
|
||||
profile_controls_frame.grid(row=0, column=0, columnspan=4, sticky="ew", pady=(0, 10))
|
||||
profile_controls_frame.columnconfigure(0, weight=1) # Let combobox expand
|
||||
|
||||
self.profile_combobox = ttk.Combobox(
|
||||
profile_controls_frame, textvariable=self.selected_profile_var, state="readonly"
|
||||
)
|
||||
self.profile_combobox.grid(row=0, column=0, sticky="ew", padx=(0, 5))
|
||||
self.profile_combobox.bind("<<ComboboxSelected>>", self._on_profile_selected)
|
||||
|
||||
new_button = ttk.Button(profile_controls_frame, text="New", command=self._on_new_profile, width=5)
|
||||
new_button.grid(row=0, column=1, padx=(0, 2))
|
||||
|
||||
save_button = ttk.Button(profile_controls_frame, text="Save", command=self._on_save_profile, width=5)
|
||||
save_button.grid(row=0, column=2, padx=(0, 2))
|
||||
|
||||
delete_button = ttk.Button(profile_controls_frame, text="Delete", command=self._on_delete_profile, width=7)
|
||||
delete_button.grid(row=0, column=3)
|
||||
|
||||
# --- Row 1 & 2: Bounding Box Entries ---
|
||||
ttk.Label(area_frame, text="Lat Min:").grid(row=1, column=0, padx=(0, 2), pady=2, sticky=tk.W)
|
||||
self.lat_min_entry = ttk.Entry(area_frame, textvariable=self.lat_min_var)
|
||||
self.lat_min_entry.grid(row=1, column=1, padx=(0, 5), pady=2, sticky=tk.EW)
|
||||
|
||||
ttk.Label(area_frame, text="Lon Min:").grid(row=1, column=2, padx=(5, 2), pady=2, sticky=tk.W)
|
||||
self.lon_min_entry = ttk.Entry(area_frame, textvariable=self.lon_min_var)
|
||||
self.lon_min_entry.grid(row=1, column=3, padx=(0, 0), pady=2, sticky=tk.EW)
|
||||
|
||||
ttk.Label(area_frame, text="Lat Max:").grid(row=2, column=0, padx=(0, 2), pady=2, sticky=tk.W)
|
||||
self.lat_max_entry = ttk.Entry(area_frame, textvariable=self.lat_max_var)
|
||||
self.lat_max_entry.grid(row=2, column=1, padx=(0, 5), pady=2, sticky=tk.EW)
|
||||
|
||||
ttk.Label(area_frame, text="Lon Max:").grid(row=2, column=2, padx=(5, 2), pady=2, sticky=tk.W)
|
||||
self.lon_max_entry = ttk.Entry(area_frame, textvariable=self.lon_max_var)
|
||||
self.lon_max_entry.grid(row=2, column=3, padx=(0, 0), pady=2, sticky=tk.EW)
|
||||
|
||||
# --- Function Notebook ---
|
||||
self.function_notebook = ttk.Notebook(self.parent_frame)
|
||||
self.function_notebook.pack(fill=tk.BOTH, expand=True, padx=2, pady=2)
|
||||
self.function_notebook.pack(side=tk.TOP, fill=tk.BOTH, expand=True, padx=2, pady=2)
|
||||
|
||||
# --- Live Tab ---
|
||||
live_tab_frame = ttk.Frame(self.function_notebook, padding=10)
|
||||
self.function_notebook.add(live_tab_frame, text="Live Monitor")
|
||||
|
||||
self.start_live_button = ttk.Button(
|
||||
live_tab_frame, text="Start Live Monitoring", command=self._on_start_live_monitoring
|
||||
)
|
||||
self.start_live_button = ttk.Button(live_tab_frame, text="Start Live Monitoring", command=self._on_start_live_monitoring)
|
||||
self.start_live_button.pack(side=tk.LEFT, padx=5, pady=5)
|
||||
|
||||
self.stop_live_button = ttk.Button(
|
||||
live_tab_frame,
|
||||
text="Stop Live Monitoring",
|
||||
command=self._on_stop_live_monitoring,
|
||||
state=tk.DISABLED,
|
||||
)
|
||||
self.stop_live_button = ttk.Button(live_tab_frame, text="Stop Live Monitoring", command=self._on_stop_live_monitoring, state=tk.DISABLED)
|
||||
self.stop_live_button.pack(side=tk.LEFT, padx=5, pady=5)
|
||||
|
||||
# --- Historical Download Tab ---
|
||||
download_tab_frame = ttk.Frame(self.function_notebook, padding=10)
|
||||
self.function_notebook.add(download_tab_frame, text="Historical Download")
|
||||
|
||||
ttk.Label(
|
||||
download_tab_frame,
|
||||
text="Historical download controls will be here.",
|
||||
font=("Arial", 10, "italic")
|
||||
).pack(expand=True)
|
||||
ttk.Label(download_tab_frame, text="Historical download controls...").pack()
|
||||
|
||||
# --- Playback Tab ---
|
||||
playback_tab_frame = ttk.Frame(self.function_notebook, padding=10)
|
||||
self.function_notebook.add(playback_tab_frame, text="Playback")
|
||||
|
||||
ttk.Label(
|
||||
playback_tab_frame,
|
||||
text="Playback controls will be here.",
|
||||
font=("Arial", 10, "italic")
|
||||
).pack(expand=True)
|
||||
ttk.Label(playback_tab_frame, text="Playback controls...").pack()
|
||||
|
||||
self.function_notebook.bind("<<NotebookTabChanged>>", self._on_tab_change)
|
||||
|
||||
# --- Event Handlers ---
|
||||
def _on_start_live_monitoring(self):
|
||||
"""Callback for the 'Start Live Monitoring' button."""
|
||||
if not self.controller:
|
||||
module_logger.error("Controller not available to start live monitoring.")
|
||||
return
|
||||
|
||||
module_logger.info("FunctionNotebookPanel: Start Live Monitoring clicked.")
|
||||
# The controller will fetch the BBox from AreaManagementPanel via MainWindow
|
||||
self.controller.start_live_monitoring()
|
||||
if self.controller: self.controller.start_live_monitoring()
|
||||
|
||||
def _on_stop_live_monitoring(self):
|
||||
"""Callback for the 'Stop Live Monitoring' button."""
|
||||
if not self.controller:
|
||||
module_logger.error("Controller not available to stop live monitoring.")
|
||||
if self.controller: self.controller.stop_live_monitoring()
|
||||
|
||||
def _on_profile_selected(self, event=None):
|
||||
selected_name = self.selected_profile_var.get()
|
||||
if selected_name and self.controller:
|
||||
self.controller.load_area_profile(selected_name)
|
||||
|
||||
def _on_new_profile(self):
|
||||
self.selected_profile_var.set("")
|
||||
self.update_bbox_gui_fields({})
|
||||
|
||||
def _on_save_profile(self):
|
||||
if self.controller: self.controller.save_current_area_as_profile()
|
||||
|
||||
def _on_delete_profile(self):
|
||||
profile_to_delete = self.selected_profile_var.get()
|
||||
if not profile_to_delete or profile_to_delete == DEFAULT_PROFILE_NAME:
|
||||
messagebox.showerror("Delete Error", "Cannot delete the default profile or no profile selected.", parent=self.parent_frame)
|
||||
return
|
||||
if messagebox.askyesno("Confirm Delete", f"Delete profile '{profile_to_delete}'?", parent=self.parent_frame):
|
||||
if self.controller: self.controller.delete_area_profile(profile_to_delete)
|
||||
|
||||
module_logger.info("FunctionNotebookPanel: Stop Live Monitoring clicked.")
|
||||
self.controller.stop_live_monitoring()
|
||||
|
||||
def _on_tab_change(self, event: Optional[tk.Event] = None):
|
||||
"""Handles changes in the selected function tab."""
|
||||
def _on_tab_change(self, event=None):
|
||||
if not self.function_notebook: return
|
||||
tab_text = self.function_notebook.tab(self.function_notebook.index("current"), "text")
|
||||
if self.controller and hasattr(self.controller, "on_function_tab_changed"):
|
||||
self.controller.on_function_tab_changed(tab_text)
|
||||
|
||||
# --- Public Methods ---
|
||||
def update_profile_list(self):
|
||||
if self.controller:
|
||||
profile_names = self.controller.get_profile_names()
|
||||
self.profile_combobox["values"] = profile_names
|
||||
self.set_selected_profile(self.selected_profile_var.get() or DEFAULT_PROFILE_NAME)
|
||||
|
||||
def set_selected_profile(self, profile_name: str):
|
||||
if profile_name in self.profile_combobox["values"]:
|
||||
self.selected_profile_var.set(profile_name)
|
||||
elif self.profile_combobox["values"]:
|
||||
self.selected_profile_var.set(self.profile_combobox["values"][0])
|
||||
|
||||
def get_bounding_box_input(self):
|
||||
# ... (metodo rimane invariato)
|
||||
try:
|
||||
tab_text = self.function_notebook.tab(self.function_notebook.index("current"), "text")
|
||||
module_logger.info(f"FunctionNotebookPanel: Switched to tab: {tab_text}")
|
||||
bbox_candidate = {
|
||||
"lat_min": float(self.lat_min_var.get()), "lon_min": float(self.lon_min_var.get()),
|
||||
"lat_max": float(self.lat_max_var.get()), "lon_max": float(self.lon_max_var.get()),
|
||||
}
|
||||
return bbox_candidate if _is_valid_bbox_dict(bbox_candidate) else None
|
||||
except (ValueError, TypeError):
|
||||
return None
|
||||
|
||||
# Delegate placeholder updates to MainWindow via controller
|
||||
if self.controller and hasattr(self.controller, "on_function_tab_changed"):
|
||||
self.controller.on_function_tab_changed(tab_text)
|
||||
|
||||
except (tk.TclError, IndexError) as e:
|
||||
module_logger.warning(f"Error getting selected tab info: {e}")
|
||||
def update_bbox_gui_fields(self, bbox_dict: Dict[str, float]):
|
||||
# ... (metodo rimane invariato)
|
||||
if bbox_dict and _is_valid_bbox_dict(bbox_dict):
|
||||
decimals = getattr(app_config, "COORDINATE_DECIMAL_PLACES", 5)
|
||||
self.lat_min_var.set(f"{bbox_dict['lat_min']:.{decimals}f}")
|
||||
self.lon_min_var.set(f"{bbox_dict['lon_min']:.{decimals}f}")
|
||||
self.lat_max_var.set(f"{bbox_dict['lat_max']:.{decimals}f}")
|
||||
self.lon_max_var.set(f"{bbox_dict['lon_max']:.{decimals}f}")
|
||||
else:
|
||||
self.lat_min_var.set("")
|
||||
self.lon_min_var.set("")
|
||||
self.lat_max_var.set("")
|
||||
self.lon_max_var.set("")
|
||||
|
||||
def set_monitoring_button_states(self, is_monitoring_active: bool):
|
||||
"""
|
||||
Sets the state of the Start/Stop buttons for the Live tab.
|
||||
"""
|
||||
# ... (metodo rimane invariato)
|
||||
if self.start_live_button and self.start_live_button.winfo_exists():
|
||||
self.start_live_button.config(state=tk.DISABLED if is_monitoring_active else tk.NORMAL)
|
||||
|
||||
if self.stop_live_button and self.stop_live_button.winfo_exists():
|
||||
self.stop_live_button.config(state=tk.NORMAL if is_monitoring_active else tk.DISABLED)
|
||||
|
||||
def get_active_tab_text(self) -> Optional[str]:
|
||||
"""Returns the text of the currently active tab."""
|
||||
if not self.function_notebook: return None
|
||||
try:
|
||||
return self.function_notebook.tab(self.function_notebook.index("current"), "text")
|
||||
except (tk.TclError, IndexError):
|
||||
return None
|
||||
def set_controls_state(self, enabled: bool):
|
||||
state = tk.NORMAL if enabled else tk.DISABLED
|
||||
readonly_state = "readonly" if enabled else tk.DISABLED
|
||||
|
||||
self.profile_combobox.config(state=readonly_state)
|
||||
for entry in [self.lat_min_entry, self.lon_min_entry, self.lat_max_entry, self.lon_max_entry]:
|
||||
if entry.winfo_exists(): entry.config(state=state)
|
||||
|
||||
# Abilita/Disabilita i bottoni dei profili
|
||||
for child in self.profile_combobox.master.winfo_children():
|
||||
if isinstance(child, ttk.Button):
|
||||
child.config(state=state)
|
||||
Loading…
Reference in New Issue
Block a user