SXXXXXXX_ControlPanel/ui.py
VALLONGOL f0c49a7934 fix overlay function
add shift sar map
2025-04-14 15:55:12 +02:00

809 lines
33 KiB
Python

# --- 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, SAR info display,
and helper functions for window creation. Uses ttk themed widgets where appropriate
and includes elements for new functionalities like coordinate copying, map interaction,
and SAR overlay shifting. Uses standardized logging prefixes.
"""
# Standard library imports
import logging
from typing import TYPE_CHECKING, Dict, Tuple, Optional
# Third-party imports
import tkinter as tk
from tkinter import ttk
# Removed: from tkinter import colorchooser (color chooser is called from ControlPanelApp)
# Local application imports
import config # Import config for defaults
# Type hinting for App reference
if TYPE_CHECKING:
# Import the specific main application class name
from ControlPanel import ControlPanelApp
class ControlPanel(ttk.Frame):
"""
Represents the main control panel frame containing SAR, MFD, and Map
parameter widgets, information displays (using read-only Entries for copying),
and statistics labels. Initializes UI elements and provides methods for updating
specific UI components like coordinate displays and MFD colors.
"""
def __init__(self, parent: tk.Widget, app: "ControlPanelApp", *args, **kwargs):
"""
Initializes the ControlPanel frame.
Args:
parent (tk.Widget): The parent widget (usually the main window).
app (ControlPanelApp): Reference to the main application instance for callbacks.
"""
log_prefix = "[UI Setup]"
logging.debug(f"{log_prefix} Initializing ControlPanel frame...")
super().__init__(parent, *args, **kwargs)
self.app: "ControlPanelApp" = app # Store reference to the App instance
# --- StringVars for Entry widgets ---
# SAR Info Frame
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")
# --- >>> START OF NEW VARS <<< ---
self.map_mouse_coords_var = tk.StringVar(value="Lat=N/A, Lon=N/A")
# Map Parameters Frame
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}"
)
# --- >>> END OF NEW VARS <<< ---
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 dictionary
self.mfd_color_labels: Dict[str, tk.Label] = {}
# Initialize the 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.
Order: SAR Params, MFD Params, Map Params, SAR Info.
"""
log_prefix = "[UI Setup]"
logging.debug(f"{log_prefix} Starting init_ui widget creation...")
# Configure main frame packing
self.pack(side=tk.TOP, fill=tk.BOTH, expand=True, padx=5, pady=5)
# --- 1. SAR Parameters Frame ---
logging.debug(f"{log_prefix} Creating SAR Parameters frame...")
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
# Row 0: Test Image and Record SAR Checkboxes
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 # -> 1
# Row 1: Size and Palette
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: # Set initial SAR size from AppState
initial_factor = 1
if self.app.state.sar_display_width > 0:
initial_factor = config.SAR_WIDTH // self.app.state.sar_display_width
initial_size_str = f"1:{initial_factor}"
if initial_size_str not in config.SAR_SIZE_FACTORS:
initial_size_str = config.DEFAULT_SAR_SIZE
self.sar_size_combo.set(initial_size_str)
except Exception as e:
logging.warning(f"{log_prefix} Error setting initial SAR size: {e}")
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("<<ComboboxSelected>>", 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("<<ComboboxSelected>>", self.app.update_sar_palette)
sar_row += 1 # -> 2
# Row 2: Contrast and Brightness
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, # Uses the raw value passed by the command
)
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, # Uses the raw value passed by the command
)
self.brightness_scale.grid(
row=sar_row, column=3, sticky=tk.EW, padx=(0, 5), pady=1
)
sar_row += 1 # -> 3
# Configure column weights for expansion in SAR frame
self.sar_params_frame.columnconfigure(1, weight=1)
self.sar_params_frame.columnconfigure(3, weight=1)
logging.debug(f"{log_prefix} SAR Parameters frame created.")
# --- 2. MFD Parameters Frame ---
logging.debug(f"{log_prefix} Creating MFD Parameters frame...")
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)
# Define categories in the desired display order for pairing
mfd_categories_ordered = [
"Occlusion",
"Cat A",
"Cat B",
"Cat C",
"Cat C1",
"Cat C2",
"Cat C3",
]
num_categories = len(mfd_categories_ordered)
logging.debug(f"{log_prefix} Creating compacted MFD category controls...")
for index, internal_name in enumerate(mfd_categories_ordered):
current_row = index // 2
col_offset = 0 if (index % 2 == 0) else 4 # Left pair or right pair
# Label for category
label = ttk.Label(self.mfd_params_frame, text=f"{internal_name}:")
label.grid(
row=current_row, column=0 + col_offset, sticky=tk.W, padx=(5, 1), pady=1
)
# Intensity Slider
intensity_var_name = (
f"mfd_{internal_name.replace(' ', '_').lower()}_intensity_var"
)
initial_intensity = config.DEFAULT_MFD_INTENSITY
try: # Get initial from AppState
initial_intensity = self.app.state.mfd_params["categories"][
internal_name
]["intensity"]
except Exception as e:
logging.warning(
f"{log_prefix} Error getting MFD intensity for {internal_name}: {e}"
)
intensity_var = tk.IntVar(value=initial_intensity)
setattr(self, intensity_var_name, intensity_var)
slider = ttk.Scale(
self.mfd_params_frame,
orient=tk.HORIZONTAL,
length=100,
from_=0,
to=255,
variable=intensity_var,
# Use lambda to pass current value and category name to App callback
command=lambda v, name=internal_name, var=intensity_var: self.app.update_mfd_category_intensity(
name, var.get()
),
)
slider.grid(
row=current_row, column=1 + col_offset, sticky=tk.EW, padx=1, pady=1
)
# Color Chooser Button
color_button = ttk.Button(
self.mfd_params_frame,
text="Color",
width=5,
# Use lambda to pass category name to App callback
command=lambda name=internal_name: self.app.choose_mfd_category_color(
name
),
)
color_button.grid(
row=current_row, column=2 + col_offset, sticky=tk.W, padx=1, pady=1
)
# Color Preview Label
color_label = tk.Label(
self.mfd_params_frame, text="", width=3, relief=tk.SUNKEN, borderwidth=1
)
try: # Set initial color from AppState
initial_bgr = self.app.state.mfd_params["categories"][internal_name][
"color"
]
initial_hex = (
f"#{initial_bgr[2]:02x}{initial_bgr[1]:02x}{initial_bgr[0]:02x}"
)
color_label.config(background=initial_hex)
except Exception as e:
logging.warning(
f"{log_prefix} Error setting MFD color for {internal_name}: {e}"
)
color_label.config(background="grey") # Fallback color
color_label.grid(
row=current_row, column=3 + col_offset, sticky=tk.W, padx=(1, 5), pady=1
)
self.mfd_color_labels[internal_name] = color_label # Store label reference
# Place Raw Map slider
last_cat_row = (num_categories - 1) // 2
raw_map_start_col = 4 if (num_categories % 2 != 0) else 0
raw_map_row = last_cat_row if (num_categories % 2 != 0) else last_cat_row + 1
raw_map_label = ttk.Label(self.mfd_params_frame, text="Raw Map:")
raw_map_label.grid(
row=raw_map_row,
column=0 + raw_map_start_col,
sticky=tk.W,
padx=(5, 1),
pady=1,
)
initial_raw_intensity = config.DEFAULT_MFD_RAW_MAP_INTENSITY
try:
initial_raw_intensity = self.app.state.mfd_params["raw_map_intensity"]
except Exception as e:
logging.warning(f"{log_prefix} Error getting raw map intensity: {e}")
self.mfd_raw_map_intensity_var = tk.IntVar(value=initial_raw_intensity)
raw_map_slider = ttk.Scale(
self.mfd_params_frame,
orient=tk.HORIZONTAL,
length=100,
from_=0,
to=255,
variable=self.mfd_raw_map_intensity_var,
command=lambda v: self.app.update_mfd_raw_map_intensity(
self.mfd_raw_map_intensity_var.get()
),
)
raw_map_slider.grid(
row=raw_map_row,
column=1 + raw_map_start_col,
columnspan=3,
sticky=tk.EW,
padx=(1, 5),
pady=1,
)
# Configure column weights for sliders in MFD frame
self.mfd_params_frame.columnconfigure(1, weight=1)
self.mfd_params_frame.columnconfigure(
5, weight=1
) # Slider column for right pair
logging.debug(f"{log_prefix} MFD Parameters frame created.")
# --- 3. Map Parameters Frame ---
logging.debug(f"{log_prefix} Creating Map Parameters frame...")
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
# Row 0: Map Display Size
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("<<ComboboxSelected>>", self.app.update_map_size)
# --- >>> START OF NEW CODE FOR SAVE BUTTON <<< ---
# Row 0, Col 2/3: Save Map View Button
self.save_map_button = ttk.Button(
self.map_params_frame,
text="Save Map View",
command=self.app.save_current_map_view, # New callback in App/ControlPanel
)
self.save_map_button.grid(
row=map_row, column=2, columnspan=2, sticky=tk.E, padx=5, pady=1
)
# --- >>> END OF NEW CODE FOR SAVE BUTTON <<< ---
map_row += 1 # -> 1
# Row 1: SAR Overlay Checkbox
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 # -> 2
# Row 2: SAR Overlay Alpha Slider
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,
# Remove command, use variable tracing or button release binding in ControlPanel
# command=self.app.update_sar_overlay_alpha,
)
# Bind release event (will be handled in ControlPanelApp)
self.alpha_scale.bind("<ButtonRelease-1>", self.app.on_alpha_slider_release)
self.alpha_scale.grid(
row=map_row, column=1, columnspan=3, sticky=tk.EW, padx=(0, 5), pady=1
)
map_row += 1 # -> 3
# --- >>> START OF NEW CODE FOR SHIFT <<< ---
# Row 3: SAR Overlay Shift Inputs and Apply Button
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_shift_label = ttk.Label(self.map_params_frame, text="Lat:")
lat_shift_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_shift_label = ttk.Label(self.map_params_frame, text="Lon:")
lon_shift_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, # New callback in App/ControlPanel
)
self.apply_shift_button.grid(
row=map_row, column=5, sticky=tk.E, padx=(5, 5), pady=1
)
map_row += 1 # -> 4
# --- >>> END OF NEW CODE FOR SHIFT <<< ---
# Configure column weights for expansion in Map frame
# Adjust weights based on new layout
self.map_params_frame.columnconfigure(1, weight=0) # Combobox fixed width
self.map_params_frame.columnconfigure(2, weight=1) # Shift entry / Alpha slider
self.map_params_frame.columnconfigure(3, weight=0) # Shift label fixed width
self.map_params_frame.columnconfigure(4, weight=1) # Shift entry / Alpha slider
self.map_params_frame.columnconfigure(5, weight=0) # Apply button fixed width
logging.debug(f"{log_prefix} Map Parameters frame created.")
# --- 4. SAR Info Frame ---
logging.debug(f"{log_prefix} Creating SAR Info frame...")
self.sar_info_frame = ttk.Labelframe(self, text="Info Display", padding=5)
self.sar_info_frame.pack(side=tk.TOP, fill=tk.X, padx=5, pady=2)
info_row = 0
# --- Row 0: Image Ref Coords (Entry + Button) ---
ref_label = ttk.Label(self.sar_info_frame, text="Image Ref:")
ref_label.grid(row=info_row, column=0, sticky=tk.W, padx=5, pady=1)
self.sar_center_entry = ttk.Entry(
self.sar_info_frame,
textvariable=self.sar_center_coords_var,
state="readonly", # Make it read-only but selectable
width=40, # Adjust width as needed
)
self.sar_center_entry.grid(
row=info_row, column=1, columnspan=3, sticky=tk.EW, padx=(0, 5), pady=1
)
self.ref_gmaps_button = ttk.Button(
self.sar_info_frame,
text="Go",
width=3,
command=lambda: self.app.go_to_google_maps("sar_center"), # New callback
)
self.ref_gmaps_button.grid(
row=info_row, column=4, sticky=tk.E, padx=(0, 5), pady=1
)
info_row += 1
# --- Row 1: Image Orient & Image Size (Read-only Entries) ---
orient_label = ttk.Label(self.sar_info_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.sar_info_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.sar_info_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.sar_info_frame,
textvariable=self.sar_size_km_var,
state="readonly",
width=25,
)
self.sar_size_entry.grid(
row=info_row, column=3, columnspan=2, sticky=tk.EW, padx=(0, 5), pady=1
)
info_row += 1
# --- Row 2: SAR Mouse Coords (Entry + Button) ---
sar_mouse_label = ttk.Label(self.sar_info_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.sar_info_frame,
textvariable=self.mouse_coords_var,
state="readonly",
width=40,
)
self.mouse_latlon_entry.grid(
row=info_row, column=1, columnspan=3, sticky=tk.EW, padx=(0, 5), pady=1
)
self.sar_mouse_gmaps_button = ttk.Button(
self.sar_info_frame,
text="Go",
width=3,
command=lambda: self.app.go_to_google_maps("sar_mouse"), # New callback
)
self.sar_mouse_gmaps_button.grid(
row=info_row, column=4, sticky=tk.E, padx=(0, 5), pady=1
)
info_row += 1
# --- >>> START OF NEW ROW FOR MAP MOUSE <<< ---
# --- Row 3: Map Mouse Coords (Entry + Button) ---
map_mouse_label = ttk.Label(self.sar_info_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.sar_info_frame,
textvariable=self.map_mouse_coords_var,
state="readonly",
width=40,
)
self.map_mouse_latlon_entry.grid(
row=info_row, column=1, columnspan=3, sticky=tk.EW, padx=(0, 5), pady=1
)
self.map_mouse_gmaps_button = ttk.Button(
self.sar_info_frame,
text="Go",
width=3,
command=lambda: self.app.go_to_google_maps("map_mouse"), # New callback
)
self.map_mouse_gmaps_button.grid(
row=info_row, column=4, sticky=tk.E, padx=(0, 5), pady=1
)
info_row += 1
# --- >>> END OF NEW ROW FOR MAP MOUSE <<< ---
# --- Row 4: Drop & Incomplete Stats (Read-only Entries) ---
dropped_label_text = ttk.Label(self.sar_info_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.sar_info_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.sar_info_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.sar_info_frame,
textvariable=self.incomplete_stats_var,
state="readonly",
width=15,
)
self.incomplete_entry.grid(
row=info_row, column=3, columnspan=2, sticky=tk.EW, padx=(0, 5), pady=1
)
info_row += 1
# Configure column weights for SAR Info frame
self.sar_info_frame.columnconfigure(1, weight=1)
self.sar_info_frame.columnconfigure(3, weight=1)
self.sar_info_frame.columnconfigure(4, weight=0) # Button column
logging.debug(f"{log_prefix} SAR Info frame created.")
# --- End of init_ui ---
logging.debug(f"{log_prefix} init_ui widget creation complete.")
# --- Methods to update UI displays ---
# --- >>> START OF MODIFIED/NEW SET METHODS <<< ---
def set_sar_center_coords(self, latitude_str: str, longitude_str: str):
"""Updates the SAR Image Ref coordinates Entry widget."""
log_prefix = "[UI Update]"
text_to_display = f"Lat={latitude_str}, Lon={longitude_str}"
try:
if hasattr(self, "sar_center_coords_var"):
self.sar_center_coords_var.set(text_to_display)
else:
logging.warning(f"{log_prefix} sar_center_coords_var not found.")
except tk.TclError as e: # Catch potential errors if widget is destroyed
if not self.app.state.shutting_down:
logging.warning(
f"{log_prefix} Error updating SAR center coords UI (TclError): {e}"
)
except Exception as e:
logging.exception(
f"{log_prefix} Unexpected error updating SAR center coords UI:"
)
def set_mouse_coordinates(self, latitude_str: str, longitude_str: str):
"""Updates the SAR Mouse coordinates Entry widget."""
log_prefix = "[UI Update]"
text_to_display = f"Lat={latitude_str}, Lon={longitude_str}"
try:
if hasattr(self, "mouse_coords_var"):
self.mouse_coords_var.set(text_to_display)
else:
logging.warning(f"{log_prefix} mouse_coords_var not found.")
except tk.TclError as e:
if not self.app.state.shutting_down:
logging.warning(
f"{log_prefix} Error updating mouse coords UI (TclError): {e}"
)
except Exception as e:
logging.exception(
f"{log_prefix} Unexpected error updating mouse coords UI:"
)
def set_map_mouse_coordinates(self, latitude_str: str, longitude_str: str):
"""Updates the Map Mouse coordinates Entry widget."""
log_prefix = "[UI Update]"
text_to_display = f"Lat={latitude_str}, Lon={longitude_str}"
try:
if hasattr(self, "map_mouse_coords_var"):
self.map_mouse_coords_var.set(text_to_display)
else:
logging.warning(f"{log_prefix} map_mouse_coords_var not found.")
except tk.TclError as e:
if not self.app.state.shutting_down:
logging.warning(
f"{log_prefix} Error updating map mouse coords UI (TclError): {e}"
)
except Exception as e:
logging.exception(
f"{log_prefix} Unexpected error updating map mouse coords UI:"
)
# --- >>> END OF MODIFIED/NEW SET METHODS <<< ---
def set_sar_orientation(self, orientation_deg_str: str):
"""Updates the SAR orientation Entry widget."""
log_prefix = "[UI Update]"
try:
if hasattr(self, "sar_orientation_var"):
self.sar_orientation_var.set(orientation_deg_str)
else:
logging.warning(f"{log_prefix} sar_orientation_var not found.")
except tk.TclError as e:
if not self.app.state.shutting_down:
logging.warning(
f"{log_prefix} Error updating SAR orientation UI (TclError): {e}"
)
except Exception as e:
logging.exception(
f"{log_prefix} Unexpected error updating SAR orientation UI:"
)
def set_sar_size_km(self, size_text: str):
"""Updates the SAR image size Entry widget."""
log_prefix = "[UI Update]"
try:
if hasattr(self, "sar_size_km_var"):
self.sar_size_km_var.set(size_text)
else:
logging.warning(f"{log_prefix} sar_size_km_var not found.")
except tk.TclError as e:
if not self.app.state.shutting_down:
logging.warning(
f"{log_prefix} Error updating SAR size UI (TclError): {e}"
)
except Exception as e:
logging.exception(f"{log_prefix} Unexpected error updating SAR size UI:")
def set_statistics_display(self, dropped_text: str, incomplete_text: str):
"""Updates the statistics display Entry widgets."""
log_prefix = "[UI Update Stats]"
try:
if hasattr(self, "dropped_stats_var"):
self.dropped_stats_var.set(dropped_text)
else:
logging.warning(f"{log_prefix} dropped_stats_var not found.")
if hasattr(self, "incomplete_stats_var"):
self.incomplete_stats_var.set(incomplete_text)
else:
logging.warning(f"{log_prefix} incomplete_stats_var not found.")
except tk.TclError as e:
if not self.app.state.shutting_down:
logging.warning(
f"{log_prefix} Error updating statistics UI (TclError): {e}"
)
except Exception as e:
logging.exception(f"{log_prefix} Unexpected error updating statistics UI:")
def update_mfd_color_display(
self, category_name: str, color_bgr_tuple: Tuple[int, int, int]
):
"""Updates the background color of the specified MFD category's display label."""
log_prefix = "[UI Update MFD Color]"
if category_name in self.mfd_color_labels:
target_label = self.mfd_color_labels[category_name]
try:
if target_label.winfo_exists():
# Format BGR tuple to HEX color string (e.g., #RRGGBB)
hex_color = (
f"#{int(color_bgr_tuple[2]):02x}" # Red
f"{int(color_bgr_tuple[1]):02x}" # Green
f"{int(color_bgr_tuple[0]):02x}" # Blue
)
target_label.config(background=hex_color)
except tk.TclError as e:
if not self.app.state.shutting_down:
logging.warning(
f"{log_prefix} Error updating color display for {category_name} (TclError): {e}"
)
except Exception as e:
logging.exception(
f"{log_prefix} Error updating color display for {category_name}:"
)
else:
logging.warning(
f"{log_prefix} Unknown category key for MFD color display: '{category_name}'"
)
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)
logging.debug(f"{log_prefix} StatusBar initialization complete.")
def set_status_text(self, text: str):
"""Sets the text displayed in the status bar. Handles potential Tkinter errors."""
log_prefix = "[UI Status Set]"
try:
# Update config only if widget still exists
if self.winfo_exists():
self.config(text=text)
except tk.TclError as e:
# Log warning if widget is destroyed (common during shutdown)
logging.warning(
f"{log_prefix} Ignoring error setting status bar text (widget destroyed?): {e}"
)
except Exception as e:
# Log other unexpected errors
logging.exception(f"{log_prefix} Unexpected error setting status bar text:")
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]"
logging.debug(f"{log_prefix} Creating main Tkinter root window...")
try:
root = tk.Tk()
root.title(title)
root.minsize(min_width, min_height)
# Set initial position using geometry string
root.geometry(f"+{x_pos}+{y_pos}")
logging.debug(
f"{log_prefix} Main Tkinter root window created and positioned at ({x_pos},{y_pos})."
)
return root
except Exception as e:
logging.critical(
f"{log_prefix} Failed to create main Tkinter window: {e}", exc_info=True
)
raise # Re-raise the exception after logging
# --- END OF FILE ui.py ---