SXXXXXXX_FlightMonitor/flightmonitor/gui/panels/data_logging_panel.py
2025-06-17 08:52:39 +02:00

247 lines
9.3 KiB
Python

# 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()