From aa67f12a20abdbb255acdf92cdd0bdc9a9e0d352 Mon Sep 17 00:00:00 2001 From: tanyar09 Date: Thu, 9 Oct 2025 14:00:30 -0400 Subject: [PATCH] Enhance Search GUI with processed status and improved results display This commit updates the SearchGUI class to include a new "Processed" column, displaying the processing status of photos. The results area now features a header with item count, and sorting functionality has been added for the processed status. Additionally, the treeview has been enhanced with a vertical scrollbar for better navigation. The logic for displaying and sorting results has been updated to accommodate these changes, improving the overall user experience in managing photo collections. --- search_gui.py | 137 +++++++++++++++++++++++++++++++++++--------------- 1 file changed, 96 insertions(+), 41 deletions(-) diff --git a/search_gui.py b/search_gui.py index 6769ae5..c265682 100644 --- a/search_gui.py +++ b/search_gui.py @@ -225,13 +225,21 @@ class SearchGUI: # Results area results_frame = ttk.Frame(main) results_frame.pack(fill=tk.BOTH, expand=True) - ttk.Label(results_frame, text="Results:", font=("Arial", 10, "bold")).pack(anchor="w") + + # Results header with count + results_header = ttk.Frame(results_frame) + results_header.pack(fill=tk.X) + results_label = ttk.Label(results_header, text="Results:", font=("Arial", 10, "bold")) + results_label.pack(side=tk.LEFT) + results_count_label = ttk.Label(results_header, text="(0 items)", font=("Arial", 10), foreground="gray") + results_count_label.pack(side=tk.LEFT, padx=(6, 0)) - columns = ("select", "person", "tags", "open_dir", "open_photo", "path", "date_taken") + columns = ("select", "person", "tags", "processed", "open_dir", "open_photo", "path", "date_taken") tree = ttk.Treeview(results_frame, columns=columns, show="headings", selectmode="browse") tree.heading("select", text="☑") tree.heading("person", text="Person", command=lambda: sort_treeview("person")) tree.heading("tags", text="Tags", command=lambda: sort_treeview("tags")) + tree.heading("processed", text="Processed", command=lambda: sort_treeview("processed")) tree.heading("open_dir", text="📁") tree.heading("open_photo", text="👤") tree.heading("path", text="Photo path", command=lambda: sort_treeview("path")) @@ -239,11 +247,19 @@ class SearchGUI: tree.column("select", width=50, anchor="center") tree.column("person", width=180, anchor="w") tree.column("tags", width=200, anchor="w") + tree.column("processed", width=80, anchor="center") tree.column("open_dir", width=50, anchor="center") tree.column("open_photo", width=50, anchor="center") tree.column("path", width=400, anchor="w") tree.column("date_taken", width=100, anchor="center") - tree.pack(fill=tk.BOTH, expand=True, pady=(4, 0)) + + # Add vertical scrollbar for the treeview + tree_v_scrollbar = ttk.Scrollbar(results_frame, orient="vertical", command=tree.yview) + tree.configure(yscrollcommand=tree_v_scrollbar.set) + + # Pack treeview and scrollbar + tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, pady=(4, 0)) + tree_v_scrollbar.pack(side=tk.RIGHT, fill=tk.Y, pady=(4, 0)) # Buttons btns = ttk.Frame(main) @@ -275,6 +291,7 @@ class SearchGUI: # Sort the items # For person, tags, and path columns, sort alphabetically # For date_taken column, sort by date + # For processed column, sort by processed status (Yes/No) # For icon columns, maintain original order if col in ['person', 'tags', 'path']: items.sort(key=lambda x: x[0].lower(), reverse=self.sort_reverse) @@ -286,6 +303,15 @@ class SearchGUI: return "9999-12-31" # Put "No date" entries at the end return date_str items.sort(key=date_sort_key, reverse=self.sort_reverse) + elif col == 'processed': + # Sort by processed status (Yes comes before No) + def processed_sort_key(item): + processed_str = item[0] + if processed_str == "Yes": + return "0" # Yes comes first + else: + return "1" # No comes second + items.sort(key=processed_sort_key, reverse=self.sort_reverse) else: # For icon columns, just reverse if clicking same column if self.sort_column == col and self.sort_reverse: @@ -303,6 +329,7 @@ class SearchGUI: # Reset all headers tree.heading("person", text="Person") tree.heading("tags", text="Tags") + tree.heading("processed", text="Processed") tree.heading("path", text="Photo path") tree.heading("date_taken", text="Date Taken") @@ -313,6 +340,9 @@ class SearchGUI: elif self.sort_column == "tags": indicator = " ↓" if self.sort_reverse else " ↑" tree.heading("tags", text="Tags" + indicator) + elif self.sort_column == "processed": + indicator = " ↓" if self.sort_reverse else " ↑" + tree.heading("processed", text="Processed" + indicator) elif self.sort_column == "path": indicator = " ↓" if self.sort_reverse else " ↑" tree.heading("path", text="Photo path" + indicator) @@ -344,7 +374,7 @@ class SearchGUI: # Restore people icon column for name search tree.column("open_photo", width=50, minwidth=50, anchor="center") tree.heading("open_photo", text="👤") - # Restore all columns to display + # Restore all columns to display (hide processed column for name search) tree["displaycolumns"] = ("select", "person", "tags", "open_dir", "open_photo", "path", "date_taken") elif choice == self.SEARCH_TYPES[1]: # Search photos by date date_frame.pack(fill=tk.X) @@ -364,7 +394,7 @@ class SearchGUI: tree.column("open_photo", width=50, minwidth=50, anchor="center") tree.heading("open_photo", text="👤") # Show all columns except person for date search - tree["displaycolumns"] = ("select", "tags", "open_dir", "open_photo", "path", "date_taken") + tree["displaycolumns"] = ("select", "tags", "processed", "open_dir", "open_photo", "path", "date_taken") elif choice == self.SEARCH_TYPES[2]: # Search photos by tags tag_frame.pack(fill=tk.X) tag_mode_frame.pack(fill=tk.X, pady=(4, 0)) @@ -383,7 +413,7 @@ class SearchGUI: tree.column("open_photo", width=50, minwidth=50, anchor="center") tree.heading("open_photo", text="👤") # Also hide the column from display - tree["displaycolumns"] = ("select", "tags", "open_dir", "open_photo", "path", "date_taken") + tree["displaycolumns"] = ("select", "tags", "processed", "open_dir", "open_photo", "path", "date_taken") elif choice == self.SEARCH_TYPES[6]: # Photos without faces # No input needed for this search type search_btn.configure(state="normal") @@ -394,7 +424,7 @@ class SearchGUI: tree.column("open_photo", width=0, minwidth=0, anchor="center") tree.heading("open_photo", text="") # Also hide the columns from display - tree["displaycolumns"] = ("select", "tags", "open_dir", "path", "date_taken") + tree["displaycolumns"] = ("select", "tags", "processed", "open_dir", "path", "date_taken") # Auto-run search for photos without faces do_search() elif choice == self.SEARCH_TYPES[7]: # Photos without tags @@ -407,7 +437,7 @@ class SearchGUI: tree.column("open_photo", width=50, minwidth=50, anchor="center") tree.heading("open_photo", text="👤") # Show all columns except person for photos without tags search - tree["displaycolumns"] = ("select", "tags", "open_dir", "open_photo", "path", "date_taken") + tree["displaycolumns"] = ("select", "tags", "processed", "open_dir", "open_photo", "path", "date_taken") # Auto-run search for photos without tags do_search() else: @@ -427,7 +457,7 @@ class SearchGUI: tree.column("open_photo", width=50, minwidth=50, anchor="center") tree.heading("open_photo", text="👤") # Show all columns except person for other search types - tree["displaycolumns"] = ("select", "tags", "open_dir", "open_photo", "path", "date_taken") + tree["displaycolumns"] = ("select", "tags", "processed", "open_dir", "open_photo", "path", "date_taken") def filter_results_by_folder(results, folder_path): """Filter search results by folder path if specified.""" @@ -458,6 +488,8 @@ class SearchGUI: self.selected_photos.clear() # Clear tag cache self.photo_tags_cache.clear() + # Reset results count + results_count_label.config(text="(0 items)") update_header_display() def add_results(rows: List[tuple]): @@ -468,33 +500,38 @@ class SearchGUI: # For date search: (path, date_taken) - hide person column path, date_taken = row photo_tags = get_photo_tags_for_display(path) - tree.insert("", tk.END, values=("☐", "", photo_tags, "📁", "👤", path, date_taken)) + processed_status = get_photo_processed_status(path) + tree.insert("", tk.END, values=("☐", "", photo_tags, processed_status, "📁", "👤", path, date_taken)) elif search_type_var.get() == self.SEARCH_TYPES[2]: # Tag search # For tag search: (path, tag_info) - hide person column # Show ALL tags for the photo, not just matching ones path, tag_info = row photo_tags = get_photo_tags_for_display(path) date_taken = get_photo_date_taken(path) - tree.insert("", tk.END, values=("☐", "", photo_tags, "📁", "👤", path, date_taken)) + processed_status = get_photo_processed_status(path) + tree.insert("", tk.END, values=("☐", "", photo_tags, processed_status, "📁", "👤", path, date_taken)) elif search_type_var.get() == self.SEARCH_TYPES[6]: # Photos without faces # For photos without faces: (path, tag_info) - hide person and people icon columns path, tag_info = row photo_tags = get_photo_tags_for_display(path) date_taken = get_photo_date_taken(path) - tree.insert("", tk.END, values=("☐", "", photo_tags, "📁", "", path, date_taken)) + processed_status = get_photo_processed_status(path) + tree.insert("", tk.END, values=("☐", "", photo_tags, processed_status, "📁", "", path, date_taken)) elif search_type_var.get() == self.SEARCH_TYPES[7]: # Photos without tags # For photos without tags: (path, filename) - hide person column path, filename = row photo_tags = get_photo_tags_for_display(path) # Will be "No tags" date_taken = get_photo_date_taken(path) - tree.insert("", tk.END, values=("☐", "", photo_tags, "📁", "👤", path, date_taken)) + processed_status = get_photo_processed_status(path) + tree.insert("", tk.END, values=("☐", "", photo_tags, processed_status, "📁", "👤", path, date_taken)) else: # For name search: (path, full_name) - show person column p, full_name = row # Get tags for this photo photo_tags = get_photo_tags_for_display(p) date_taken = get_photo_date_taken(p) - tree.insert("", tk.END, values=("☐", full_name, photo_tags, "📁", "👤", p, date_taken)) + processed_status = get_photo_processed_status(p) + tree.insert("", tk.END, values=("☐", full_name, photo_tags, processed_status, "📁", "👤", p, date_taken)) # Sort by appropriate column by default when results are first loaded if rows and self.sort_column is None: @@ -532,6 +569,10 @@ class SearchGUI: tree.move(child, '', index) # Update header display update_header_display() + + # Update results count + item_count = len(tree.get_children()) + results_count_label.config(text=f"({item_count} items)") def do_search(): clear_results() @@ -658,10 +699,10 @@ class SearchGUI: def toggle_photo_selection(row_id, vals): """Toggle checkbox selection for a photo.""" - if len(vals) < 6: + if len(vals) < 7: return current_state = vals[0] # Checkbox is now in column 0 (first) - path = vals[5] # Photo path is now in column 5 (last) + path = vals[6] # Photo path is now in column 6 (last) if current_state == "☐": # Select photo new_state = "☑" @@ -714,7 +755,7 @@ class SearchGUI: # Update all checkboxes to unselected state for item in tree.get_children(): vals = tree.item(item, "values") - if len(vals) >= 6 and vals[0] == "☑": + if len(vals) >= 7 and vals[0] == "☑": new_vals = list(vals) new_vals[0] = "☐" tree.item(item, values=new_vals) @@ -857,6 +898,20 @@ class SearchGUI: except Exception: return "No date" + def get_photo_processed_status(photo_path): + """Get processed status for a photo to display in the processed column.""" + try: + with self.db.get_db_connection() as conn: + cursor = conn.cursor() + cursor.execute('SELECT processed FROM photos WHERE path = ?', (photo_path,)) + result = cursor.fetchone() + if result and result[0] is not None: + return "Yes" if result[0] else "No" + else: + return "No" # Default to not processed + except Exception: + return "No" + def get_photo_people_tooltip(photo_path): """Get people information for a photo to display in tooltip.""" try: @@ -1180,8 +1235,8 @@ class SearchGUI: # Update each affected row in the search results for item in tree.get_children(): vals = tree.item(item, "values") - if len(vals) >= 6: - photo_path = vals[5] # Photo path is at index 5 + if len(vals) >= 7: + photo_path = vals[6] # Photo path is at index 6 if photo_path in affected_photo_paths: # Get current tags for this photo from cache current_tags = get_photo_tags_for_display(photo_path) @@ -1217,26 +1272,26 @@ class SearchGUI: is_photos_without_faces = (search_type_var.get() == self.SEARCH_TYPES[6]) if is_name_search: - # Name search: all columns visible including person + # Name search: all columns visible including person (processed column hidden) select_col = "#1" # select is column 1 open_dir_col = "#4" # open_dir is column 4 face_col = "#5" # open_photo is column 5 path_col = "#6" # path is column 6 - path_index = 5 # path is at index 5 in values array + path_index = 6 # path is at index 6 in values array (still same since processed is hidden from display) elif is_photos_without_faces: # Photos without faces: person and people icon columns are hidden select_col = "#1" # select is column 1 - open_dir_col = "#3" # open_dir is column 3 - face_col = "#4" # open_photo is column 4 (but hidden) - path_col = "#4" # path is column 4 (since people icon is hidden) - path_index = 5 # path is at index 5 in values array + open_dir_col = "#4" # open_dir is column 4 + face_col = "#5" # open_photo is column 5 (but hidden) + path_col = "#5" # path is column 5 (since people icon is hidden) + path_index = 6 # path is at index 6 in values array else: # All other searches: person column is hidden, people icon visible select_col = "#1" # select is column 1 - open_dir_col = "#3" # open_dir is column 3 - face_col = "#4" # open_photo is column 4 - path_col = "#5" # path is column 5 - path_index = 5 # path is at index 5 in values array + open_dir_col = "#4" # open_dir is column 4 + face_col = "#5" # open_photo is column 5 + path_col = "#6" # path is column 6 + path_index = 6 # path is at index 6 in values array path = vals[path_index] # Photo path if col_id == open_dir_col: # Open directory column @@ -1330,26 +1385,26 @@ class SearchGUI: is_photos_without_faces = (search_type_var.get() == self.SEARCH_TYPES[6]) if is_name_search: - # Name search: all columns visible including person + # Name search: all columns visible including person (processed column hidden) tags_col = "#3" # tags is column 3 open_dir_col = "#4" # open_dir is column 4 face_col = "#5" # open_photo is column 5 path_col = "#6" # path is column 6 - path_index = 5 # path is at index 5 in values array + path_index = 6 # path is at index 6 in values array (still same since processed is hidden from display) elif is_photos_without_faces: # Photos without faces: person and people icon columns are hidden tags_col = "#2" # tags is column 2 - open_dir_col = "#3" # open_dir is column 3 - face_col = "#4" # open_photo is column 4 (but hidden) - path_col = "#4" # path is column 4 (since people icon is hidden) - path_index = 5 # path is at index 5 in values array + open_dir_col = "#4" # open_dir is column 4 + face_col = "#5" # open_photo is column 5 (but hidden) + path_col = "#5" # path is column 5 (since people icon is hidden) + path_index = 6 # path is at index 6 in values array else: # All other searches: person column is hidden, people icon visible tags_col = "#2" # tags is column 2 - open_dir_col = "#3" # open_dir is column 3 - face_col = "#4" # open_photo is column 4 - path_col = "#5" # path is column 5 - path_index = 5 # path is at index 5 in values array + open_dir_col = "#4" # open_dir is column 4 + face_col = "#5" # open_photo is column 5 + path_col = "#6" # path is column 6 + path_index = 6 # path is at index 6 in values array if col_id == tags_col: # Tags column tree.config(cursor="") @@ -1395,8 +1450,8 @@ class SearchGUI: # Show and center root.update_idletasks() - # Widened to ensure all columns are visible by default, including the new date taken column - self.gui_core.center_window(root, 1200, 520) + # Widened to ensure all columns are visible by default, including the new processed column + self.gui_core.center_window(root, 1300, 520) root.deiconify() root.mainloop() return 0