# FlightMonitor/gui/panels/log_status_panel.py """ Panel for displaying application status (semaphore and message) and logs. """ import tkinter as tk from tkinter import ttk from tkinter.scrolledtext import ScrolledText from tkinter import font as tkFont # For checking font availability from typing import Dict, Any, Optional from ...utils.logger import get_logger from ...utils.gui_utils import GUI_STATUS_UNKNOWN, SEMAPHORE_COLOR_STATUS_MAP module_logger = get_logger(__name__) # Constants for semaphore appearance SEMAPHORE_SIZE = 12 SEMAPHORE_PAD = 3 SEMAPHORE_BORDER_WIDTH = 1 SEMAPHORE_TOTAL_SIZE = SEMAPHORE_SIZE + 2 * (SEMAPHORE_PAD + SEMAPHORE_BORDER_WIDTH) class LogStatusPanel: """ Manages the GUI elements for status display (semaphore, message) and logging. """ def __init__(self, parent_frame: ttk.Frame, root_tk_instance: tk.Tk): """ Initializes the LogStatusPanel. Args: parent_frame: The parent ttk.Frame where this panel will be placed. root_tk_instance: The main Tkinter root window instance. """ self.parent_frame = parent_frame self.root_tk = root_tk_instance # Needed for background color self._semaphore_oval_id: Optional[int] = None # --- Status Bar (Semaphore and Message) --- # Le righe seguenti creano il frame per la barra di stato, il canvas del semaforo # e l'etichetta del messaggio di stato. Questa è la prima parte della UI gestita da questo panel. self.status_bar_frame = ttk.Frame(self.parent_frame, padding=(5, 3)) self.status_bar_frame.pack(side=tk.TOP, fill=tk.X, pady=(0, 5)) self.semaphore_canvas = tk.Canvas( self.status_bar_frame, width=SEMAPHORE_TOTAL_SIZE, height=SEMAPHORE_TOTAL_SIZE, bg=self.root_tk.cget("bg"), # Match root background highlightthickness=0, ) self.semaphore_canvas.pack(side=tk.LEFT, padx=(0, 5)) self._create_semaphore_oval() # Chiama il metodo helper per creare il cerchio del semaforo self.status_label = ttk.Label( self.status_bar_frame, text="Status: Initializing..." ) self.status_label.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 2)) # --- Log Area --- # Le righe seguenti creano il frame per l'area di log e il widget di testo scrollabile. # Questa è la seconda parte della UI gestita da questo panel. self.log_frame = ttk.Frame(self.parent_frame, padding=(5, 0, 5, 5)) self.log_frame.pack(side=tk.TOP, fill=tk.BOTH, expand=True, padx=0, pady=0) # Determina il font per il widget di log. Preferisce "Consolas" se disponibile, altrimenti "Courier New". log_font_family = ( "Consolas" if "Consolas" in tkFont.families() else "Courier New" ) self.log_text_widget = ScrolledText( self.log_frame, state=tk.DISABLED, # Inizialmente disabilitato per prevenire input diretto dell'utente height=10, # Altezza di default, la dimensione finale sarà gestita dalla PanedWindow wrap=tk.WORD, # Va a capo le parole font=(log_font_family, 9), relief=tk.SUNKEN, # Effetto visivo "incassato" borderwidth=1, ) self.log_text_widget.pack(fill=tk.BOTH, expand=True, padx=0, pady=0) module_logger.debug("LogStatusPanel initialized.") def _create_semaphore_oval(self): """ Private helper method to create the oval shape for the status semaphore. Called during initialization of the panel. """ # Verifica che il canvas esista e che non sia già stato creato l'ID del semaforo per evitare errori. if not ( self.semaphore_canvas and self.semaphore_canvas.winfo_exists() and hasattr(self, "_semaphore_oval_id") ): module_logger.warning( "Semaphore canvas not ready or attribute missing, cannot create oval." ) return # Calcola le coordinate per il cerchio del semaforo, includendo padding e bordo. x0 = SEMAPHORE_PAD + SEMAPHORE_BORDER_WIDTH y0 = SEMAPHORE_PAD + SEMAPHORE_BORDER_WIDTH x1 = x0 + SEMAPHORE_SIZE y1 = y0 + SEMAPHORE_SIZE # Ottiene il colore iniziale dal dizionario di mappatura degli stati o un colore di default. initial_color = SEMAPHORE_COLOR_STATUS_MAP.get( GUI_STATUS_UNKNOWN, "gray70" ) try: # Crea l'elemento ovale sul canvas e ne memorizza l'ID. self._semaphore_oval_id = self.semaphore_canvas.create_oval( x0, y0, x1, y1, fill=initial_color, outline="gray30", width=SEMAPHORE_BORDER_WIDTH, ) except tk.TclError as e: # Logga un errore se la creazione fallisce (es. widget già distrutto). module_logger.error(f"Error creating semaphore oval: {e}") def update_status_display(self, status_level: str, message: str): """ Updates the semaphore color and status message displayed in the GUI. Args: status_level: The new status level (e.g., GUI_STATUS_OK, GUI_STATUS_ERROR). Used to determine the semaphore color. message: The new status message string to display next to the semaphore. """ # Ottiene il colore corrispondente al livello di stato. Se non trovato, usa un colore di default. color_to_set = SEMAPHORE_COLOR_STATUS_MAP.get( status_level, SEMAPHORE_COLOR_STATUS_MAP.get(GUI_STATUS_UNKNOWN, "gray60") ) # Aggiorna il colore del semaforo sul canvas, se il canvas esiste. if ( self.semaphore_canvas and self.semaphore_canvas.winfo_exists() and self._semaphore_oval_id is not None ): try: self.semaphore_canvas.itemconfig( self._semaphore_oval_id, fill=color_to_set ) except tk.TclError: # Cattura TclError che può accadere durante la chiusura dell'app pass # Aggiorna il testo dell'etichetta di stato, se l'etichetta esiste. current_status_text = f"Status: {message}" if self.status_label and self.status_label.winfo_exists(): try: self.status_label.config(text=current_status_text) except tk.TclError: # Cattura TclError che può accadere durante la chiusura dell'app pass def get_log_widget(self) -> ScrolledText: """ Returns the Tkinter ScrolledText widget used for displaying logs. This method is essential as it allows the external logger setup (from `FlightMonitor.utils.logger`) to attach itself to this specific widget. Returns: The `tkinter.scrolledtext.ScrolledText` widget. """ return self.log_text_widget