SXXXXXXX_ControlPanel/ui.py
2025-04-15 14:06:44 +02:00

755 lines
29 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, info display
with Google Maps/Earth links, and helper functions for window creation.
The main ControlPanel frame is designed to be placed within a container managed
by the main application.
"""
# Standard library imports
import logging
from typing import TYPE_CHECKING, Dict, Tuple, Optional
# Third-party imports
import tkinter as tk
from tkinter import ttk
# Import ScrolledText only if needed within this module (currently not)
# from tkinter.scrolledtext import ScrolledText
# Local application imports
import config
# Type hinting for App reference
if TYPE_CHECKING:
# Assuming ControlPanelApp is defined in ControlPanel.py
from ControlPanel import ControlPanelApp
class ControlPanel(ttk.Frame): # This is the main panel holding user controls
"""
Main control panel frame containing SAR, MFD, Map parameter widgets,
information displays, statistics labels, and interaction buttons.
This frame is typically placed in the main application window's container.
"""
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 UI elements ---
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"
)
# --- References to UI widgets ---
self.mfd_color_labels: Dict[str, tk.Label] = {}
# References to metadata widgets are REMOVED from here
# --- Initialize UI structure ---
self.init_ui() # Call the UI building method
logging.debug(f"{log_prefix} ControlPanel frame initialization complete.")
# --- UI Construction Method ---
def init_ui(self):
"""Initializes and arranges the user interface widgets within this frame."""
log_prefix = "[UI Setup]"
logging.debug(f"{log_prefix} Starting init_ui widget creation...")
# This frame (self) is placed by its parent (container_frame in App)
# DO NOT pack or grid self here.
# --- 1. 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 counter for SAR grid
# Test Image Checkbox
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
)
# Record SAR Checkbox
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
# SAR Size Combobox
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 value based on current state
factor = 1
if self.app.state.sar_display_width > 0:
factor = max(1, config.SAR_WIDTH // self.app.state.sar_display_width)
sz_str = f"1:{factor}"
if sz_str in config.SAR_SIZE_FACTORS:
self.sar_size_combo.set(sz_str)
else:
self.sar_size_combo.set(config.DEFAULT_SAR_SIZE)
except Exception: # Fallback to default if error
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)
# SAR Palette Combobox
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) # Set from state
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
# SAR Contrast Slider
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, # Set from state
command=self.app.update_contrast,
)
self.contrast_scale.grid(
row=sar_row, column=1, sticky=tk.EW, padx=(0, 10), pady=1
)
# SAR Brightness Slider
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, # Set from state
command=self.app.update_brightness,
)
self.brightness_scale.grid(
row=sar_row, column=3, sticky=tk.EW, padx=(0, 5), pady=1
)
# SAR Metadata Checkbox
sar_row += 1
self.show_meta_var = tk.BooleanVar(value=self.app.state.display_sar_metadata)
self.show_meta_check = ttk.Checkbutton(
self.sar_params_frame,
text="Show SAR Metadata",
variable=self.show_meta_var,
command=self.app.toggle_sar_metadata_display # Link to app callback
)
self.show_meta_check.grid(
row=sar_row, column=0, columnspan=4, sticky=tk.W, padx=5, pady=(5, 2)
)
# Configure SAR frame column weights
self.sar_params_frame.columnconfigure(1, weight=1) # Allow sliders to expand
self.sar_params_frame.columnconfigure(3, weight=1)
# --- 2. 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)
mfd_categories_ordered = [ # Order for display
"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 # Two categories per row
col_offset = 0 if (index % 2 == 0) else 4 # Offset for second column
# Category Label
cat_label = ttk.Label(self.mfd_params_frame, text=f"{name}:")
cat_label.grid(
row=row, column=0 + col_offset, sticky=tk.W, padx=(5, 1), pady=1
)
# Intensity Slider Variable and Widget
intensity_var = tk.IntVar(value=config.DEFAULT_MFD_INTENSITY)
try: # Set initial value from state
intensity_var.set(
self.app.state.mfd_params["categories"][name]["intensity"]
)
except Exception:
pass # Ignore if state not ready or key missing
intensity_scale = ttk.Scale(
self.mfd_params_frame,
orient=tk.HORIZONTAL,
length=100,
from_=0,
to=255,
variable=intensity_var,
# Use lambda to pass name and var correctly to callback
command=lambda v, n=name, var=intensity_var: (
self.app.update_mfd_category_intensity(n, var.get())
),
)
intensity_scale.grid(
row=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,
command=lambda n=name: self.app.choose_mfd_category_color(n),
)
color_button.grid(
row=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 state
bgr = self.app.state.mfd_params["categories"][name]["color"]
hex_color = f"#{bgr[2]:02x}{bgr[1]:02x}{bgr[0]:02x}"
color_label.config(background=hex_color)
except Exception:
color_label.config(background="grey") # Fallback
color_label.grid(
row=row, column=3 + col_offset, sticky=tk.W, padx=(1, 5), pady=1
)
self.mfd_color_labels[name] = color_label # Store label reference
# Raw Map Intensity Slider
last_cat_row = (num_categories - 1) // 2
# Determine position based on number of categories
raw_map_col_offset = 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_col_offset, sticky=tk.W, padx=(5, 1), pady=1
)
raw_map_intensity_var = tk.IntVar(value=config.DEFAULT_MFD_RAW_MAP_INTENSITY)
try: # Set initial value from state
raw_map_intensity_var.set(self.app.state.mfd_params["raw_map_intensity"])
except Exception:
pass
# Store var reference if needed elsewhere
self.mfd_raw_map_intensity_var = raw_map_intensity_var
raw_map_scale = ttk.Scale(
self.mfd_params_frame,
orient=tk.HORIZONTAL,
length=100,
from_=0,
to=255,
variable=raw_map_intensity_var,
command=lambda v: self.app.update_mfd_raw_map_intensity(
raw_map_intensity_var.get()
),
)
raw_map_scale.grid(
row=raw_map_row,
column=1 + raw_map_col_offset,
columnspan=3, # Span 3 cols after label
sticky=tk.EW,
padx=(1, 5),
pady=1,
)
# Configure MFD frame column weights
self.mfd_params_frame.columnconfigure(1, weight=1) # Allow sliders to expand
self.mfd_params_frame.columnconfigure(5, weight=1) # Allow sliders on right
# --- 3. 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 counter for Map grid
# Map Size Combobox
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) # Set default initially
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)
# Save Map Button
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
)
map_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
# 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,
)
# Trigger update only on release for performance
self.alpha_scale.bind("<ButtonRelease-1>", 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
)
map_row += 1
# SAR 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_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
)
# Configure Map frame column weights
self.map_params_frame.columnconfigure(2, weight=1) # Allow Lat entry expand
self.map_params_frame.columnconfigure(4, weight=1) # Allow Lon entry expand
# --- 4. 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 # Row counter for Info grid
button_width = 3 # 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
)
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")
)
self.ref_gearth_button.grid(
row=info_row, column=5, sticky=tk.E, padx=(0, 5), pady=1
)
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
)
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
)
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")
)
self.sar_mouse_gearth_button.grid(
row=info_row, column=5, sticky=tk.E, padx=(0, 5), pady=1
)
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
)
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")
)
self.map_mouse_gearth_button.grid(
row=info_row, column=5, sticky=tk.E, padx=(0, 5), pady=1
)
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
)
info_row += 1
# --- Row 5: "GE All" Button ---
self.ge_all_button = ttk.Button(
self.info_display_frame,
text="GE All",
command=self.app.go_to_all_gearth # Link to app callback
)
self.ge_all_button.grid(
row=info_row,
column=0, # Start column 0
columnspan=6, # Span all 6 columns
sticky=tk.EW, # Expand horizontally
padx=5,
pady=(5, 5) # Padding top and bottom
)
# Configure column weights for Info Display frame
self.info_display_frame.columnconfigure(1, weight=1) # Entry column
self.info_display_frame.columnconfigure(3, weight=1) # Entry column
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.")
# --- 5. Metadata Display Frame (Creation REMOVED from here) ---
# The structure is now created in ControlPanelApp.__init__
# --- End of init_ui ---
logging.debug(f"{log_prefix} init_ui widget creation complete.")
# --- UI Update Methods ---
def set_sar_center_coords(self, latitude_str: str, longitude_str: str):
"""Updates the SAR Center coordinates display."""
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):
"""Updates the SAR Mouse coordinates display."""
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):
"""Updates the Map Mouse coordinates display."""
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):
"""Updates the SAR Orientation display."""
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):
"""Updates the SAR Size display."""
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):
"""Updates the statistics display fields."""
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]
):
"""Updates the background color of an MFD category preview label."""
if category_name in self.mfd_color_labels:
lbl = self.mfd_color_labels[category_name]
# Format BGR tuple to hex color string #RRGGBB
hex_color = (
f"#{color_bgr_tuple[2]:02x}"
f"{color_bgr_tuple[1]:02x}"
f"{color_bgr_tuple[0]:02x}"
)
try:
# Update label background if it still exists
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}"
)
# --- StatusBar Class ---
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, # Anchor text to the West (left)
padding=(5, 2), # Add some internal padding
*args,
**kwargs
)
# Packed by the main application layout manager
def set_status_text(self, text: str):
"""Sets the text displayed in the status bar."""
try:
# Update text only if the widget still exists
if self.winfo_exists():
self.config(text=text)
except Exception:
# Ignore errors during status update, especially during shutdown
pass
# --- Window Creation Helper ---
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:
# Create the root window
root = tk.Tk()
# Set window title
root.title(title)
# Set minimum size constraint
root.minsize(min_width, min_height)
# Set initial position (may be overridden by OS window manager)
root.geometry(f"+{x_pos}+{y_pos}")
logging.debug(
f"{log_prefix} Main Tkinter root window created (requested pos: {x_pos},{y_pos})."
)
return root
except Exception as e:
logging.critical(
f"{log_prefix} Failed to create main Tkinter window:", exc_info=True
)
# Re-raise the exception to halt execution if window creation fails
raise
# --- END OF FILE ui.py ---