From 06b1fe79e1a7e62216bcf248da51313fd1c57749 Mon Sep 17 00:00:00 2001 From: VALLONGOL Date: Wed, 25 Jun 2025 15:42:01 +0200 Subject: [PATCH] draft add flight analyzer tab --- config/config.json | 1 + radar_data_reader/core/app_controller.py | 69 +++++++++++++++++++ radar_data_reader/gui/main_window.py | 84 +++++++++++++++++++++++ radar_data_reader/utils/config_manager.py | 1 + 4 files changed, 155 insertions(+) diff --git a/config/config.json b/config/config.json index 4059e03..3628f8c 100644 --- a/config/config.json +++ b/config/config.json @@ -3,6 +3,7 @@ "last_opened_rec_file": "C:/src/____GitProjects/radar_data_reader/_rec/_25-05-15-12-22-52_sata_345.rec", "last_out_output_dir": "C:/src/____GitProjects/radar_data_reader/_rec", "last_rec_output_dir": "C:\\src\\____GitProjects\\radar_data_reader\\_rec", + "last_flight_folder": "//tsclient/F/__DATI_VOLI/Volo12 - Maggio 2025/_rec", "active_out_export_profile_name": "gsp_data", "export_profiles": [ { diff --git a/radar_data_reader/core/app_controller.py b/radar_data_reader/core/app_controller.py index 46e854d..a90d057 100644 --- a/radar_data_reader/core/app_controller.py +++ b/radar_data_reader/core/app_controller.py @@ -16,6 +16,7 @@ from typing import List, Any, Dict, Tuple, Optional from tkinter import filedialog, messagebox import tkinter as tk import ctypes +import threading from ..utils.config_manager import ConfigManager from ..core.file_reader import run_worker_process @@ -532,3 +533,71 @@ class AppController: self.worker_process.terminate() self._close_all_files() logger.shutdown_logging_system() + + def select_and_analyze_flight_folder(self): + """ + Opens a dialog to select a folder and then starts the analysis + in a separate thread to keep the GUI responsive. + """ + initial_dir = self.config_manager.get("last_flight_folder") + if new_dir := filedialog.askdirectory( + initialdir=initial_dir, title="Select Folder with Flight Recordings" + ): + # --- Update GUI immediately to show something is happening --- + self.view.analyzer_rec_folder_var.set(new_dir) + self.view.analyzer_info_var.set("Scanning folder, please wait...") + self.view.start_analysis_button.config(state=tk.DISABLED) + # Force GUI update + self.view.update_idletasks() + + # --- Run the slow analysis in a background thread --- + analysis_thread = threading.Thread( + target=self._analyze_folder_worker, + args=(new_dir,), + daemon=True + ) + analysis_thread.start() + + def _analyze_folder_worker(self, dir_path_str: str): + """ + Worker thread function to perform the slow task of scanning folder contents. + """ + try: + folder_path = Path(dir_path_str) + self.config_manager.set("last_flight_folder", dir_path_str) + self.config_manager.save_config() + + rec_files = sorted([f for f in folder_path.glob("*.rec")]) + + if not rec_files: + self.view.analyzer_info_var.set("No .rec files found in the selected folder.") + # The button remains disabled, which is correct + return + + total_size_bytes = sum(f.stat().st_size for f in rec_files) + total_size_mb = total_size_bytes / (1024 * 1024) + file_count = len(rec_files) + + info_text = ( + f"Found {file_count} .rec files, " + f"Total size: {total_size_mb:.2f} MB. Ready for analysis." + ) + self.view.analyzer_info_var.set(info_text) + + # Generate default flight name + first_file_name = rec_files[0].stem + match = re.search(r"(\d{2})-(\d{2})-(\d{2})-(\d{2})-(\d{2})-(\d{2})", first_file_name) + if match: + yy, mo, dd, hh, mi, ss = match.groups() + flight_name = f"{yy}{mo}{dd}_{hh}{mi}{ss}_Flight" + self.view.analyzer_flight_name_var.set(flight_name) + else: + self.view.analyzer_flight_name_var.set(f"{folder_path.name}_Flight") + + # Enable the analysis button on the main thread + self.view.start_analysis_button.config(state=tk.NORMAL) + + except Exception as e: + log.error(f"Error in folder analysis worker: {e}", exc_info=True) + self.view.analyzer_info_var.set(f"Error during folder analysis: {e}") + self.view.start_analysis_button.config(state=tk.DISABLED) diff --git a/radar_data_reader/gui/main_window.py b/radar_data_reader/gui/main_window.py index 3afd06c..72f445e 100644 --- a/radar_data_reader/gui/main_window.py +++ b/radar_data_reader/gui/main_window.py @@ -75,6 +75,10 @@ class MainWindow(tk.Frame): self.progress_text_var = tk.StringVar(value="N/A") self.batches_found_var = tk.StringVar(value="N/A") self.progress_bar_var = tk.DoubleVar(value=0) + + self.analyzer_rec_folder_var = tk.StringVar() + self.analyzer_flight_name_var = tk.StringVar() + self.analyzer_info_var = tk.StringVar(value="Please select a folder and a flight name.") def _create_widgets(self): menu_bar = tk.Menu(self.master) @@ -98,12 +102,19 @@ class MainWindow(tk.Frame): self.out_processor_tab = ttk.Frame(self.notebook, padding="10") self.rec_converter_tab = ttk.Frame(self.notebook, padding="10") + # --- NUOVA RIGA --- + self.flight_analyzer_tab = ttk.Frame(self.notebook, padding="10") + # --- ORDINE MODIFICATO --- + # Mettiamo il nuovo wizard come prima tab perché è il punto di partenza del workflow + self.notebook.add(self.flight_analyzer_tab, text="Flight Analyzer") self.notebook.add(self.out_processor_tab, text="OUT Processor") self.notebook.add(self.rec_converter_tab, text="REC to OUT Converter") self._create_out_processor_tab(self.out_processor_tab) self._create_rec_converter_tab(self.rec_converter_tab) + # --- NUOVA CHIAMATA --- + self._create_flight_analyzer_tab(self.flight_analyzer_tab) self._create_log_console_frame(main_frame) @@ -115,6 +126,79 @@ class MainWindow(tk.Frame): padding=2, ) self.status_bar.pack(side=tk.BOTTOM, fill=tk.X) + + def _create_flight_analyzer_tab(self, parent): + """Creates the widgets for the new Flight Analyzer wizard tab.""" + parent.columnconfigure(0, weight=1) + # La riga 2 conterrà la timeline, quindi le diamo peso per espandersi + parent.rowconfigure(2, weight=1) + + # --- Frame 1: Input e Setup del Volo --- + setup_frame = ttk.LabelFrame(parent, text="Flight Setup") + setup_frame.grid(row=0, column=0, sticky="ew", padx=5, pady=5) + setup_frame.columnconfigure(1, weight=1) + + # Selezione cartella REC + ttk.Label(setup_frame, text="Recordings Folder:").grid( + row=0, column=0, padx=5, pady=5, sticky="w" + ) + rec_folder_entry = ttk.Entry( + setup_frame, textvariable=self.analyzer_rec_folder_var, state="readonly" + ) + rec_folder_entry.grid(row=0, column=1, sticky="ew", padx=5) + ttk.Button( + setup_frame, text="Browse...", command=self.controller.select_and_analyze_flight_folder).grid(row=0, column=2, padx=5) + + # Nome del Volo + ttk.Label(setup_frame, text="Flight Name:").grid( + row=1, column=0, padx=5, pady=5, sticky="w" + ) + flight_name_entry = ttk.Entry(setup_frame, textvariable=self.analyzer_flight_name_var) + flight_name_entry.grid(row=1, column=1, columnspan=2, sticky="ew", padx=5) + + # --- Frame 2: Azioni e Informazioni --- + action_frame = ttk.Frame(parent) + action_frame.grid(row=1, column=0, sticky="ew", padx=5, pady=10) + + self.start_analysis_button = ttk.Button( + action_frame, text="Start Flight Analysis", command=lambda: print("TODO: Start Analysis"), + state=tk.DISABLED + ) + self.start_analysis_button.pack(side=tk.LEFT) + + # Area per info preliminari + info_label = ttk.Label(action_frame, textvariable=self.analyzer_info_var) + info_label.pack(side=tk.LEFT, padx=20) + + + # --- Frame 3: Risultati e Timeline --- + results_frame = ttk.LabelFrame(parent, text="Flight Summary & Segments") + results_frame.grid(row=2, column=0, sticky="nsew", padx=5, pady=5) + results_frame.columnconfigure(0, weight=1) + results_frame.rowconfigure(0, weight=1) + + # Tabella per la timeline + self.flight_timeline_tree = ttk.Treeview( + results_frame, + columns=("mode", "start_batch", "end_batch", "duration"), + show="headings" + ) + self.flight_timeline_tree.heading("mode", text="Mode") + self.flight_timeline_tree.heading("start_batch", text="Start Batch") + self.flight_timeline_tree.heading("end_batch", text="End Batch") + self.flight_timeline_tree.heading("duration", text="Duration (s)") + self.flight_timeline_tree.grid(row=0, column=0, sticky="nsew") + + # Scrollbar per la tabella + tree_scrollbar = ttk.Scrollbar(results_frame, orient="vertical", command=self.flight_timeline_tree.yview) + self.flight_timeline_tree.configure(yscrollcommand=tree_scrollbar.set) + tree_scrollbar.grid(row=0, column=1, sticky="ns") + + # Bottone per esportare i segmenti + self.export_segment_button = ttk.Button( + results_frame, text="Export Selected Segment(s)", state=tk.DISABLED, command=lambda: print("TODO: Export Segment") # TODO + ) + self.export_segment_button.grid(row=1, column=0, columnspan=2, pady=5) def _create_out_processor_tab(self, parent): parent.columnconfigure(1, weight=1) diff --git a/radar_data_reader/utils/config_manager.py b/radar_data_reader/utils/config_manager.py index 74a3c87..062fb1e 100644 --- a/radar_data_reader/utils/config_manager.py +++ b/radar_data_reader/utils/config_manager.py @@ -79,6 +79,7 @@ class ConfigManager: "last_opened_rec_file": "", "last_out_output_dir": "", "last_rec_output_dir": "", + "last_flight_folder": "", "active_out_export_profile_name": "Default", "export_profiles": [default_export_profile.to_dict()], "cpp_converter_config": default_cpp_config,