# --- 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("<>", 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("<>", 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("<>", 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("", 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 ---