Refactor AutoMatchGUI and ModifyIdentifiedGUI for improved unsaved changes handling

This commit enhances the AutoMatchGUI and ModifyIdentifiedGUI classes by refining the logic for handling unsaved changes when quitting. The AutoMatchGUI now uses a simplified messagebox for unsaved changes, while the ModifyIdentifiedGUI introduces a similar prompt to warn users about pending changes. Additionally, the logic for managing matched IDs has been updated to ensure consistency in face identification. These improvements aim to enhance user experience by providing clearer warnings and preserving user actions more effectively.
This commit is contained in:
tanyar09 2025-10-06 12:25:44 -04:00
parent ac546a09e0
commit 70cb11adbd
2 changed files with 44 additions and 85 deletions

View File

@ -305,8 +305,9 @@ class AutoMatchGUI:
def on_confirm_matches():
nonlocal identified_count, current_matched_index, identified_faces_per_person
if current_matched_index < len(matched_ids):
matched_id = matched_ids[current_matched_index]
active_ids = filtered_matched_ids if filtered_matched_ids is not None else matched_ids
if current_matched_index < len(active_ids):
matched_id = active_ids[current_matched_index]
matches_for_this_person = matches_by_matched[matched_id]
# Initialize identified faces for this person if not exists
@ -438,87 +439,22 @@ class AutoMatchGUI:
def on_quit_auto_match():
nonlocal window_destroyed
# Check for unsaved changes before quitting
if has_unsaved_changes():
# Show warning dialog with custom width
from tkinter import messagebox
# Create a custom dialog for better width control
dialog = tk.Toplevel(root)
dialog.title("Unsaved Changes")
dialog.geometry("500x250")
dialog.resizable(True, True)
dialog.transient(root)
dialog.grab_set()
# Center the dialog
dialog.geometry("+%d+%d" % (root.winfo_rootx() + 50, root.winfo_rooty() + 50))
# Main message
message_frame = ttk.Frame(dialog, padding="20")
message_frame.pack(fill=tk.BOTH, expand=True)
# Warning icon and text
icon_label = ttk.Label(message_frame, text="⚠️", font=("Arial", 16))
icon_label.pack(anchor=tk.W)
main_text = ttk.Label(message_frame,
text="You have unsaved changes that will be lost if you quit.",
font=("Arial", 10))
main_text.pack(anchor=tk.W, pady=(5, 10))
# Options
options_text = ttk.Label(message_frame,
text="• Yes: Save current changes and quit\n"
"• No: Quit without saving\n"
"• Cancel: Return to auto-match",
font=("Arial", 9))
options_text.pack(anchor=tk.W, pady=(0, 10))
# Buttons
button_frame = ttk.Frame(dialog)
button_frame.pack(fill=tk.X, padx=20, pady=(0, 20))
result = None
def on_yes():
nonlocal result
result = True
dialog.destroy()
def on_no():
nonlocal result
result = False
dialog.destroy()
def on_cancel():
nonlocal result
result = None
dialog.destroy()
yes_btn = ttk.Button(button_frame, text="Yes", command=on_yes)
no_btn = ttk.Button(button_frame, text="No", command=on_no)
cancel_btn = ttk.Button(button_frame, text="Cancel", command=on_cancel)
yes_btn.pack(side=tk.LEFT, padx=(0, 5))
no_btn.pack(side=tk.LEFT, padx=5)
cancel_btn.pack(side=tk.RIGHT, padx=(5, 0))
# Wait for dialog to close
dialog.wait_window()
if result is None: # Cancel - don't quit
result = messagebox.askyesnocancel(
"Unsaved Changes",
"You have unsaved changes that will be lost if you quit.\n\n"
"Yes: Save current changes and quit\n"
"No: Quit without saving\n"
"Cancel: Return to auto-match"
)
if result is None:
# Cancel
return
elif result: # Yes - save changes first
# Save current checkbox states before quitting
if result:
# Save current person's changes, then quit
save_current_checkbox_states()
# Note: We don't actually save to database here, just preserve the states
# The user would need to click Save button for each person to persist changes
print("⚠️ Warning: Changes are preserved but not saved to database.")
print(" Click 'Save Changes' button for each person to persist changes.")
on_confirm_matches()
if not window_destroyed:
window_destroyed = True
try:
@ -587,8 +523,9 @@ class AutoMatchGUI:
Note: Do NOT modify original states here to avoid false positives
when a user toggles and reverts a checkbox.
"""
if current_matched_index < len(matched_ids) and match_vars:
current_matched_id = matched_ids[current_matched_index]
active_ids = filtered_matched_ids if filtered_matched_ids is not None else matched_ids
if current_matched_index < len(active_ids) and match_vars:
current_matched_id = active_ids[current_matched_index]
matches_for_current_person = matches_by_matched[current_matched_id]
if len(match_vars) == len(matches_for_current_person):
@ -752,8 +689,10 @@ class AutoMatchGUI:
if self.verbose >= 2:
print(f"DEBUG: Checkbox changed for person {person_id}, face {face_id}: {current_value}")
# Bind the callback to the variable
match_var.trace('w', lambda *args: on_checkbox_change(match_var, matched_id, match['unidentified_id']))
# Bind the callback to the variable (avoid late-binding by capturing ids)
current_person_id = matched_id
current_face_id = match['unidentified_id']
match_var.trace('w', lambda *args, var=match_var, person_id=current_person_id, face_id=current_face_id: on_checkbox_change(var, person_id, face_id))
# Configure match frame for grid layout
match_frame.columnconfigure(0, weight=0) # Checkbox column - fixed width

View File

@ -1015,6 +1015,26 @@ class ModifyIdentifiedGUI:
def on_quit():
nonlocal window_destroyed
# Warn if there are pending unmatched faces (unsaved changes)
try:
if unmatched_faces:
result = messagebox.askyesnocancel(
"Unsaved Changes",
"You have pending changes that are not saved.\n\n"
"Yes: Save and quit\n"
"No: Quit without saving\n"
"Cancel: Return to window"
)
if result is None:
# Cancel
return
if result is True:
# Save then quit
on_save_all_changes()
# If result is False, fall through and quit without saving
except Exception:
# If any issue occurs, proceed to normal quit
pass
on_closing()
if not window_destroyed:
window_destroyed = True
@ -1049,10 +1069,10 @@ class ModifyIdentifiedGUI:
show_person_faces(current_person_id, current_person_name)
messagebox.showinfo("Changes Saved", f"Successfully unlinked {count} face(s).")
save_btn_bottom = ttk.Button(control_frame, text="💾 Save changes", command=on_save_all_changes)
save_btn_bottom.pack(side=tk.RIGHT, padx=(0, 10))
quit_btn = ttk.Button(control_frame, text="❌ Quit", command=on_quit)
quit_btn.pack(side=tk.RIGHT)
save_btn_bottom = ttk.Button(control_frame, text="💾 Save changes", command=on_save_all_changes)
save_btn_bottom.pack(side=tk.RIGHT, padx=(0, 10))
# Show the window
try: