SXXXXXXX_RadarDataReader/radar_data_reader/gui/main_window.py
VALLONGOL 71ada6d3b1 Chore: Stop tracking files based on .gitignore update.
Untracked files matching the following rules:
- Rule "!.vscode/launch.json": 1 file
2025-06-18 13:58:09 +02:00

179 lines
7.6 KiB
Python

"""
Main View for the Radar Data Reader application.
"""
import tkinter as tk
from tkinter import scrolledtext, filedialog, ttk
from pathlib import Path
from typing import Dict, Any
import queue
from ..utils import logger
log = logger.get_logger(__name__)
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 = self.controller.result_queue
self.total_items_for_progress = 0
self._init_vars()
self.pack(fill=tk.BOTH, expand=True)
self.master.title("Radar Data Reader")
self.master.geometry("800x700")
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):
self.filepath_var = tk.StringVar()
self.batch_id_var = tk.StringVar(value="N/A")
self.timetag_var = tk.StringVar(value="N/A")
self.progress_var = tk.DoubleVar(value=0)
self.status_bar_var = tk.StringVar(value="Ready")
def _create_widgets(self):
main_frame = tk.Frame(self)
main_frame.pack(padx=10, pady=10, fill=tk.BOTH, expand=True)
main_frame.rowconfigure(2, weight=1)
main_frame.columnconfigure(0, weight=1)
controls_frame = ttk.LabelFrame(main_frame, text="Controls")
controls_frame.grid(row=0, column=0, sticky="ew", pady=(0, 5))
file_select_frame = ttk.Frame(controls_frame)
file_select_frame.pack(fill=tk.X, padx=5, pady=5)
file_select_frame.columnconfigure(1, weight=1)
ttk.Label(file_select_frame, text="Radar File:").grid(row=0, column=0, padx=(0, 5))
self.file_entry = ttk.Entry(file_select_frame, textvariable=self.filepath_var, state="readonly")
self.file_entry.grid(row=0, column=1, sticky="ew")
self.browse_button = ttk.Button(file_select_frame, text="Browse...", command=self.on_browse_click)
self.browse_button.grid(row=0, column=2, padx=(5, 0))
button_frame = ttk.Frame(controls_frame)
button_frame.pack(fill=tk.X, padx=5, pady=5)
button_frame.columnconfigure(0, weight=1)
button_frame.columnconfigure(1, weight=1)
self.process_button = ttk.Button(button_frame, text="Process File", command=self.on_process_click)
self.process_button.grid(row=0, column=0, sticky="ew", padx=2)
self.stop_button = ttk.Button(button_frame, text="Stop", command=self.on_stop_click, state=tk.DISABLED)
self.stop_button.grid(row=0, column=1, sticky="ew", padx=2)
status_frame = ttk.LabelFrame(main_frame, text="Live Data & Progress")
status_frame.grid(row=1, column=0, sticky="ew", pady=5)
status_frame.columnconfigure(1, weight=1)
status_frame.columnconfigure(3, weight=1)
ttk.Label(status_frame, text="Batch ID:").grid(row=0, column=0, padx=5)
ttk.Label(status_frame, textvariable=self.batch_id_var).grid(row=0, column=1, sticky="w")
ttk.Label(status_frame, text="TimeTag:").grid(row=0, column=2, padx=5)
ttk.Label(status_frame, textvariable=self.timetag_var).grid(row=0, column=3, sticky="w")
self.progress_bar = ttk.Progressbar(status_frame, variable=self.progress_var, maximum=100)
self.progress_bar.grid(row=1, column=0, columnspan=4, sticky="ew", padx=5, pady=5)
log_frame = ttk.LabelFrame(main_frame, text="Log Console")
log_frame.grid(row=2, 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)
self.log_widget.grid(row=0, column=0, sticky="nsew", padx=5, pady=5)
self.status_bar = ttk.Label(self.master, textvariable=self.status_bar_var, relief=tk.SUNKEN, anchor=tk.W, padding=2)
self.status_bar.pack(side=tk.BOTTOM, fill=tk.X)
def _setup_gui_logging(self, logging_config):
logger.add_tkinter_handler(self.log_widget, self.master, logging_config)
def set_filepath(self, path: str):
self.filepath_var.set(path)
def get_filepath(self) -> str:
return self.filepath_var.get()
def ask_open_filename(self, current_path: str) -> str:
initial_dir = Path(current_path).parent if current_path and Path(current_path).exists() else Path.cwd()
return filedialog.askopenfilename(initialdir=initial_dir, filetypes=[("Radar Output", "*.out"), ("All files", "*.*")])
def start_processing_ui(self):
self.update_ui_for_processing_state(True)
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.browse_button.config(state=state)
self.process_button.config(state=state)
self.stop_button.config(state=tk.NORMAL if is_processing else tk.DISABLED)
if is_processing:
self.status_bar_var.set("Processing file... Please wait.")
self.master.config(cursor="watch")
self.batch_id_var.set("Starting...")
self.progress_var.set(0)
else:
self.status_bar_var.set("Ready")
self.progress_var.set(0)
self.batch_id_var.set("Done")
self.master.config(cursor="")
def poll_result_queue(self):
# Process a limited number of messages per call to keep the GUI responsive
for _ in range(100):
try:
msg = self.gui_update_queue.get_nowait()
msg_type = msg.get("type")
if msg_type == "start":
self.total_items_for_progress = msg.get("total", 0)
elif msg_type == "progress":
batch_id = msg.get("batch_id", "N/A")
timetag = msg.get("timetag", "N/A")
log.info(f"Processed Batch ID: {batch_id} (TimeTag: {timetag})")
self.batch_id_var.set(str(batch_id))
self.timetag_var.set(str(timetag))
if self.total_items_for_progress > 0:
progress = (msg.get("blocks_done", 0) / self.total_items_for_progress) * 100
self.progress_var.set(progress)
elif msg_type == "complete":
self.controller.handle_worker_completion(
results=msg.get("results", []),
was_interrupted=msg.get("interrupted", False)
)
self.update_ui_for_processing_state(False)
return
elif msg_type == "error":
log.error(f"Received error from worker: {msg.get('message')}")
self.update_ui_for_processing_state(False)
return
except queue.Empty:
break # No more messages
if self.controller.is_processing:
self.after(100, self.poll_result_queue)
def on_browse_click(self):
self.controller.select_file()
def on_process_click(self):
self.controller.start_processing()
def on_stop_click(self):
self.controller.stop_processing()
def on_close(self):
self.controller.shutdown()
self.master.destroy()