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.
This commit is contained in:
parent
18e65e88fc
commit
aa67f12a20
137
search_gui.py
137
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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user