# FlightMonitor/gui/panels/data_logging_panel.py """ Defines the GUI panel for controlling raw data logging functionality. This panel is designed to be shared across different monitoring modes. """ import tkinter as tk from tkinter import ttk, filedialog, font as tkFont from typing import Optional, Any from datetime import datetime, timezone, timedelta import time from flightmonitor.utils.logger import get_logger module_logger = get_logger(__name__) class DataLoggingPanel: """ Manages the GUI elements for enabling and configuring raw data logging. """ def __init__(self, parent_frame: ttk.Frame, controller: Any): """ Initializes the DataLoggingPanel. Args: parent_frame: The parent ttk.Frame where this panel will be placed. controller: The application controller instance. """ self.parent_frame = parent_frame self.controller = controller # --- Internal State --- self.session_start_time: Optional[float] = None self.session_query_count: int = 0 self.session_query_rate_sec: Optional[int] = None self._session_update_after_id: Optional[str] = None # --- Tkinter Variables --- self.enable_logging_var = tk.BooleanVar(value=False) self.log_directory_var = tk.StringVar(value="") self.last_query_result_var = tk.StringVar(value="Last query result: N/A") self.session_info_var = tk.StringVar(value="Session not active.") # --- Widget References --- self.log_dir_entry: Optional[ttk.Entry] = None self.browse_button: Optional[ttk.Button] = None self.open_folder_button: Optional[ttk.Button] = None self.summary_table: Optional[ttk.Treeview] = None self._build_ui() module_logger.debug("DataLoggingPanel initialized.") def _build_ui(self): """Builds all the widgets for the panel.""" container = ttk.LabelFrame( self.parent_frame, text="Data Logging Session", padding=10 ) container.pack(fill=tk.X, expand=True, padx=2, pady=5) self.enable_checkbox = ttk.Checkbutton( container, text="Enable Raw Data Logging", variable=self.enable_logging_var, command=self._on_toggle_logging, ) self.enable_checkbox.pack(anchor=tk.W, pady=(0, 5)) dir_frame = ttk.Frame(container) dir_frame.pack(fill=tk.X, expand=True) dir_frame.columnconfigure(1, weight=1) ttk.Label(dir_frame, text="Save to:").grid( row=0, column=0, sticky=tk.W, padx=(0, 5) ) self.log_dir_entry = ttk.Entry( dir_frame, textvariable=self.log_directory_var, state=tk.DISABLED ) self.log_dir_entry.grid(row=0, column=1, sticky=tk.EW) self.browse_button = ttk.Button( dir_frame, text="Browse...", command=self._on_browse_directory, state=tk.DISABLED, ) self.browse_button.grid(row=0, column=2, sticky=tk.E, padx=(5, 2)) self.open_folder_button = ttk.Button( dir_frame, text="Open Folder", command=self._on_open_folder, state=tk.DISABLED, ) self.open_folder_button.grid(row=0, column=3, sticky=tk.E, padx=(2, 0)) info_font = tkFont.Font(family="Helvetica", size=9, slant="italic") last_result_label = ttk.Label( container, textvariable=self.last_query_result_var, font=info_font, foreground="navy", ) last_result_label.pack(anchor=tk.W, pady=(8, 0)) session_info_label = ttk.Label( container, textvariable=self.session_info_var, font=info_font, foreground="darkgreen", ) session_info_label.pack(anchor=tk.W, pady=(0, 2)) table_frame = ttk.Frame(container) table_frame.pack(fill=tk.BOTH, expand=True, pady=(2, 0)) self.summary_table = ttk.Treeview( table_frame, columns=("timestamp", "count"), show="headings", height=4 ) self.summary_table.heading("timestamp", text="Fetch Timestamp (UTC)") self.summary_table.heading("count", text="Aircraft Count") self.summary_table.column("timestamp", width=160, anchor=tk.W) self.summary_table.column("count", width=100, anchor=tk.CENTER) scrollbar = ttk.Scrollbar( table_frame, orient="vertical", command=self.summary_table.yview ) self.summary_table.configure(yscrollcommand=scrollbar.set) scrollbar.pack(side=tk.RIGHT, fill=tk.Y) self.summary_table.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) def _on_toggle_logging(self): is_enabled = self.enable_logging_var.get() new_state = tk.NORMAL if is_enabled else tk.DISABLED if self.log_dir_entry: self.log_dir_entry.config(state=new_state) if self.browse_button: self.browse_button.config(state=new_state) if self.open_folder_button: self.open_folder_button.config(state=new_state) def _on_browse_directory(self): directory = filedialog.askdirectory( title="Select Directory to Save Raw Data", parent=self.parent_frame ) if directory: self.log_directory_var.set(directory) def _on_open_folder(self): directory_path = self.get_log_directory() if self.controller and hasattr(self.controller, "open_log_directory"): self.controller.open_log_directory(directory_path) def session_started(self, start_timestamp: float, query_rate_sec: Optional[int]): """To be called by the controller when a monitoring/download session starts.""" self.session_start_time = start_timestamp self.session_query_count = 0 self.session_query_rate_sec = query_rate_sec if self._session_update_after_id: self.parent_frame.after_cancel(self._session_update_after_id) self._update_session_duration() module_logger.info(f"DataLoggingPanel session timer started at {start_timestamp}.") def session_stopped(self): """To be called by the controller when a session stops.""" if self._session_update_after_id: self.parent_frame.after_cancel(self._session_update_after_id) self._session_update_after_id = None self.session_start_time = None self.session_query_count = 0 self.session_query_rate_sec = None self.session_info_var.set("Session not active.") module_logger.info("DataLoggingPanel session timer stopped.") def _update_session_duration(self): """Periodically updates the session duration label.""" if self.session_start_time is None: return elapsed_seconds = time.time() - self.session_start_time duration_str = str(timedelta(seconds=int(elapsed_seconds))) start_time_str = datetime.fromtimestamp(self.session_start_time, timezone.utc).strftime('%H:%M:%S') rate_str = "" if self.session_query_rate_sec is not None and self.session_query_rate_sec > 0: rate_str = f" (1 per {self.session_query_rate_sec:.0f}s)" session_text = ( f"Session started: {start_time_str} (UTC) | " f"Duration: {duration_str} | " f"Queries: {self.session_query_count}{rate_str}" ) self.session_info_var.set(session_text) self._session_update_after_id = self.parent_frame.after(1000, self._update_session_duration) def increment_query_count(self): """Increments the session query counter.""" self.session_query_count += 1 # The label will be updated by the next scheduled _update_session_duration call def is_logging_enabled(self) -> bool: return self.enable_logging_var.get() def get_log_directory(self) -> str: return self.log_directory_var.get() def add_summary_entry(self, timestamp: float, count: int): if not self.summary_table or not self.summary_table.winfo_exists(): return try: dt_object = datetime.fromtimestamp(timestamp, timezone.utc) time_str = dt_object.strftime("%Y-%m-%d %H:%M:%S") self.summary_table.insert("", 0, values=(time_str, count)) except (ValueError, TypeError, tk.TclError): pass def update_last_query_result(self, timestamp: float, count: int): try: dt_object = datetime.fromtimestamp(timestamp, timezone.utc) time_str = dt_object.strftime("%H:%M:%S") result_text = f"Last query result: {count} aircraft at {time_str} UTC" self.last_query_result_var.set(result_text) except (ValueError, TypeError, tk.TclError): pass def clear_summary_table(self): if not self.summary_table or not self.summary_table.winfo_exists(): return self.last_query_result_var.set("Last query result: N/A") self.session_stopped() try: for item in self.summary_table.get_children(): self.summary_table.delete(item) except tk.TclError: pass def update_settings(self, enabled: bool, directory: str): self.enable_logging_var.set(enabled) self.log_directory_var.set(directory) self._on_toggle_logging()