From 2394afb5eeffd86aaa0f1bc3efeec5b783d26da1 Mon Sep 17 00:00:00 2001 From: tanyar09 Date: Mon, 29 Sep 2025 13:07:46 -0400 Subject: [PATCH] Add last name search functionality to PhotoTagger GUI. Implement search and clear buttons for filtering people by last name, enhancing user experience. Update navigation and display logic to reflect filtered results, ensuring proper handling of UI states and feedback when no matches are found. --- photo_tagger.py | 87 +++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 77 insertions(+), 10 deletions(-) diff --git a/photo_tagger.py b/photo_tagger.py index 3670acf..0a52fb6 100644 --- a/photo_tagger.py +++ b/photo_tagger.py @@ -2710,13 +2710,24 @@ class PhotoTagger: # Configure row weights main_frame.rowconfigure(0, weight=1) + # Search controls for filtering people by last name + last_name_search_var = tk.StringVar() + search_row = ttk.Frame(left_frame) + search_row.grid(row=0, column=0, sticky=(tk.W, tk.E), pady=(0, 10)) + search_entry = ttk.Entry(search_row, textvariable=last_name_search_var, width=20) + search_entry.pack(side=tk.LEFT) + search_btn = ttk.Button(search_row, text="Search", width=8) + search_btn.pack(side=tk.LEFT, padx=(6, 0)) + clear_btn = ttk.Button(search_row, text="Clear", width=6) + clear_btn.pack(side=tk.LEFT, padx=(6, 0)) + # Matched person info matched_info_label = ttk.Label(left_frame, text="", font=("Arial", 10, "bold")) - matched_info_label.grid(row=0, column=0, pady=(0, 10), sticky=tk.W) + matched_info_label.grid(row=1, column=0, pady=(0, 10), sticky=tk.W) # Matched person image matched_canvas = tk.Canvas(left_frame, width=300, height=300, bg='white') - matched_canvas.grid(row=1, column=0, pady=(0, 10)) + matched_canvas.grid(row=2, column=0, pady=(0, 10)) # Save button for this person (will be created after function definitions) save_btn = None @@ -2777,6 +2788,7 @@ class PhotoTagger: # Button commands current_matched_index = 0 matched_ids = [person_id for person_id, _, _ in person_faces_list if person_id in matches_by_matched and matches_by_matched[person_id]] + filtered_matched_ids = None # filtered subset based on last name search match_checkboxes = [] match_vars = [] @@ -2846,7 +2858,8 @@ class PhotoTagger: # Save current checkbox states before navigating away save_current_checkbox_states() current_matched_index += 1 - if current_matched_index < len(matched_ids): + active_ids = filtered_matched_ids if filtered_matched_ids is not None else matched_ids + if current_matched_index < len(active_ids): update_display() else: finish_auto_match() @@ -2874,6 +2887,48 @@ class PhotoTagger: return True return False + def apply_last_name_filter(): + """Filter people by last name and update navigation""" + nonlocal filtered_matched_ids, current_matched_index + query = last_name_search_var.get().strip().lower() + if query: + # Filter person_faces_list by last name + filtered_people = [] + for person_id, face, person_name in person_faces_list: + # Extract last name from person_name (format: "Last, First") + if ',' in person_name: + last_name = person_name.split(',')[0].strip().lower() + else: + last_name = person_name.strip().lower() + + if query in last_name: + filtered_people.append((person_id, face, person_name)) + + # Get filtered matched_ids + filtered_matched_ids = [person_id for person_id, _, _ in filtered_people if person_id in matches_by_matched and matches_by_matched[person_id]] + else: + filtered_matched_ids = None + + # Reset to first person in filtered list + current_matched_index = 0 + if filtered_matched_ids: + update_display() + else: + # No matches - clear display + matched_info_label.config(text="No people match filter") + matched_canvas.delete("all") + matched_canvas.create_text(150, 150, text="No matches found", fill="gray") + matches_canvas.delete("all") + update_button_states() + + def clear_last_name_filter(): + """Clear filter and show all people""" + nonlocal filtered_matched_ids, current_matched_index + last_name_search_var.set("") + filtered_matched_ids = None + current_matched_index = 0 + update_display() + def on_quit_auto_match(): nonlocal window_destroyed @@ -2985,10 +3040,11 @@ class PhotoTagger: # Create save button now that functions are defined save_btn = ttk.Button(left_frame, text="💾 Save Changes", command=on_confirm_matches) - save_btn.grid(row=2, column=0, pady=(0, 10), sticky=(tk.W, tk.E)) + save_btn.grid(row=3, column=0, pady=(0, 10), sticky=(tk.W, tk.E)) def update_button_states(): """Update button states based on current position""" + active_ids = filtered_matched_ids if filtered_matched_ids is not None else matched_ids # Enable/disable Back button based on position if current_matched_index > 0: back_btn.config(state='normal') @@ -2996,15 +3052,16 @@ class PhotoTagger: back_btn.config(state='disabled') # Enable/disable Next button based on position - if current_matched_index < len(matched_ids) - 1: + if current_matched_index < len(active_ids) - 1: next_btn.config(state='normal') else: next_btn.config(state='disabled') def update_save_button_text(): """Update save button text with current person name""" - 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] # Get person name from the first match for this person matches_for_current_person = matches_by_matched[matched_id] if matches_for_current_person: @@ -3042,11 +3099,12 @@ class PhotoTagger: def update_display(): nonlocal current_matched_index - if current_matched_index >= len(matched_ids): + active_ids = filtered_matched_ids if filtered_matched_ids is not None else matched_ids + if current_matched_index >= len(active_ids): finish_auto_match() return - matched_id = matched_ids[current_matched_index] + matched_id = active_ids[current_matched_index] matches_for_this_person = matches_by_matched[matched_id] # Update button states @@ -3056,7 +3114,8 @@ class PhotoTagger: update_save_button_text() # Update title - root.title(f"Auto-Match Face Identification - {current_matched_index + 1}/{len(matched_ids)}") + active_ids = filtered_matched_ids if filtered_matched_ids is not None else matched_ids + root.title(f"Auto-Match Face Identification - {current_matched_index + 1}/{len(active_ids)}") # Get the first match to get matched person info if not matches_for_this_person: @@ -3234,6 +3293,14 @@ class PhotoTagger: # Window was destroyed before we could show it return 0 + # Wire up search controls now that helper functions exist + try: + search_btn.config(command=lambda: apply_last_name_filter()) + clear_btn.config(command=lambda: clear_last_name_filter()) + search_entry.bind('', lambda e: apply_last_name_filter()) + except Exception: + pass + # Start with first matched person update_display()