From 70cb11adbdc2e0425b4c270b5244b032470a87a5 Mon Sep 17 00:00:00 2001 From: tanyar09 Date: Mon, 6 Oct 2025 12:25:44 -0400 Subject: [PATCH] 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. --- auto_match_gui.py | 105 ++++++++------------------------------- modify_identified_gui.py | 24 ++++++++- 2 files changed, 44 insertions(+), 85 deletions(-) diff --git a/auto_match_gui.py b/auto_match_gui.py index fa937a3..4ba6c38 100644 --- a/auto_match_gui.py +++ b/auto_match_gui.py @@ -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 diff --git a/modify_identified_gui.py b/modify_identified_gui.py index 0a51cbf..5b94b73 100644 --- a/modify_identified_gui.py +++ b/modify_identified_gui.py @@ -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: