# --- START OF FILE ui.py --- # ui.py """ THIS SOFTWARE IS PROVIDED “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. Defines the user interface components for the Control Panel application, including the main control panel area, status bar, map parameters, info display with Google Maps/Earth links, and helper functions for window creation. """ # Standard library imports import logging from typing import TYPE_CHECKING, Dict, Tuple, Optional # Third-party imports import tkinter as tk from tkinter import ttk # Local application imports import config # Type hinting for App reference if TYPE_CHECKING: from ControlPanel import ControlPanelApp class ControlPanel(ttk.Frame): """ Main control panel frame containing SAR, MFD, Map parameter widgets, information displays, statistics labels, and interaction buttons. """ def __init__(self, parent: tk.Widget, app: "ControlPanelApp", *args, **kwargs): """Initializes the ControlPanel frame.""" log_prefix = "[UI Setup]" logging.debug(f"{log_prefix} Initializing ControlPanel frame...") super().__init__(parent, *args, **kwargs) self.app: "ControlPanelApp" = app # StringVars for Entry widgets self.sar_center_coords_var = tk.StringVar(value="Lat=N/A, Lon=N/A") self.sar_orientation_var = tk.StringVar(value="N/A") self.sar_size_km_var = tk.StringVar(value="N/A") self.mouse_coords_var = tk.StringVar(value="Lat=N/A, Lon=N/A") self.map_mouse_coords_var = tk.StringVar(value="Lat=N/A, Lon=N/A") self.sar_lat_shift_var = tk.StringVar( value=f"{self.app.state.sar_lat_shift_deg:.6f}" ) self.sar_lon_shift_var = tk.StringVar( value=f"{self.app.state.sar_lon_shift_deg:.6f}" ) self.dropped_stats_var = tk.StringVar(value="Drop (Q): S=0, M=0, Tk=0, Mo=0") self.incomplete_stats_var = tk.StringVar(value="Incmpl (RX): S=0, M=0") # MFD color preview labels self.mfd_color_labels: Dict[str, tk.Label] = {} # Initialize UI elements self.init_ui() logging.debug(f"{log_prefix} ControlPanel frame initialization complete.") def init_ui(self): """Initializes and arranges the user interface widgets within the frame.""" log_prefix = "[UI Setup]" logging.debug(f"{log_prefix} Starting init_ui widget creation...") self.pack(side=tk.TOP, fill=tk.BOTH, expand=True, padx=5, pady=5) # --- 1. SAR Parameters Frame --- # (Code Unchanged - Test, Record, Size, Palette, Contrast, Brightness) self.sar_params_frame = ttk.Labelframe(self, text="SAR Parameters", padding=5) self.sar_params_frame.pack(side=tk.TOP, fill=tk.X, padx=5, pady=(5, 2)) sar_row = 0 self.test_image_var = tk.IntVar(value=1 if config.ENABLE_TEST_MODE else 0) self.test_image_check = ttk.Checkbutton( self.sar_params_frame, text="Test Image", variable=self.test_image_var, command=self.app.update_image_mode, ) self.test_image_check.grid( row=sar_row, column=0, columnspan=2, sticky=tk.W, padx=5, pady=2 ) self.record_sar_var = tk.BooleanVar(value=config.DEFAULT_SAR_RECORDING_ENABLED) self.record_sar_check = ttk.Checkbutton( self.sar_params_frame, text="Record SAR", variable=self.record_sar_var, command=self.app.toggle_sar_recording, ) self.record_sar_check.grid( row=sar_row, column=2, columnspan=2, sticky=tk.W, padx=5, pady=2 ) sar_row += 1 self.sar_size_label = ttk.Label(self.sar_params_frame, text="Size:") self.sar_size_label.grid( row=sar_row, column=0, sticky=tk.W, padx=(5, 2), pady=1 ) self.sar_size_combo = ttk.Combobox( self.sar_params_frame, values=config.SAR_SIZE_FACTORS, state="readonly", width=6, ) try: factor = ( max(1, config.SAR_WIDTH // self.app.state.sar_display_width) if self.app.state.sar_display_width > 0 else 1 ) sz_str = f"1:{factor}" self.sar_size_combo.set( sz_str if sz_str in config.SAR_SIZE_FACTORS else config.DEFAULT_SAR_SIZE ) except: self.sar_size_combo.set(config.DEFAULT_SAR_SIZE) self.sar_size_combo.grid( row=sar_row, column=1, sticky=tk.EW, padx=(0, 10), pady=1 ) self.sar_size_combo.bind("<>", self.app.update_sar_size) self.palette_label = ttk.Label(self.sar_params_frame, text="Palette:") self.palette_label.grid(row=sar_row, column=2, sticky=tk.W, padx=(0, 2), pady=1) self.palette_combo = ttk.Combobox( self.sar_params_frame, values=config.COLOR_PALETTES, state="readonly", width=8, ) self.palette_combo.set(self.app.state.sar_palette) self.palette_combo.grid( row=sar_row, column=3, sticky=tk.EW, padx=(0, 5), pady=1 ) self.palette_combo.bind("<>", self.app.update_sar_palette) sar_row += 1 self.contrast_label = ttk.Label(self.sar_params_frame, text="Contrast:") self.contrast_label.grid( row=sar_row, column=0, sticky=tk.W, padx=(5, 2), pady=1 ) self.contrast_scale = ttk.Scale( self.sar_params_frame, orient=tk.HORIZONTAL, from_=0.1, to=3.0, value=self.app.state.sar_contrast, command=self.app.update_contrast, ) self.contrast_scale.grid( row=sar_row, column=1, sticky=tk.EW, padx=(0, 10), pady=1 ) self.brightness_label = ttk.Label(self.sar_params_frame, text="Brightness:") self.brightness_label.grid( row=sar_row, column=2, sticky=tk.W, padx=(0, 2), pady=1 ) self.brightness_scale = ttk.Scale( self.sar_params_frame, orient=tk.HORIZONTAL, from_=-100, to=100, value=self.app.state.sar_brightness, command=self.app.update_brightness, ) self.brightness_scale.grid( row=sar_row, column=3, sticky=tk.EW, padx=(0, 5), pady=1 ) self.sar_params_frame.columnconfigure(1, weight=1) self.sar_params_frame.columnconfigure(3, weight=1) # --- 2. MFD Parameters Frame --- # (Code Unchanged - Category controls, Raw Map slider) self.mfd_params_frame = ttk.Labelframe(self, text="MFD Parameters", padding=5) self.mfd_params_frame.pack(side=tk.TOP, fill=tk.X, padx=5, pady=2) mfd_categories_ordered = [ "Occlusion", "Cat A", "Cat B", "Cat C", "Cat C1", "Cat C2", "Cat C3", ] num_categories = len(mfd_categories_ordered) for index, name in enumerate(mfd_categories_ordered): row = index // 2 col_offset = 0 if (index % 2 == 0) else 4 ttk.Label(self.mfd_params_frame, text=f"{name}:").grid( row=row, column=0 + col_offset, sticky=tk.W, padx=(5, 1), pady=1 ) intensity_var = tk.IntVar(value=config.DEFAULT_MFD_INTENSITY) try: intensity_var.set( self.app.state.mfd_params["categories"][name]["intensity"] ) except: pass setattr( self, f"mfd_{name.replace(' ','_').lower()}_intensity_var", intensity_var, ) ttk.Scale( self.mfd_params_frame, orient=tk.HORIZONTAL, length=100, from_=0, to=255, variable=intensity_var, command=lambda v, n=name, var=intensity_var: self.app.update_mfd_category_intensity( n, var.get() ), ).grid(row=row, column=1 + col_offset, sticky=tk.EW, padx=1, pady=1) ttk.Button( self.mfd_params_frame, text="Color", width=5, command=lambda n=name: self.app.choose_mfd_category_color(n), ).grid(row=row, column=2 + col_offset, sticky=tk.W, padx=1, pady=1) cl = tk.Label( self.mfd_params_frame, text="", width=3, relief=tk.SUNKEN, borderwidth=1 ) try: bgr = self.app.state.mfd_params["categories"][name]["color"] cl.config(background=f"#{bgr[2]:02x}{bgr[1]:02x}{bgr[0]:02x}") except: cl.config(background="grey") cl.grid( row=row, column=3 + col_offset, sticky=tk.W, padx=(1, 5), pady=1 ) self.mfd_color_labels[name] = cl last_row = (num_categories - 1) // 2 raw_col = 4 if (num_categories % 2 != 0) else 0 raw_row = last_row if (num_categories % 2 != 0) else last_row + 1 ttk.Label(self.mfd_params_frame, text="Raw Map:").grid( row=raw_row, column=0 + raw_col, sticky=tk.W, padx=(5, 1), pady=1 ) raw_var = tk.IntVar(value=config.DEFAULT_MFD_RAW_MAP_INTENSITY) try: raw_var.set(self.app.state.mfd_params["raw_map_intensity"]) except: pass self.mfd_raw_map_intensity_var = raw_var ttk.Scale( self.mfd_params_frame, orient=tk.HORIZONTAL, length=100, from_=0, to=255, variable=raw_var, command=lambda v: self.app.update_mfd_raw_map_intensity(raw_var.get()), ).grid( row=raw_row, column=1 + raw_col, columnspan=3, sticky=tk.EW, padx=(1, 5), pady=1, ) self.mfd_params_frame.columnconfigure(1, weight=1) self.mfd_params_frame.columnconfigure(5, weight=1) # --- 3. Map Parameters Frame --- # (Code Unchanged - Map Size, Save Button, Overlay Checkbox, Alpha Slider, Shift Inputs+Button) self.map_params_frame = ttk.Labelframe(self, text="Map Parameters", padding=5) self.map_params_frame.pack(side=tk.TOP, fill=tk.X, padx=5, pady=2) map_row = 0 self.map_size_label = ttk.Label(self.map_params_frame, text="Map Display Size:") self.map_size_label.grid( row=map_row, column=0, sticky=tk.W, padx=(5, 2), pady=1 ) self.map_size_combo = ttk.Combobox( self.map_params_frame, values=config.MAP_SIZE_FACTORS, state="readonly", width=6, ) self.map_size_combo.set(config.DEFAULT_MAP_SIZE) self.map_size_combo.grid( row=map_row, column=1, sticky=tk.EW, padx=(2, 10), pady=1 ) self.map_size_combo.bind("<>", self.app.update_map_size) self.save_map_button = ttk.Button( self.map_params_frame, text="Save Map View", command=self.app.save_current_map_view, ) self.save_map_button.grid( row=map_row, column=2, columnspan=4, sticky=tk.E, padx=5, pady=1 ) # Span more columns map_row += 1 self.sar_overlay_var = tk.BooleanVar( value=self.app.state.map_sar_overlay_enabled ) self.sar_overlay_check = ttk.Checkbutton( self.map_params_frame, text="Show SAR Overlay on Map", variable=self.sar_overlay_var, command=self.app.toggle_sar_overlay, ) self.sar_overlay_check.grid( row=map_row, column=0, columnspan=2, sticky=tk.W, padx=5, pady=2 ) map_row += 1 self.alpha_label = ttk.Label(self.map_params_frame, text="SAR Overlay Alpha:") self.alpha_label.grid(row=map_row, column=0, sticky=tk.W, padx=(5, 2), pady=1) self.sar_overlay_alpha_var = tk.DoubleVar( value=self.app.state.map_sar_overlay_alpha ) self.alpha_scale = ttk.Scale( self.map_params_frame, orient=tk.HORIZONTAL, from_=0.0, to=1.0, variable=self.sar_overlay_alpha_var, ) self.alpha_scale.bind("", self.app.on_alpha_slider_release) self.alpha_scale.grid( row=map_row, column=1, columnspan=5, sticky=tk.EW, padx=(0, 5), pady=1 ) # Span more map_row += 1 shift_label = ttk.Label(self.map_params_frame, text="SAR Shift (deg):") shift_label.grid(row=map_row, column=0, sticky=tk.W, padx=(5, 2), pady=1) lat_label = ttk.Label(self.map_params_frame, text="Lat:") lat_label.grid(row=map_row, column=1, sticky=tk.W, padx=(0, 0), pady=1) self.lat_shift_entry = ttk.Entry( self.map_params_frame, textvariable=self.sar_lat_shift_var, width=10 ) self.lat_shift_entry.grid( row=map_row, column=2, sticky=tk.W, padx=(0, 5), pady=1 ) lon_label = ttk.Label(self.map_params_frame, text="Lon:") lon_label.grid(row=map_row, column=3, sticky=tk.W, padx=(5, 0), pady=1) self.lon_shift_entry = ttk.Entry( self.map_params_frame, textvariable=self.sar_lon_shift_var, width=10 ) self.lon_shift_entry.grid( row=map_row, column=4, sticky=tk.W, padx=(0, 5), pady=1 ) self.apply_shift_button = ttk.Button( self.map_params_frame, text="Apply Shift", command=self.app.apply_sar_overlay_shift, ) self.apply_shift_button.grid( row=map_row, column=5, sticky=tk.E, padx=(5, 5), pady=1 ) map_row += 1 self.map_params_frame.columnconfigure(2, weight=1) self.map_params_frame.columnconfigure( 4, weight=1 ) # Give weight to entry columns # --- 4. Info Display Frame --- logging.debug(f"{log_prefix} Creating Info Display frame...") self.info_display_frame = ttk.Labelframe(self, text="Info Display", padding=5) self.info_display_frame.pack(side=tk.TOP, fill=tk.X, padx=5, pady=2) info_row = 0 button_width = 3 # Define a standard width for Go/GE buttons # --- Row 0: SAR Center Coords --- ref_label = ttk.Label(self.info_display_frame, text="SAR Center:") ref_label.grid(row=info_row, column=0, sticky=tk.W, padx=(5, 2), pady=1) self.sar_center_entry = ttk.Entry( self.info_display_frame, textvariable=self.sar_center_coords_var, state="readonly", width=35, ) self.sar_center_entry.grid( row=info_row, column=1, columnspan=3, sticky=tk.EW, padx=(0, 2), pady=1 ) # --- >>> START OF MODIFIED BUTTONS <<< --- self.ref_gmaps_button = ttk.Button( self.info_display_frame, text="Go", width=button_width, command=lambda: self.app.go_to_google_maps("sar_center"), ) self.ref_gmaps_button.grid( row=info_row, column=4, sticky=tk.E, padx=(0, 1), pady=1 ) self.ref_gearth_button = ttk.Button( self.info_display_frame, text="GE", width=button_width, command=lambda: self.app.go_to_google_earth("sar_center"), ) # New Button + Callback self.ref_gearth_button.grid( row=info_row, column=5, sticky=tk.E, padx=(0, 5), pady=1 ) # --- >>> END OF MODIFIED BUTTONS <<< --- info_row += 1 # --- Row 1: Image Orient & Image Size --- orient_label = ttk.Label(self.info_display_frame, text="Image Orient:") orient_label.grid(row=info_row, column=0, sticky=tk.W, padx=5, pady=1) self.sar_orientation_entry = ttk.Entry( self.info_display_frame, textvariable=self.sar_orientation_var, state="readonly", width=15, ) self.sar_orientation_entry.grid( row=info_row, column=1, sticky=tk.EW, padx=(0, 5), pady=1 ) size_label = ttk.Label(self.info_display_frame, text="Image Size:") size_label.grid(row=info_row, column=2, sticky=tk.W, padx=(10, 2), pady=1) self.sar_size_entry = ttk.Entry( self.info_display_frame, textvariable=self.sar_size_km_var, state="readonly", width=25, ) self.sar_size_entry.grid( row=info_row, column=3, columnspan=3, sticky=tk.EW, padx=(0, 5), pady=1 ) # Span 3 cols info_row += 1 # --- Row 2: SAR Mouse Coords --- sar_mouse_label = ttk.Label(self.info_display_frame, text="SAR Mouse:") sar_mouse_label.grid(row=info_row, column=0, sticky=tk.W, padx=5, pady=1) self.mouse_latlon_entry = ttk.Entry( self.info_display_frame, textvariable=self.mouse_coords_var, state="readonly", width=35, ) self.mouse_latlon_entry.grid( row=info_row, column=1, columnspan=3, sticky=tk.EW, padx=(0, 2), pady=1 ) # --- >>> START OF MODIFIED BUTTONS <<< --- self.sar_mouse_gmaps_button = ttk.Button( self.info_display_frame, text="Go", width=button_width, command=lambda: self.app.go_to_google_maps("sar_mouse"), ) self.sar_mouse_gmaps_button.grid( row=info_row, column=4, sticky=tk.E, padx=(0, 1), pady=1 ) self.sar_mouse_gearth_button = ttk.Button( self.info_display_frame, text="GE", width=button_width, command=lambda: self.app.go_to_google_earth("sar_mouse"), ) # New Button + Callback self.sar_mouse_gearth_button.grid( row=info_row, column=5, sticky=tk.E, padx=(0, 5), pady=1 ) # --- >>> END OF MODIFIED BUTTONS <<< --- info_row += 1 # --- Row 3: Map Mouse Coords --- map_mouse_label = ttk.Label(self.info_display_frame, text="Map Mouse:") map_mouse_label.grid(row=info_row, column=0, sticky=tk.W, padx=5, pady=1) self.map_mouse_latlon_entry = ttk.Entry( self.info_display_frame, textvariable=self.map_mouse_coords_var, state="readonly", width=35, ) self.map_mouse_latlon_entry.grid( row=info_row, column=1, columnspan=3, sticky=tk.EW, padx=(0, 2), pady=1 ) # --- >>> START OF MODIFIED BUTTONS <<< --- self.map_mouse_gmaps_button = ttk.Button( self.info_display_frame, text="Go", width=button_width, command=lambda: self.app.go_to_google_maps("map_mouse"), ) self.map_mouse_gmaps_button.grid( row=info_row, column=4, sticky=tk.E, padx=(0, 1), pady=1 ) self.map_mouse_gearth_button = ttk.Button( self.info_display_frame, text="GE", width=button_width, command=lambda: self.app.go_to_google_earth("map_mouse"), ) # New Button + Callback self.map_mouse_gearth_button.grid( row=info_row, column=5, sticky=tk.E, padx=(0, 5), pady=1 ) # --- >>> END OF MODIFIED BUTTONS <<< --- info_row += 1 # --- Row 4: Drop & Incomplete Stats --- dropped_label_text = ttk.Label(self.info_display_frame, text="Stats Drop:") dropped_label_text.grid(row=info_row, column=0, sticky=tk.W, padx=5, pady=1) self.dropped_entry = ttk.Entry( self.info_display_frame, textvariable=self.dropped_stats_var, state="readonly", width=30, ) self.dropped_entry.grid( row=info_row, column=1, sticky=tk.EW, padx=(0, 5), pady=1 ) incomplete_label_text = ttk.Label(self.info_display_frame, text="Incomplete:") incomplete_label_text.grid( row=info_row, column=2, sticky=tk.W, padx=(10, 2), pady=1 ) self.incomplete_entry = ttk.Entry( self.info_display_frame, textvariable=self.incomplete_stats_var, state="readonly", width=15, ) self.incomplete_entry.grid( row=info_row, column=3, columnspan=3, sticky=tk.EW, padx=(0, 5), pady=1 ) # Span 3 # --- Row 5: "GE All" Button --- info_row += 1 # Incrementa la riga self.ge_all_button = ttk.Button( self.info_display_frame, text="GE All", command=self.app.go_to_all_gearth # Collega alla nuova funzione callback ) # Posiziona il bottone, ad esempio centrato o allineato a destra self.ge_all_button.grid( row=info_row, column=0, # Inizia dalla prima colonna columnspan=6, # Fallo espandere su tutte le colonne per centrarlo (o allinea a destra) sticky=tk.EW, # Espandi orizzontalmente padx=5, pady=(5, 5) # Aggiungi padding sotto ) # Configure column weights for Info Display frame self.info_display_frame.columnconfigure( 1, weight=1 ) # Give weight to first entry column self.info_display_frame.columnconfigure( 3, weight=1 ) # Give weight to second entry column (col 3) self.info_display_frame.columnconfigure(4, weight=0) # Button Go self.info_display_frame.columnconfigure(5, weight=0) # Button GE logging.debug(f"{log_prefix} Info Display frame created.") # --- End of init_ui --- logging.debug(f"{log_prefix} init_ui widget creation complete.") # --- Methods to update UI displays --- # (set_sar_center_coords, set_mouse_coordinates, set_map_mouse_coordinates) # (set_sar_orientation, set_sar_size_km, set_statistics_display) # (update_mfd_color_display - Unchanged logic, adapt if needed) def set_sar_center_coords(self, latitude_str: str, longitude_str: str): text = f"Lat={latitude_str}, Lon={longitude_str}" try: self.sar_center_coords_var.set(text) except Exception as e: logging.warning(f"[UI Update] Error setting SAR center coords: {e}") def set_mouse_coordinates(self, latitude_str: str, longitude_str: str): text = f"Lat={latitude_str}, Lon={longitude_str}" try: self.mouse_coords_var.set(text) except Exception as e: logging.warning(f"[UI Update] Error setting SAR mouse coords: {e}") def set_map_mouse_coordinates(self, latitude_str: str, longitude_str: str): text = f"Lat={latitude_str}, Lon={longitude_str}" try: self.map_mouse_coords_var.set(text) except Exception as e: logging.warning(f"[UI Update] Error setting Map mouse coords: {e}") def set_sar_orientation(self, orientation_deg_str: str): try: self.sar_orientation_var.set(orientation_deg_str) except Exception as e: logging.warning(f"[UI Update] Error setting SAR orientation: {e}") def set_sar_size_km(self, size_text: str): try: self.sar_size_km_var.set(size_text) except Exception as e: logging.warning(f"[UI Update] Error setting SAR size: {e}") def set_statistics_display(self, dropped_text: str, incomplete_text: str): try: self.dropped_stats_var.set(dropped_text) self.incomplete_stats_var.set(incomplete_text) except Exception as e: logging.warning(f"[UI Update] Error setting stats display: {e}") def update_mfd_color_display( self, category_name: str, color_bgr_tuple: Tuple[int, int, int] ): if category_name in self.mfd_color_labels: lbl = self.mfd_color_labels[category_name] hex_color = f"#{color_bgr_tuple[2]:02x}{color_bgr_tuple[1]:02x}{color_bgr_tuple[0]:02x}" try: if lbl.winfo_exists(): lbl.config(background=hex_color) except Exception as e: logging.exception( f"[UI Update] Error updating MFD color for {category_name}: {e}" ) class StatusBar(ttk.Label): """Represents the status bar at the bottom of the main window.""" def __init__(self, parent: tk.Widget, *args, **kwargs): log_prefix = "[UI Setup]" logging.debug(f"{log_prefix} Initializing StatusBar...") super().__init__( parent, text=config.INITIAL_STATUS_MESSAGE, relief=tk.SUNKEN, anchor=tk.W, *args, **kwargs, ) self.pack(side=tk.BOTTOM, fill=tk.X) def set_status_text(self, text: str): """Sets the text displayed in the status bar.""" try: if self.winfo_exists(): self.config(text=text) except Exception: pass # Ignore errors during status update def create_main_window( title: str, min_width: int, min_height: int, x_pos: int, y_pos: int ) -> tk.Tk: """Creates and configures the main Tkinter root window.""" log_prefix = "[UI Setup]" try: root = tk.Tk() root.title(title) root.minsize(min_width, min_height) root.geometry(f"+{x_pos}+{y_pos}") logging.debug( f"{log_prefix} Main Tkinter root window created at ({x_pos},{y_pos})." ) return root except Exception as e: logging.critical( f"{log_prefix} Failed to create main Tkinter window:", exc_info=True ) raise # --- END OF FILE ui.py ---