SXXXXXXX_RadarDataReader/radar_data_reader/gui/main_window.py
VALLONGOL d1b9838555 update readme, manual
add segment definition
2025-07-14 15:10:14 +02:00

708 lines
30 KiB
Python

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