SXXXXXXX_ControlPanel/controlpanel/gui/ui.py
2025-05-06 11:18:50 +02:00

736 lines
28 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. The metadata display components are now created and
managed directly by the main application (ControlPanelApp).
"""
# 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 ScrolledText import as it's no longer created here
# from tkinter.scrolledtext import ScrolledText
# Local application imports
from controlpanel import config
# Type hinting for App reference
if TYPE_CHECKING:
# Assuming ControlPanelApp is defined in ControlPanel.py
from controlpanel.app_main 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")
# Checkbox variable for metadata toggle (still needed here)
self.show_meta_var = tk.BooleanVar(value=self.app.state.display_sar_metadata)
# --- References to UI widgets ---
self.mfd_color_labels: Dict[str, tk.Label] = {}
# References to metadata widgets are REMOVED from this class
# --- 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 (Still created here as it belongs logically with SAR params)
sar_row += 1
# self.show_meta_var is already created in __init__
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 = [
"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
# 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:
intensity_var.set(
self.app.state.mfd_params["categories"][name]["intensity"]
)
except Exception:
pass
intensity_scale = 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())
),
)
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:
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")
color_label.grid(
row=row, column=3 + col_offset, sticky=tk.W, padx=(1, 5), pady=1
)
self.mfd_color_labels[name] = color_label
# Raw Map Intensity Slider
last_cat_row = (num_categories - 1) // 2
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:
raw_map_intensity_var.set(self.app.state.mfd_params["raw_map_intensity"])
except Exception:
pass
self.mfd_raw_map_intensity_var = raw_map_intensity_var # Keep reference
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,
sticky=tk.EW,
padx=(1, 5),
pady=1,
)
# Configure MFD frame column weights
self.mfd_params_frame.columnconfigure(1, weight=1)
self.mfd_params_frame.columnconfigure(5, weight=1)
# --- 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)
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,
)
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)
self.map_params_frame.columnconfigure(4, weight=1)
# --- 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
)
self.ge_all_button.grid(
row=info_row, column=0, columnspan=6, sticky=tk.EW, padx=5, pady=(5, 5)
)
# 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) ---
# This is now created and managed in ControlPanelApp
# --- 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)
# This is less critical now as the app constructor sets the final position
root.geometry(f"+{x_pos}+{y_pos}")
logging.debug(
f"{log_prefix} Main Tkinter root window created (initial 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 ---