center gui into screen

This commit is contained in:
VALLONGOL 2025-06-24 14:37:36 +02:00
parent 90d09cd005
commit 1527526188
6 changed files with 307 additions and 111 deletions

View File

@ -7,6 +7,7 @@ import logging
from pathlib import Path
from radar_data_reader.gui.main_window import MainWindow
from radar_data_reader.gui.gui_utils import center_window
from radar_data_reader.utils import logger
from radar_data_reader.utils.config_manager import ConfigManager
from radar_data_reader.core.app_controller import AppController
@ -41,6 +42,8 @@ def main():
controller = AppController(config_manager)
root = tk.Tk()
# Hide the window initially to prevent flickering while it's being built
root.withdraw()
logger.setup_basic_logging(root, LOGGING_CONFIG)
@ -50,18 +53,18 @@ def main():
controller.bind_view(view)
# Center the main window on the screen before showing it
center_window(root)
view.mainloop()
except Exception as e:
log_or_print = (
logger.get_logger(__name__) if logger._logging_system_active else print
)
# --- THIS IS THE CORRECTED LINE ---
if callable(log_or_print):
# This case is for the fallback 'print' function
log_or_print(f"A critical error occurred: {e}")
else:
# This case is for the logger object
log_or_print.critical(f"A critical error occurred: {e}", exc_info=True)
if root:

View File

@ -41,14 +41,14 @@ def _get_value_from_path(batch: DataBatch, field: ExportField) -> Any:
return batch.batch_id
# Split the path by dots and brackets to handle attributes and indices
parts = re.split(r'\.|\[' , path)
parts = re.split(r"\.|\[", path)
current_obj = batch
for part in parts:
if current_obj is None:
return "N/A"
if part.endswith(']'):
if part.endswith("]"):
# This is an index access
index_str = part[:-1]
if not index_str.isdigit():
@ -57,7 +57,9 @@ def _get_value_from_path(batch: DataBatch, field: ExportField) -> Any:
try:
current_obj = current_obj[int(index_str)]
except (IndexError, TypeError):
log.warning(f"Index out of bounds for '{index_str}' in path: {path}")
log.warning(
f"Index out of bounds for '{index_str}' in path: {path}"
)
return "N/A"
else:
# This is an attribute access
@ -68,7 +70,7 @@ def _get_value_from_path(batch: DataBatch, field: ExportField) -> Any:
# Handle translation for enums if the final value is an integer
if field.translate_with_enum and isinstance(value, int):
# For enum translation, we need the path without indices
enum_path = re.sub(r'\[\d+\]', '', path)
enum_path = re.sub(r"\[\d+\]", "", path)
enum_class = ENUM_REGISTRY.get(enum_path)
if enum_class:
return get_enum_name(enum_class, value)

View File

