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()