# radar_data_reader/gui/main_window.py """ Main View for the Radar Data Reader application. Features a tab for processing .out files and a tab for converting .rec files. """ import tkinter as tk from tkinter import scrolledtext, ttk, messagebox import logging from typing import Dict, Any, List, Optional import queue from .gui_utils import center_window from ..utils import logger from ..core.export_profiles import ExportProfile from .segment_processor_tab import SegmentProcessorTab log = logger.get_logger(__name__) try: from radar_data_reader import _version as wrapper_version WRAPPER_APP_VERSION_STRING = f"{wrapper_version.__version__} ({wrapper_version.GIT_BRANCH}/{wrapper_version.GIT_COMMIT_HASH[:7]})" WRAPPER_BUILD_INFO = f"Wrapper Built: {wrapper_version.BUILD_TIMESTAMP}" except ImportError: WRAPPER_APP_VERSION_STRING = "(Dev Wrapper)" WRAPPER_BUILD_INFO = "Wrapper build time unknown" class MainWindow(tk.Frame): """The main application window (View).""" def __init__(self, master: tk.Tk, controller, logging_config: Dict[str, Any]): super().__init__(master) self.master = master self.controller = controller self.gui_update_queue = controller.result_queue self.total_blocks_for_progress = 0 self.profile_editor_window = None self.rec_config_window = None self.export_config_window = None self.segments_done_count = 0 self._init_vars() self.pack(fill=tk.BOTH, expand=True) self.master.title( f"Radar Data Reader & Processor - {WRAPPER_APP_VERSION_STRING}" ) self.master.geometry("1280x800") self._create_widgets() self._setup_gui_logging(logging_config) self.master.protocol("WM_DELETE_WINDOW", self.on_close) log.info("Main window View initialized.") def _init_vars(self): """Initialize all Tkinter variables.""" self.status_bar_var = tk.StringVar(value="Ready") self.out_filepath_var = tk.StringVar() self.out_output_dir_var = tk.StringVar() self.out_basename_var = tk.StringVar() self.out_output_csv_var = tk.BooleanVar(value=True) self.out_csv_use_tab_var = tk.BooleanVar(value=False) self.out_output_json_var = tk.BooleanVar(value=False) self.out_use_full_path_var = tk.BooleanVar(value=False) self.out_csv_profile_var = tk.StringVar() self.out_json_profile_var = tk.StringVar() self.rec_filepath_var = tk.StringVar() self.rec_file_count_var = tk.IntVar(value=1) self.rec_output_dir_var = tk.StringVar() self.rec_basename_var = tk.StringVar() 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." ) self.analyzer_progress_var = tk.DoubleVar(value=0) self.analyzer_progress_text_var = tk.StringVar(value="N/A") self.aggregate_by_scale_var = tk.BooleanVar(value=True) self.aggregate_by_waveform_var = tk.BooleanVar(value=True) def _create_widgets(self): menu_bar = tk.Menu(self.master) self.master.config(menu=menu_bar) file_menu = tk.Menu(menu_bar, tearoff=0) menu_bar.add_cascade(label="File", menu=file_menu) file_menu.add_command( label="Manage Export Profiles...", command=self.controller.open_profile_editor, ) file_menu.add_separator() file_menu.add_command(label="Exit", command=self.on_close) main_frame = tk.Frame(self) main_frame.pack(padx=10, pady=10, fill=tk.BOTH, expand=True) main_frame.columnconfigure(0, weight=1) main_frame.rowconfigure(1, weight=1) self.notebook = ttk.Notebook(main_frame) self.notebook.grid(row=0, column=0, sticky="nsew", pady=(0, 10)) self.flight_analyzer_tab = ttk.Frame(self.notebook, padding="10") self.segment_processor_tab = SegmentProcessorTab(self.notebook, self.controller) self.out_processor_tab = ttk.Frame(self.notebook, padding="10") self.rec_converter_tab = ttk.Frame(self.notebook, padding="10") self.notebook.add(self.flight_analyzer_tab, text="1. Flight Analyzer") self.notebook.add(self.segment_processor_tab, text="2. Segment Processor") self.notebook.add(self.out_processor_tab, text="3. Single OUT Processor") self.notebook.add(self.rec_converter_tab, text="4. REC to OUT Converter") self._create_flight_analyzer_tab(self.flight_analyzer_tab) self._create_out_processor_tab(self.out_processor_tab) self._create_rec_converter_tab(self.rec_converter_tab) self._create_log_console_frame(main_frame) self.status_bar = ttk.Label( self, textvariable=self.status_bar_var, relief=tk.SUNKEN, anchor=tk.W, padding=2, ) self.status_bar.pack(side=tk.BOTTOM, fill=tk.X) def _create_flight_analyzer_tab(self, parent): parent.columnconfigure(0, weight=1) parent.rowconfigure(4, weight=1) 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) 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) 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) aggregation_frame = ttk.LabelFrame(parent, text="Segment Definition") aggregation_frame.grid(row=1, column=0, sticky="ew", padx=5, pady=5) ttk.Checkbutton( aggregation_frame, text="Create new segment on Scale change", variable=self.aggregate_by_scale_var, ).pack(side=tk.LEFT, padx=10, pady=5) ttk.Checkbutton( aggregation_frame, text="Create new segment on Waveform (WF) change", variable=self.aggregate_by_waveform_var, ).pack(side=tk.LEFT, padx=10, pady=5) action_frame = ttk.Frame(parent) action_frame.grid(row=2, column=0, sticky="ew", padx=5, pady=10) self.start_analysis_button = ttk.Button( action_frame, text="Start Flight Analysis", command=self.controller.start_flight_analysis, state=tk.DISABLED, ) self.start_analysis_button.pack(side=tk.LEFT, padx=(0, 5)) self.open_flight_folder_button = ttk.Button( action_frame, text="Open Flight Folder", command=self.controller.open_current_flight_folder, state=tk.DISABLED, ) self.open_flight_folder_button.pack(side=tk.LEFT, padx=5) info_label = ttk.Label(action_frame, textvariable=self.analyzer_info_var) info_label.pack(side=tk.LEFT, padx=20) progress_frame = ttk.Frame(parent) progress_frame.grid(row=3, column=0, sticky="ew", padx=5, pady=5) progress_frame.columnconfigure(1, weight=1) ttk.Label(progress_frame, text="Analysis Progress:").grid( row=0, column=0, sticky="w" ) self.analyzer_progressbar = ttk.Progressbar( progress_frame, variable=self.analyzer_progress_var ) self.analyzer_progressbar.grid(row=0, column=1, sticky="ew", padx=5) ttk.Label(progress_frame, textvariable=self.analyzer_progress_text_var).grid( row=0, column=2, sticky="w" ) results_frame = ttk.LabelFrame(parent, text="Flight Summary & Segments") results_frame.grid(row=4, column=0, sticky="nsew", padx=5, pady=5) results_frame.columnconfigure(0, weight=1) results_frame.rowconfigure(0, weight=1) self.flight_timeline_tree = ttk.Treeview( results_frame, columns=( "status", "start_batch", "end_batch", "batch_count", "duration", "start_file", "end_file", "file_count", ), show="headings", selectmode="extended", ) self.flight_timeline_tree.heading("status", text="Segment (Mode | Scale | WF)") 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("batch_count", text="Batch Count") self.flight_timeline_tree.heading("duration", text="Duration (s)") self.flight_timeline_tree.heading("start_file", text="Start File") self.flight_timeline_tree.heading("end_file", text="End File") self.flight_timeline_tree.heading("file_count", text="# Files") self.flight_timeline_tree.column("status", width=250, stretch=True) self.flight_timeline_tree.column("start_batch", width=90, anchor="center") self.flight_timeline_tree.column("end_batch", width=90, anchor="center") self.flight_timeline_tree.column("batch_count", width=90, anchor="center") self.flight_timeline_tree.column("duration", width=90, anchor="center") self.flight_timeline_tree.column("start_file", width=200, stretch=True) self.flight_timeline_tree.column("end_file", width=200, stretch=True) self.flight_timeline_tree.column("file_count", width=60, anchor="center") self.flight_timeline_tree.grid(row=0, column=0, sticky="nsew") 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") buttons_frame = ttk.Frame(results_frame) buttons_frame.grid(row=1, column=0, columnspan=2, sticky="ew", pady=5) ttk.Button( buttons_frame, text="Select All", command=self._select_all_segments ).pack(side=tk.LEFT, padx=(0, 5)) ttk.Button( buttons_frame, text="Select None", command=self._deselect_all_segments ).pack(side=tk.LEFT) self.export_segment_button = ttk.Button( buttons_frame, text="Export Selected Segment(s)", state=tk.DISABLED, command=self.controller.start_segment_export, ) self.export_segment_button.pack(side=tk.RIGHT) configure_export_button = ttk.Button( buttons_frame, text="Configure Segment Export...", command=self.controller.open_export_config_editor, ) configure_export_button.pack(side=tk.RIGHT, padx=5) def _select_all_segments(self): children = self.flight_timeline_tree.get_children() self.flight_timeline_tree.selection_set(children) def _deselect_all_segments(self): self.flight_timeline_tree.selection_remove( self.flight_timeline_tree.selection() ) def _create_out_processor_tab(self, parent): parent.columnconfigure(1, weight=1) input_frame = ttk.LabelFrame(parent, text="Input .out File") input_frame.grid(row=0, column=0, columnspan=3, sticky="ew", padx=5, pady=5) input_frame.columnconfigure(1, weight=1) ttk.Label(input_frame, text="File Path:").grid( row=0, column=0, padx=5, pady=5, sticky="w" ) out_file_entry = ttk.Entry( input_frame, textvariable=self.out_filepath_var, state="readonly" ) out_file_entry.grid(row=0, column=1, sticky="ew", padx=5) self.out_browse_button = ttk.Button( input_frame, text="Browse...", command=self.controller.select_out_file ) self.out_browse_button.grid(row=0, column=2, padx=5, pady=5) self.out_filepath_var.trace_add("write", self.controller.on_out_config_changed) output_frame = ttk.LabelFrame(parent, text="Output Configuration") output_frame.grid(row=1, column=0, columnspan=3, sticky="ew", padx=5, pady=5) output_frame.columnconfigure(1, weight=1) ttk.Label(output_frame, text="Output Directory:").grid( row=0, column=0, padx=5, pady=5, sticky="w" ) out_dir_entry = ttk.Entry(output_frame, textvariable=self.out_output_dir_var) out_dir_entry.grid(row=0, column=1, sticky="ew", padx=5) out_dir_buttons_frame = ttk.Frame(output_frame) out_dir_buttons_frame.grid(row=0, column=2, padx=5) ttk.Button( out_dir_buttons_frame, text="Browse...", command=lambda: self.controller.select_output_dir(self.out_output_dir_var), ).pack(side=tk.LEFT) ttk.Button( out_dir_buttons_frame, text="Open...", command=lambda: self.controller.open_folder_from_path( self.out_output_dir_var.get() ), ).pack(side=tk.LEFT, padx=(5, 0)) ttk.Label(output_frame, text="Base Filename:").grid( row=1, column=0, padx=5, pady=5, sticky="w" ) ttk.Entry(output_frame, textvariable=self.out_basename_var).grid( row=1, column=1, columnspan=2, sticky="ew", padx=5 ) formats_frame = ttk.LabelFrame(parent, text="Output Formats & Options") formats_frame.grid(row=2, column=0, columnspan=3, sticky="ew", padx=5, pady=5) formats_frame.columnconfigure(1, weight=1) ttk.Checkbutton( formats_frame, text="Generate .csv file", variable=self.out_output_csv_var ).grid(row=0, column=0, sticky="w", padx=5, pady=2) self.out_csv_profile_combobox = ttk.Combobox( formats_frame, textvariable=self.out_csv_profile_var, state="readonly", width=25, ) self.out_csv_profile_combobox.grid(row=0, column=1, sticky="w", padx=5) ttk.Checkbutton( formats_frame, text="Generate .json file", variable=self.out_output_json_var ).grid(row=1, column=0, sticky="w", padx=5, pady=2) self.out_json_profile_combobox = ttk.Combobox( formats_frame, textvariable=self.out_json_profile_var, state="readonly", width=25, ) self.out_json_profile_combobox.grid(row=1, column=1, sticky="w", padx=5) options_subframe = ttk.Frame(formats_frame) options_subframe.grid(row=0, column=2, rowspan=2, sticky="w", padx=(20, 5)) ttk.Checkbutton( options_subframe, text="Use Tab Separator (CSV)", variable=self.out_csv_use_tab_var, ).pack(anchor="w") ttk.Checkbutton( options_subframe, text="Use Full Path for Headers", variable=self.out_use_full_path_var, ).pack(anchor="w") action_frame = ttk.Frame(parent) action_frame.grid(row=3, column=0, columnspan=3, pady=(10, 0)) self.out_process_button = ttk.Button( action_frame, text="Process .out File", command=self.controller.start_out_processing, ) self.out_process_button.pack(side=tk.LEFT, padx=5) self.out_stop_button = ttk.Button( action_frame, text="Stop", command=self.controller.stop_processing, state=tk.DISABLED, ) self.out_stop_button.pack(side=tk.LEFT, padx=5) self._create_live_data_frame(parent).grid( row=4, column=0, columnspan=3, sticky="ew", padx=5, pady=(10, 0) ) def _create_rec_converter_tab(self, parent): parent.columnconfigure(1, weight=1) input_frame = ttk.LabelFrame(parent, text="Input REC Sequence") input_frame.grid(row=0, column=0, columnspan=3, sticky="ew", padx=5, pady=5) input_frame.columnconfigure(1, weight=1) ttk.Label(input_frame, text="First .rec File:").grid( row=0, column=0, padx=5, pady=5, sticky="w" ) rec_file_entry = ttk.Entry( input_frame, textvariable=self.rec_filepath_var, state="readonly" ) rec_file_entry.grid(row=0, column=1, sticky="ew", padx=5) ttk.Button( input_frame, text="Browse...", command=self.controller.select_rec_file ).grid(row=0, column=2, padx=5) ttk.Label(input_frame, text="Number of Files (/n):").grid( row=1, column=0, padx=5, pady=5, sticky="w" ) ttk.Spinbox( input_frame, from_=1, to=1000, textvariable=self.rec_file_count_var, width=10, ).grid(row=1, column=1, padx=5, pady=5, sticky="w") self.rec_filepath_var.trace_add("write", self.controller.on_rec_config_changed) self.rec_file_count_var.trace_add( "write", self.controller.on_rec_config_changed ) output_frame = ttk.LabelFrame(parent, text="Generated .out File") output_frame.grid(row=1, column=0, columnspan=3, sticky="ew", padx=5, pady=5) output_frame.columnconfigure(1, weight=1) ttk.Label(output_frame, text="Output Directory:").grid( row=0, column=0, padx=5, pady=5, sticky="w" ) rec_dir_entry = ttk.Entry(output_frame, textvariable=self.rec_output_dir_var) rec_dir_entry.grid(row=0, column=1, sticky="ew", padx=5) rec_dir_buttons_frame = ttk.Frame(output_frame) rec_dir_buttons_frame.grid(row=0, column=2, padx=5) ttk.Button( rec_dir_buttons_frame, text="Browse...", command=lambda: self.controller.select_output_dir(self.rec_output_dir_var), ).pack(side=tk.LEFT) ttk.Button( rec_dir_buttons_frame, text="Open...", command=lambda: self.controller.open_folder_from_path( self.rec_output_dir_var.get() ), ).pack(side=tk.LEFT, padx=(5, 0)) ttk.Label(output_frame, text="Generated Filename:").grid( row=1, column=0, padx=5, pady=5, sticky="w" ) ttk.Label( output_frame, textvariable=self.rec_basename_var, relief="sunken", anchor="w", ).grid(row=1, column=1, columnspan=2, sticky="ew", padx=5) action_frame = ttk.Frame(parent) action_frame.grid(row=2, column=0, columnspan=3, pady=(10, 0)) self.rec_convert_button = ttk.Button( action_frame, text="Convert REC to OUT", command=self.controller.start_rec_conversion, ) self.rec_convert_button.pack(side=tk.LEFT, padx=5) self.rec_config_button = ttk.Button( action_frame, text="g_reconverter Advanced Config...", command=self.controller.open_rec_config_editor, ) self.rec_config_button.pack(side=tk.LEFT, padx=5) self.process_generated_out_button = ttk.Button( action_frame, text="Process Generated .out File ->", command=self.controller.process_last_generated_out, state=tk.DISABLED, ) self.process_generated_out_button.pack(side=tk.LEFT, padx=5) def _create_live_data_frame(self, parent): status_frame = ttk.LabelFrame( parent, text="Live Data & Progress (.out Processor)" ) status_frame.columnconfigure(1, weight=1) self.progress_bar = ttk.Progressbar( status_frame, variable=self.progress_bar_var, maximum=100 ) self.progress_bar.grid( row=0, column=0, columnspan=2, sticky="ew", padx=5, pady=(5, 2) ) ttk.Label(status_frame, text="Progress:", anchor="e").grid( row=1, column=0, padx=(10, 5), pady=2, sticky="e" ) ttk.Label(status_frame, textvariable=self.progress_text_var, anchor="w").grid( row=1, column=1, padx=5, pady=2, sticky="w" ) ttk.Label(status_frame, text="Batches Found:", anchor="e").grid( row=2, column=0, padx=(10, 5), pady=2, sticky="e" ) ttk.Label(status_frame, textvariable=self.batches_found_var, anchor="w").grid( row=2, column=1, padx=5, pady=2, sticky="w" ) return status_frame def _create_log_console_frame(self, parent): log_frame = ttk.LabelFrame(parent, text="Log Console") log_frame.grid(row=1, column=0, sticky="nsew", pady=(5, 0)) log_frame.rowconfigure(0, weight=1) log_frame.columnconfigure(0, weight=1) self.log_widget = scrolledtext.ScrolledText( log_frame, state=tk.DISABLED, wrap=tk.WORD, bg="#f0f0f0" ) self.log_widget.grid(row=0, column=0, sticky="nsew", padx=5, pady=5) self.log_widget.tag_config("INFO", foreground="black") self.log_widget.tag_config("ERROR", foreground="red", font=("", 0, "bold")) self.log_widget.tag_config("SUCCESS", foreground="green") self.log_widget.tag_config("WARNING", foreground="orange") self.log_widget.tag_config("DEBUG", foreground="gray") self.log_widget.tag_config("CMD", foreground="blue") def _setup_gui_logging(self, logging_config): logger.add_tkinter_handler(self.log_widget, self.master, logging_config) def update_export_profiles(self, profiles: List[ExportProfile], **kwargs): profile_names = [p.name for p in profiles] if profiles else [] active_profile_name = kwargs.get("active_out_profile", "") self.out_csv_profile_combobox["values"] = profile_names self.out_json_profile_combobox["values"] = profile_names if active_profile_name in profile_names: self.out_csv_profile_var.set(active_profile_name) self.out_json_profile_var.set(active_profile_name) elif profile_names: self.out_csv_profile_var.set(profile_names[0]) self.out_json_profile_var.set(profile_names[0]) else: self.out_csv_profile_var.set("") self.out_json_profile_var.set("") self.segment_processor_tab.update_export_profiles( profile_names, active_profile_name ) def _reset_progress(self): self.analyzer_progress_var.set(0) self.analyzer_progress_text_var.set("N/A") self.progress_bar_var.set(0) self.progress_text_var.set("N/A") self.batches_found_var.set("N/A") self.segments_done_count = 0 if hasattr(self, "segment_processor_tab"): self.segment_processor_tab.progress_var.set(0) self.segment_processor_tab.progress_text_var.set("N/A") def start_processing_ui(self): self.update_ui_for_processing_state(True) self._reset_progress() self.after(100, self.poll_result_queue) def update_ui_for_processing_state(self, is_processing: bool): state = tk.DISABLED if is_processing else tk.NORMAL self.out_browse_button.config(state=state) self.rec_convert_button.config(state=state) self.start_analysis_button.config(state=state) self.export_segment_button.config(state=state) if hasattr(self, "segment_processor_tab"): self.segment_processor_tab.load_segments_button.config(state=state) is_ready_for_batch = ( self.segment_processor_tab.process_button["state"] != tk.DISABLED ) self.segment_processor_tab.process_button.config( state=( tk.NORMAL if not is_processing and is_ready_for_batch else tk.DISABLED ) ) self.out_stop_button.config(state=tk.NORMAL if is_processing else tk.DISABLED) if not is_processing: can_process_generated = ( self.controller.last_generated_out_file and self.controller.last_generated_out_file.exists() ) self.process_generated_out_button.config( state=tk.NORMAL if can_process_generated else tk.DISABLED ) flight_folder = self.controller.flight_analyzer.current_flight_folder_path can_open_folder = flight_folder and flight_folder.exists() self.open_flight_folder_button.config( state=tk.NORMAL if can_open_folder else tk.DISABLED ) else: self.process_generated_out_button.config(state=tk.DISABLED) self.open_flight_folder_button.config(state=tk.DISABLED) if is_processing: self.status_bar_var.set("Processing... Please wait.") self.master.config(cursor="watch") else: self.status_bar_var.set("Ready") self.master.config(cursor="") def update_rec_tab_buttons_state(self, conversion_successful: bool): self.process_generated_out_button.config( state=tk.NORMAL if conversion_successful else tk.DISABLED ) def poll_result_queue(self): try: while not self.gui_update_queue.empty(): msg = self.gui_update_queue.get_nowait() msg_type = msg.get("type") if msg_type == "log": level_str = msg.get("level", "INFO").upper() level_map = { "ERROR": logging.ERROR, "WARNING": logging.WARNING, "SUCCESS": logging.INFO, "DEBUG": logging.DEBUG, } log_level = level_map.get(level_str, logging.INFO) log.log(log_level, f"[C++ Runner] {msg.get('message')}") elif msg_type == "export_log": log.info(f"[C++ Export] {msg.get('message')}") elif msg_type == "start": self.total_blocks_for_progress = msg.get("total", 0) elif msg_type == "file_progress": file_num = msg.get("file_number", 0) total_files = self.controller.total_files_for_analysis if total_files > 0: progress = (file_num / total_files) * 100 self.analyzer_progress_var.set(progress) self.analyzer_progress_text_var.set( f"Analyzing file {file_num} / {total_files}" ) elif msg_type == "segment_progress": self.segments_done_count += 1 total_segs = self.controller.total_segments_for_export if total_segs > 0: progress = (self.segments_done_count / total_segs) * 100 self.analyzer_progress_var.set(progress) self.analyzer_progress_text_var.set( f"Exported {self.segments_done_count} / {total_segs}" ) elif msg_type == "batch_progress": current, total = msg.get("current", 0), msg.get("total", 0) if total > 0: progress = (current / total) * 100 self.segment_processor_tab.progress_var.set(progress) self.segment_processor_tab.progress_text_var.set( f"Processing segment {current} of {total}: {msg.get('segment_name', '')}" ) elif msg_type == "cpp_complete": self.controller.handle_final_analysis_steps() elif msg_type == "data_batch": self.controller.handle_data_batch(msg.get("data")) elif msg_type == "analysis_summary_data": self.controller.handle_analysis_summary_data(msg) elif msg_type in ("success", "complete", "error"): self.analyzer_progress_var.set(100) self.analyzer_progress_text_var.set("Done") if hasattr(self, "segment_processor_tab"): self.segment_processor_tab.progress_var.set(100) self.segment_processor_tab.progress_text_var.set("Finished.") self.controller.handle_worker_completion(msg) except queue.Empty: pass except Exception as e: log.error(f"Error in GUI polling loop: {e}", exc_info=True) if self.controller.is_processing: self.controller.handle_worker_completion( {"type": "error", "message": str(e)} ) if self.controller.is_processing: self.after(100, self.poll_result_queue) def populate_timeline_from_dataframe(self, summary_df: "pd.DataFrame"): if summary_df is None or summary_df.empty: log.warning("Timeline population skipped: dataframe is empty or None.") return self.flight_timeline_tree.delete(*self.flight_timeline_tree.get_children()) for i, row in summary_df.iterrows(): duration_str = f"{row['Duration (s)']:.2f}" self.flight_timeline_tree.insert( "", "end", iid=str(i), values=( row["Segment (Mode | Scale | WF)"], row["Start Batch"], row["End Batch"], row["Batch Count"], duration_str, row["Start File"], row["End File"], row["# Files"], ), ) def on_close(self): if self.controller.is_processing: if messagebox.askyesno( "Confirm Exit", "A process is still running. Are you sure you want to exit?", ): self.controller.shutdown() self.master.destroy() else: self.controller.shutdown() self.master.destroy()