@ -503,6 +503,7 @@ class TimerRawIf(CtypesStructureBase):
Mirrors the C++ `timer_raw_if_t` struct (Reduced initial version).
This maps only the first, most stable part of the structure.
"""
_fields_ = [
("tcr", ctypes.c_uint32),
("tpr", ctypes.c_uint32),
@ -513,18 +514,20 @@ class TimerRawIf(CtypesStructureBase):
("spares__", ctypes.c_uint32 * 3),
("shift", ShiftRegisters),
("spare_after_shift", ctypes.c_byte * (12)), # Padding fino a 0x50
# Offset in C++: 0x50
("aesa_delay", UniquePrtFifo * 2), # Dimensione: 64 bytes * 2 = 128 bytes. Va da 0x50 a 0xD0
("spare0__", ctypes.c_byte * 48), # Padding per raggiungere 0x100 (da 0xD0 a 0x100)
(
"aesa_delay",
UniquePrtFifo * 2,
), # Dimensione: 64 bytes * 2 = 128 bytes. Va da 0x50 a 0xD0
(
"spare0__",
ctypes.c_byte * 48,
), # Padding per raggiungere 0x100 (da 0xD0 a 0x100)
# Offset in C++: 0x100
("exp_pulse1_delay", UniquePrtFifo * 2), # da 0x100 a 0x180
("exp_pulse2_delay", UniquePrtFifo * 2), # da 0x180 a 0x200
# Offset in C++: 0x200
("pretrigger_det_delay", UniquePrtFifo * 2), # da 0x200 a 0x280
# Il resto è omesso per ora
]

View File

@ -0,0 +1,55 @@
# radar_data_reader/gui/gui_utils.py
"""
GUI utility functions, such as window centering logic, that can be reused
across different parts of the application or other projects.
"""
import tkinter as tk
from typing import Optional
def center_window(
window: tk.Tk | tk.Toplevel, parent: Optional[tk.Tk | tk.Toplevel] = None
):
"""
Centers a Tkinter window on the screen or relative to a parent window.
This function should be called after the window's initial size has been
set or determined, typically after creating all its widgets. Using
`window.update_idletasks()` right before this call ensures that the
window's dimensions are up-to-date.
Args:
window: The Tkinter window (tk.Tk or tk.Toplevel) to be centered.
parent: The optional parent window. If provided, `window` will be
centered relative to the parent. If None, `window` will be
centered on the screen.
"""
window.update_idletasks() # Ensure window dimensions are calculated
# Get window's dimensions
win_width = window.winfo_width()
win_height = window.winfo_height()
if parent:
# Center relative to the parent window
parent.update_idletasks()
parent_x = parent.winfo_x()
parent_y = parent.winfo_y()
parent_width = parent.winfo_width()
parent_height = parent.winfo_height()
pos_x = parent_x + (parent_width // 2) - (win_width // 2)
pos_y = parent_y + (parent_height // 2) - (win_height // 2)
else:
# Center on the screen
screen_width = window.winfo_screenwidth()
screen_height = window.winfo_screenheight()
pos_x = (screen_width // 2) - (win_width // 2)
pos_y = (screen_height // 2) - (win_height // 2)
# Set the window's position
window.geometry(f"{win_width}x{win_height}+{pos_x}+{pos_y}")
window.deiconify() # Ensure window is visible

View File

@ -6,12 +6,13 @@ Includes a custom dialog for editing complex data paths.
"""
import tkinter as tk
from tkinter import ttk, simpledialog, messagebox
from tkinter import ttk, messagebox
import ctypes
import copy
import re
from typing import List, Type, Dict, Any, Optional
from .gui_utils import center_window
from ..core import data_structures as ds
from ..core.data_enums import ENUM_REGISTRY
from ..core.export_profiles import ExportProfile, ExportField
@ -22,12 +23,14 @@ log = logger.get_logger(__name__)
class EditPathDialog(tk.Toplevel):
"""A custom dialog window to edit a data path string."""
def __init__(self, parent, initial_value=""):
super().__init__(parent)
self.transient(parent)
self.grab_set()
self.title("Edit Data Path")
self.geometry("800x150") # Wide dialog for long paths
self.withdraw() # Hide until ready
self.result = None
@ -35,32 +38,40 @@ class EditPathDialog(tk.Toplevel):
main_frame.pack(fill=tk.BOTH, expand=True)
main_frame.columnconfigure(0, weight=1)
# Instructions Label
instructions = (
"Enter the full data path. Use '.' for attributes and '[index]' for arrays.\n"
"Example 1: main_header.ge_header.mode.master_mode\n"
"Example 2: timer_data.blob.payload.aesa_delay[0].fifo[3]"
)
ttk.Label(main_frame, text=instructions, justify=tk.LEFT).grid(row=0, column=0, sticky="w", pady=(0, 10))
ttk.Label(main_frame, text=instructions, justify=tk.LEFT).grid(
row=0, column=0, sticky="w", pady=(0, 10)
)
# Entry Widget
self.path_var = tk.StringVar(value=initial_value)
self.path_entry = ttk.Entry(main_frame, textvariable=self.path_var, font=("Courier", 10))
self.path_entry = ttk.Entry(
main_frame, textvariable=self.path_var, font=("Courier", 10), width=90
)
self.path_entry.grid(row=1, column=0, sticky="ew")
self.path_entry.focus_set()
self.path_entry.selection_range(0, tk.END)
# Buttons Frame
button_frame = ttk.Frame(main_frame)
button_frame.grid(row=2, column=0, sticky="e", pady=(10, 0))
ttk.Button(button_frame, text="OK", command=self._on_ok, default=tk.ACTIVE).pack(side=tk.LEFT, padx=5)
ttk.Button(button_frame, text="Cancel", command=self._on_cancel).pack(side=tk.LEFT)
ttk.Button(
button_frame, text="OK", command=self._on_ok, default=tk.ACTIVE
).pack(side=tk.LEFT, padx=5)
ttk.Button(button_frame, text="Cancel", command=self._on_cancel).pack(
side=tk.LEFT
)
self.protocol("WM_DELETE_WINDOW", self._on_cancel)
self.bind("<Return>", self._on_ok)
self.bind("<Escape>", self._on_cancel)
# Center the dialog relative to its parent
center_window(self, parent)
def _on_ok(self, event=None):
self.result = self.path_var.get().strip()
self.destroy()
@ -86,19 +97,24 @@ class ProfileEditorWindow(tk.Toplevel):
self.profiles = copy.deepcopy(profiles)
self._original_profiles_dict = {p.name: p.to_dict() for p in self.profiles}
self.withdraw() # Hide until ready
self._init_window()
self._init_vars()
self._create_widgets()
self._populate_available_fields_tree()
self._load_profiles_to_combobox()
# Center the window relative to its parent
center_window(self, self.master)
self.protocol("WM_DELETE_WINDOW", self._on_close)
def _init_window(self):
self.title("Export Profile Editor")
self.geometry("1200x700")
self.transient(self.master)
self.grab_set()
def _init_vars(self):
self.selected_profile_name = tk.StringVar()
@ -114,15 +130,21 @@ class ProfileEditorWindow(tk.Toplevel):
cb_frame = ttk.Frame(profile_mgmt_frame)
cb_frame.grid(row=0, column=0, sticky="ew", padx=5, pady=5)
cb_frame.columnconfigure(0, weight=1)
self.profile_combobox = ttk.Combobox(cb_frame, textvariable=self.selected_profile_name, state="readonly")
self.profile_combobox = ttk.Combobox(
cb_frame, textvariable=self.selected_profile_name, state="readonly"
)
self.profile_combobox.grid(row=0, column=0, sticky="ew")
self.profile_combobox.bind("<<ComboboxSelected>>", self._on_profile_selected)
btn_frame = ttk.Frame(profile_mgmt_frame)
btn_frame.grid(row=1, column=0, sticky="ew", padx=5)
btn_frame.columnconfigure((0, 1), weight=1)
ttk.Button(btn_frame, text="New", command=self._on_new_profile).grid(row=0, column=0, sticky="ew", padx=2)
ttk.Button(btn_frame, text="Delete", command=self._on_delete_profile).grid(row=0, column=1, sticky="ew", padx=2)
ttk.Button(btn_frame, text="New", command=self._on_new_profile).grid(
row=0, column=0, sticky="ew", padx=2
)
ttk.Button(btn_frame, text="Delete", command=self._on_delete_profile).grid(
row=0, column=1, sticky="ew", padx=2
)
fields_frame = ttk.LabelFrame(main_pane, text="Available Fields")
main_pane.add(fields_frame, weight=3)
@ -130,7 +152,9 @@ class ProfileEditorWindow(tk.Toplevel):
fields_frame.columnconfigure(0, weight=1)
self.fields_tree = ttk.Treeview(fields_frame, selectmode="browse")
self.fields_tree.grid(row=0, column=0, sticky="nsew", padx=5, pady=5)
ysb = ttk.Scrollbar(fields_frame, orient="vertical", command=self.fields_tree.yview)
ysb = ttk.Scrollbar(
fields_frame, orient="vertical", command=self.fields_tree.yview
)
self.fields_tree.configure(yscrollcommand=ysb.set)
ysb.grid(row=0, column=1, sticky="ns")
@ -143,22 +167,37 @@ class ProfileEditorWindow(tk.Toplevel):
action_btn_frame.grid(row=0, column=0, sticky="ns", padx=5, pady=5)
ttk.Button(action_btn_frame, text=">>", command=self._add_field).grid(pady=5)
ttk.Button(action_btn_frame, text="<<", command=self._remove_field).grid(pady=5)
ttk.Button(action_btn_frame, text="Up", command=lambda: self._move_field(-1)).grid(pady=10)
ttk.Button(action_btn_frame, text="Down", command=lambda: self._move_field(1)).grid(pady=5)
ttk.Button(action_btn_frame, text="Edit Path", command=self._edit_selected_field_path).grid(pady=10)
ttk.Button(action_btn_frame, text="Reset", command=self._clear_selected_fields).grid(pady=5)
ttk.Button(
action_btn_frame, text="Up", command=lambda: self._move_field(-1)
).grid(pady=10)
ttk.Button(
action_btn_frame, text="Down", command=lambda: self._move_field(1)
).grid(pady=5)
ttk.Button(
action_btn_frame, text="Edit Path", command=self._edit_selected_field_path
).grid(pady=10)
ttk.Button(
action_btn_frame, text="Reset", command=self._clear_selected_fields
).grid(pady=5)
selected_fields_frame = ttk.LabelFrame(selected_frame_container, text="Selected Fields for Profile")
selected_fields_frame = ttk.LabelFrame(
selected_frame_container, text="Selected Fields for Profile"
)
selected_fields_frame.grid(row=0, column=1, sticky="nsew")
selected_fields_frame.rowconfigure(0, weight=1)
selected_fields_frame.columnconfigure(0, weight=1)
self.selected_tree = ttk.Treeview(selected_fields_frame, columns=("display_name", "data_path", "translate"), show="headings", selectmode="browse")
self.selected_tree = ttk.Treeview(
selected_fields_frame,
columns=("display_name", "data_path", "translate"),
show="headings",
selectmode="browse",
)
self.selected_tree.heading("display_name", text="Field Name")
self.selected_tree.heading("data_path", text="Source Path")
self.selected_tree.heading("translate", text="Translate")
self.selected_tree.column("display_name", width=150, stretch=True)
self.selected_tree.column("data_path", width=300, stretch=True) # Widened column
self.selected_tree.column("data_path", width=300, stretch=True)
self.selected_tree.column("translate", width=80, anchor="center", stretch=False)
self.selected_tree.grid(row=0, column=0, sticky="nsew")
self.selected_tree.bind("<Button-1>", self._on_selected_tree_click)
@ -166,103 +205,163 @@ class ProfileEditorWindow(tk.Toplevel):
bottom_frame = ttk.Frame(self)
bottom_frame.pack(fill=tk.X, padx=10, pady=(0, 10))
ttk.Button(bottom_frame, text="Save & Close", command=self._on_save_and_close).pack(side=tk.RIGHT)
ttk.Button(bottom_frame, text="Cancel", command=self._on_close).pack(side=tk.RIGHT, padx=5)
ttk.Button(
bottom_frame, text="Save & Close", command=self._on_save_and_close
).pack(side=tk.RIGHT)
ttk.Button(bottom_frame, text="Cancel", command=self._on_close).pack(
side=tk.RIGHT, padx=5
)
def _on_selected_tree_double_click(self, event):
"""Handle double-click on the selected fields tree to edit path."""
item = self.selected_tree.identify_row(event.y)
if item:
self._edit_selected_field_path()
def _edit_selected_field_path(self):
"""Allows manual editing of the data_path for the selected field."""
selection = self.selected_tree.selection()
if not selection:
messagebox.showinfo("No Selection", "Please select a field to edit.", parent=self)
messagebox.showinfo(
"No Selection", "Please select a field to edit.", parent=self
)
return
index = int(selection[0])
profile = self._get_current_profile()
if not profile: return
if not profile:
return
field = profile.fields[index]
dialog = EditPathDialog(self, initial_value=field.data_path)
new_path = dialog.wait_for_input()
if new_path is not None and new_path != field.data_path:
# Basic validation for the path format
if not re.match(r'^[\w\.\[\]]+$', new_path):
messagebox.showerror("Invalid Path", "The path contains invalid characters.", parent=self)
if not re.match(r"^[\w\.\[\]]+$", new_path):
messagebox.showerror(
"Invalid Path", "The path contains invalid characters.", parent=self
)
return
field.data_path = new_path
self._load_profile_into_ui()
def _on_selected_tree_click(self, event):
region = self.selected_tree.identify_region(event.x, event.y)
if region != "cell": return
if region != "cell":
return
column_id = self.selected_tree.identify_column(event.x)
if column_id != "#3": return
if column_id != "#3":
return
item_id = self.selected_tree.identify_row(event.y)
if not item_id: return
if not item_id:
return
profile = self._get_current_profile()
if not profile: return
if not profile:
return
field_index = int(item_id)
field = profile.fields[field_index]
if field.data_path in ENUM_REGISTRY or re.sub(r'\[\d+\]', '', field.data_path) in ENUM_REGISTRY:
base_path = re.sub(r"\[\d+\]", "", field.data_path)
if base_path in ENUM_REGISTRY:
field.translate_with_enum = not field.translate_with_enum
self._load_profile_into_ui()
def _load_profile_into_ui(self):
for i in self.selected_tree.get_children(): self.selected_tree.delete(i)
for i in self.selected_tree.get_children():
self.selected_tree.delete(i)
profile = self._get_current_profile()
if not profile: return
if not profile:
return
for index, field in enumerate(profile.fields):
base_path = re.sub(r'\[\d+\]', '', field.data_path)
base_path = re.sub(r"\[\d+\]", "", field.data_path)
is_translatable = base_path in ENUM_REGISTRY
checkbox_char = ""
if is_translatable: checkbox_char = "" if field.translate_with_enum else ""
self.selected_tree.insert("", "end", iid=str(index), values=(field.column_name, field.data_path, checkbox_char))
if is_translatable:
checkbox_char = "" if field.translate_with_enum else ""
self.selected_tree.insert(
"",
"end",
iid=str(index),
values=(field.column_name, field.data_path, checkbox_char),
)
def _clear_selected_fields(self):
"""Clears all fields from the currently selected profile."""
profile = self._get_current_profile()
if not profile or not profile.fields: return
if messagebox.askyesno("Confirm Clear", f"Are you sure you want to remove all fields from the profile '{profile.name}'?", parent=self):
if not profile or not profile.fields:
return
if messagebox.askyesno(
"Confirm Clear",
f"Are you sure you want to remove all fields from the profile '{profile.name}'?",
parent=self,
):
profile.fields.clear()
self._load_profile_into_ui()
# --- Other methods remain unchanged ---
def _populate_available_fields_tree(self):
self.fields_tree.delete(*self.fields_tree.get_children())
batch_root = self.fields_tree.insert("", "end", iid="batch_properties", text="Batch Properties")
self.fields_tree.insert(batch_root, "end", iid="batch_id", text="batch_id", values=("batch_id", "batch_id"))
header_root = self.fields_tree.insert("", "end", iid="header_data", text="Header Data (from DSPHDRIN)")
self._recursive_populate_tree_ctypes(ds.GeHeader, header_root, "main_header.ge_header")
cdpsts_root = self.fields_tree.insert("", "end", iid="cdpsts_data", text="CDP/STS Block Data")
self._recursive_populate_tree_ctypes(ds.CdpDataLayout, cdpsts_root, "cdp_sts_results.payload.data")
timer_root = self.fields_tree.insert("", "end", iid="timer_data", text="Timer Block Data")
self._recursive_populate_tree_ctypes(ds.GrifoTimerBlob, timer_root, "timer_data.blob")
batch_root = self.fields_tree.insert(
"", "end", iid="batch_properties", text="Batch Properties"
)
self.fields_tree.insert(
batch_root,
"end",
iid="batch_id",
text="batch_id",
values=("batch_id", "batch_id"),
)
header_root = self.fields_tree.insert(
"", "end", iid="header_data", text="Header Data (from DSPHDRIN)"
)
self._recursive_populate_tree_ctypes(
ds.GeHeader, header_root, "main_header.ge_header"
)
cdpsts_root = self.fields_tree.insert(
"", "end", iid="cdpsts_data", text="CDP/STS Block Data"
)
self._recursive_populate_tree_ctypes(
ds.CdpDataLayout, cdpsts_root, "cdp_sts_results.payload.data"
)
timer_root = self.fields_tree.insert(
"", "end", iid="timer_data", text="Timer Block Data"
)
self._recursive_populate_tree_ctypes(
ds.GrifoTimerBlob, timer_root, "timer_data.blob"
)
def _recursive_populate_tree_ctypes(self, class_obj: Type[ctypes.Structure], parent_id: str, base_path: str):
if not hasattr(class_obj, '_fields_'): return
def _recursive_populate_tree_ctypes(
self, class_obj: Type[ctypes.Structure], parent_id: str, base_path: str
):
if not hasattr(class_obj, "_fields_"):
return
for field_name, field_type in class_obj._fields_:
current_path = f"{base_path}.{field_name}"
node_id = f"{parent_id}_{field_name}"
if hasattr(field_type, '_fields_'):
child_node = self.fields_tree.insert(parent_id, "end", iid=node_id, text=field_name)
self._recursive_populate_tree_ctypes(field_type, child_node, current_path)
elif hasattr(field_type, '_length_'):
self.fields_tree.insert(parent_id, "end", iid=node_id, text=f"{field_name} [Array]", values=(field_name, current_path))
if hasattr(field_type, "_fields_"):
child_node = self.fields_tree.insert(
parent_id, "end", iid=node_id, text=field_name
)
self._recursive_populate_tree_ctypes(
field_type, child_node, current_path
)
elif hasattr(field_type, "_length_"):
self.fields_tree.insert(
parent_id,
"end",
iid=node_id,
text=f"{field_name} [Array]",
values=(field_name, current_path),
)
else:
display_text = f"{field_name}"
if current_path in ENUM_REGISTRY: display_text += " (Enum)"
self.fields_tree.insert(parent_id, "end", iid=node_id, text=display_text, values=(field_name, current_path))
if current_path in ENUM_REGISTRY:
display_text += " (Enum)"
self.fields_tree.insert(
parent_id,
"end",
iid=node_id,
text=display_text,
values=(field_name, current_path),
)
def _load_profiles_to_combobox(self):
profile_names = [p.name for p in self.profiles]
self.profile_combobox["values"] = profile_names
if profile_names: self.selected_profile_name.set(profile_names[0])
if profile_names:
self.selected_profile_name.set(profile_names[0])
self._load_profile_into_ui()
def _get_current_profile(self) -> Optional[ExportProfile]:
@ -274,46 +373,63 @@ class ProfileEditorWindow(tk.Toplevel):
def _add_field(self):
selected_item_id = self.fields_tree.focus()
if not selected_item_id: return
if not selected_item_id:
return
item_values = self.fields_tree.item(selected_item_id, "values")
if not item_values or len(item_values) < 2:
messagebox.showinfo("Cannot Add Field", "Please select a specific data field.", parent=self)
messagebox.showinfo(
"Cannot Add Field", "Please select a specific data field.", parent=self
)
return
column_name, data_path = item_values
profile = self._get_current_profile()
if not profile: return
if not profile:
return
if any(f.data_path == data_path for f in profile.fields):
messagebox.showinfo("Duplicate Field", "This field is already in the profile.", parent=self)
messagebox.showinfo(
"Duplicate Field", "This field is already in the profile.", parent=self
)
return
profile.fields.append(ExportField(column_name=column_name, data_path=data_path))
self._load_profile_into_ui()
def _remove_field(self):
selection = self.selected_tree.selection()
if not selection: return
if not selection:
return
index_to_remove = int(selection[0])
profile = self._get_current_profile()
if not profile: return
if not profile:
return
del profile.fields[index_to_remove]
self._load_profile_into_ui()
def _move_field(self, direction: int):
selection = self.selected_tree.selection()
if not selection: return
if not selection:
return
index = int(selection[0])
new_index = index + direction
profile = self._get_current_profile()
if not profile or not (0 <= new_index < len(profile.fields)): return
if not profile or not (0 <= new_index < len(profile.fields)):
return
fields = profile.fields
fields.insert(new_index, fields.pop(index))
self._load_profile_into_ui()
self.selected_tree.selection_set(str(new_index))
def _on_new_profile(self):
name = simpledialog.askstring("New Profile", "Enter a name for the new profile:", parent=self)
if not name or not name.strip(): return
name = simpledialog.askstring(
"New Profile", "Enter a name for the new profile:", parent=self
)
if not name or not name.strip():
return
if any(p.name == name for p in self.profiles):
messagebox.showerror("Error", f"A profile with the name '{name}' already exists.", parent=self)
messagebox.showerror(
"Error",
f"A profile with the name '{name}' already exists.",
parent=self,
)
return
new_profile = ExportProfile(name=name.strip())
self.profiles.append(new_profile)
@ -323,8 +439,13 @@ class ProfileEditorWindow(tk.Toplevel):
def _on_delete_profile(self):
profile = self._get_current_profile()
if not profile: return
if messagebox.askyesno("Confirm Delete", f"Are you sure you want to delete the profile '{profile.name}'?", parent=self):
if not profile:
return
if messagebox.askyesno(
"Confirm Delete",
f"Are you sure you want to delete the profile '{profile.name}'?",
parent=self,
):
self.profiles.remove(profile)
self._load_profiles_to_combobox()
@ -339,8 +460,14 @@ class ProfileEditorWindow(tk.Toplevel):
def _on_close(self):
if self._check_unsaved_changes():
response = messagebox.askyesnocancel("Unsaved Changes", "You have unsaved changes. Would you like to save them?", parent=self)
if response is True: self._on_save_and_close()
elif response is False: self.destroy()
response = messagebox.askyesnocancel(
"Unsaved Changes",
"You have unsaved changes. Would you like to save them?",
parent=self,
)
if response is True:
self._on_save_and_close()
elif response is False:
self.destroy()
else:
self.destroy()

View File

@ -9,6 +9,8 @@ from tkinter import ttk, filedialog
from typing import Dict, Any
import os
from .gui_utils import center_window
class RecConfigWindow(tk.Toplevel):
"""A Toplevel window for managing g_reconvert.exe parameters."""
@ -18,18 +20,22 @@ class RecConfigWindow(tk.Toplevel):
self.controller = controller
self.config_data = current_config.copy()
self.withdraw() # Hide window until it's ready to be shown
self._init_window()
self._init_vars()
self._populate_vars_from_config()
self._create_widgets()
# Center the window relative to its parent
center_window(self, self.master)
self.protocol("WM_DELETE_WINDOW", self._on_cancel)
def _init_window(self):
self.title("g_reconverter Advanced Configuration")
self.geometry("800x650") # Increased height slightly
self.geometry("800x650")
self.transient(self.master)
self.grab_set()
# The centering is now handled by the utility function
def _init_vars(self):
"""Initializes all Tkinter variables for the parameters."""