Enhance tag management in PhotoTagger by implementing shared tag linking functions and improving tag deletion handling. Introduce a unified tag button frame for various view modes, allowing users to add tags seamlessly. Update pending tag changes cleanup logic to ensure accurate tag displays after deletions. This update streamlines the tagging process and enhances user experience across different photo views.

This commit is contained in:
tanyar09 2025-10-02 12:41:16 -04:00
parent 4602c252e8
commit 639b283c0c

View File

@ -4547,8 +4547,16 @@ class PhotoTagger:
# Delete tags
cursor.execute(f"DELETE FROM tags WHERE id IN ({','.join('?' for _ in ids_to_delete)})", ids_to_delete)
conn.commit()
# Clean up pending tag changes for deleted tags
for photo_id in list(pending_tag_changes.keys()):
pending_tag_changes[photo_id] = [tid for tid in pending_tag_changes[photo_id] if tid not in ids_to_delete]
if not pending_tag_changes[photo_id]:
del pending_tag_changes[photo_id]
refresh_tag_list()
load_existing_tags()
load_photos() # Refresh photo data to reflect deleted tags
switch_view_mode(view_mode_var.get())
except Exception as e:
messagebox.showerror("Error", f"Failed to delete tags: {e}")
@ -5092,6 +5100,106 @@ class PhotoTagger:
# Focus the popup
popup.focus_set()
# Shared tag linking functions for all view modes
def create_add_tag_handler(photo_id, label_widget, photo_tags, available_tags):
"""Create a handler function for adding tags to a photo"""
def handler():
# Create popup window for tag selection
popup = tk.Toplevel(root)
popup.title("Add Tag")
popup.transient(root)
popup.grab_set()
popup.geometry("300x150")
popup.resizable(False, False)
# Create main frame
main_frame = ttk.Frame(popup, padding="10")
main_frame.pack(fill=tk.BOTH, expand=True)
ttk.Label(main_frame, text="Select a tag:").pack(pady=(0, 5))
tag_var = tk.StringVar()
combo = ttk.Combobox(main_frame, textvariable=tag_var, values=available_tags, width=30)
combo.pack(pady=(0, 10), fill=tk.X)
combo.focus_set()
def confirm():
tag_name = tag_var.get().strip()
if not tag_name:
popup.destroy()
return
# Get or create tag ID
if tag_name in tag_name_to_id:
tag_id = tag_name_to_id[tag_name]
else:
# Create new tag in database
with self.get_db_connection() as conn:
cursor = conn.cursor()
cursor.execute('INSERT OR IGNORE INTO tags (tag_name) VALUES (?)', (tag_name,))
cursor.execute('SELECT id FROM tags WHERE tag_name = ?', (tag_name,))
tag_id = cursor.fetchone()[0]
# Update mappings
tag_name_to_id[tag_name] = tag_id
tag_id_to_name[tag_id] = tag_name
if tag_name not in existing_tags:
existing_tags.append(tag_name)
existing_tags.sort()
# Check if tag already exists (compare tag IDs) before adding to pending changes
existing_tag_ids = self._get_existing_tag_ids_for_photo(photo_id)
pending_tag_ids = pending_tag_changes.get(photo_id, [])
all_existing_tag_ids = existing_tag_ids + pending_tag_ids
if tag_id not in all_existing_tag_ids:
# Only add to pending changes if tag is actually new
if photo_id not in pending_tag_changes:
pending_tag_changes[photo_id] = []
pending_tag_changes[photo_id].append(tag_id)
# Update the display immediately - combine existing and pending, removing duplicates
existing_tags_list = self._parse_tags_string(photo_tags)
pending_tag_names = [tag_id_to_name.get(tid, f"Unknown {tid}") for tid in pending_tag_changes[photo_id]]
all_tags = existing_tags_list + pending_tag_names
unique_tags = self._deduplicate_tags(all_tags)
current_tags = ", ".join(unique_tags)
label_widget.configure(text=current_tags)
update_save_button_text()
popup.destroy()
ttk.Button(main_frame, text="Add", command=confirm).pack(pady=(0, 5))
return handler
def create_tag_buttons_frame(parent, photo_id, photo_tags, existing_tags, use_grid=False, row=0, col=0):
"""Create a frame with tag display and add button that can be used in any view mode"""
tags_frame = ttk.Frame(parent)
# Display current tags
existing_tags_list = self._parse_tags_string(photo_tags)
pending_tag_names = []
if photo_id in pending_tag_changes:
pending_tag_names = [tag_id_to_name.get(tid, f"Unknown {tid}") for tid in pending_tag_changes[photo_id]]
all_tags = existing_tags_list + pending_tag_names
unique_tags = self._deduplicate_tags(all_tags)
current_display = ", ".join(unique_tags) if unique_tags else "None"
tags_text = ttk.Label(tags_frame, text=current_display)
tags_text.pack(side=tk.LEFT)
# Add button
add_btn = tk.Button(tags_frame, text="+", width=2,
command=create_add_tag_handler(photo_id, tags_text, photo_tags, existing_tags))
add_btn.pack(side=tk.LEFT, padx=(6, 0))
# Pack or grid the frame based on the view mode
if use_grid:
tags_frame.grid(row=row, column=col, padx=5, sticky=tk.W)
else:
tags_frame.pack(side=tk.LEFT, padx=5)
return tags_frame
def show_list_view():
clear_content()
@ -5190,93 +5298,8 @@ class PhotoTagger:
elif key == 'faces':
text = str(photo['face_count'])
elif key == 'tags':
# Tags cell with inline '+' to add tags
tags_frame = ttk.Frame(row_frame)
tags_frame.grid(row=0, column=i, padx=5, sticky=tk.W)
# Show current tags + pending changes
current_display = photo['tags'] or ""
if photo['id'] in pending_tag_changes:
# Convert pending tag IDs to names for display
pending_tag_names = [tag_id_to_name.get(tag_id, f"Unknown {tag_id}") for tag_id in pending_tag_changes[photo['id']]]
existing_tags_list = self._parse_tags_string(current_display)
all_tags = existing_tags_list + pending_tag_names
current_display = ", ".join(all_tags)
if not current_display:
current_display = "None"
tags_text = ttk.Label(tags_frame, text=current_display)
tags_text.pack(side=tk.LEFT)
def make_add_tag_handler(photo_id, label_widget, photo_tags, available_tags):
def handler():
# Simple dropdown to choose existing tag
popup = tk.Toplevel(root)
popup.title("Add Tag")
popup.transient(root)
popup.grab_set()
popup.geometry("300x150")
popup.resizable(False, False)
# Create main frame
main_frame = ttk.Frame(popup, padding="10")
main_frame.pack(fill=tk.BOTH, expand=True)
ttk.Label(main_frame, text="Select a tag:").pack(pady=(0, 5))
tag_var = tk.StringVar()
combo = ttk.Combobox(main_frame, textvariable=tag_var, values=available_tags, width=30)
combo.pack(pady=(0, 10), fill=tk.X)
combo.focus_set()
def confirm():
tag_name = tag_var.get().strip()
if not tag_name:
popup.destroy()
return
# Get or create tag ID
if tag_name in tag_name_to_id:
tag_id = tag_name_to_id[tag_name]
else:
# Create new tag in database
with self.get_db_connection() as conn:
cursor = conn.cursor()
cursor.execute('INSERT OR IGNORE INTO tags (tag_name) VALUES (?)', (tag_name,))
cursor.execute('SELECT id FROM tags WHERE tag_name = ?', (tag_name,))
tag_id = cursor.fetchone()[0]
# Update mappings
tag_name_to_id[tag_name] = tag_id
tag_id_to_name[tag_id] = tag_name
if tag_name not in existing_tags:
existing_tags.append(tag_name)
existing_tags.sort()
# Check if tag already exists (compare tag IDs) before adding to pending changes
existing_tag_ids = self._get_existing_tag_ids_for_photo(photo_id)
pending_tag_ids = pending_tag_changes.get(photo_id, [])
all_existing_tag_ids = existing_tag_ids + pending_tag_ids
if tag_id not in all_existing_tag_ids:
# Only add to pending changes if tag is actually new
if photo_id not in pending_tag_changes:
pending_tag_changes[photo_id] = []
pending_tag_changes[photo_id].append(tag_id)
# Update the display immediately - combine existing and pending, removing duplicates
existing_tags_list = self._parse_tags_string(photo_tags)
pending_tag_names = [tag_id_to_name.get(tid, f"Unknown {tid}") for tid in pending_tag_changes[photo_id]]
all_tags = existing_tags_list + pending_tag_names
unique_tags = self._deduplicate_tags(all_tags)
current_tags = ", ".join(unique_tags)
label_widget.configure(text=current_tags)
update_save_button_text()
popup.destroy()
ttk.Button(main_frame, text="Add", command=confirm).pack(pady=(0, 5))
return handler
add_btn = tk.Button(tags_frame, text="+", width=2, command=make_add_tag_handler(photo['id'], tags_text, photo['tags'], existing_tags))
add_btn.pack(side=tk.LEFT, padx=(6,0))
# Use shared tag buttons frame for list view
create_tag_buttons_frame(row_frame, photo['id'], photo['tags'], existing_tags, use_grid=True, row=0, col=i)
continue
ttk.Label(row_frame, text=text).grid(row=0, column=i, padx=5, sticky=tk.W)
@ -5381,78 +5404,8 @@ class PhotoTagger:
elif key == 'faces':
text = str(photo['face_count'])
elif key == 'tags':
# Tags cell with inline '+' to add tags
tags_frame = ttk.Frame(row_frame)
tags_frame.grid(row=0, column=col_idx, padx=5, sticky=tk.W)
# Show current tags + pending changes
current_display = photo['tags'] or "None"
if photo['id'] in pending_tag_changes:
# Convert pending tag IDs to names for display
pending_tag_names = [tag_id_to_name.get(tag_id, f"Unknown {tag_id}") for tag_id in pending_tag_changes[photo['id']]]
current_display = ", ".join(pending_tag_names)
tags_text = ttk.Label(tags_frame, text=current_display)
tags_text.pack(side=tk.LEFT)
def make_add_tag_handler(photo_id, label_widget, photo_tags, available_tags):
def handler():
popup = tk.Toplevel(root)
popup.title("Add Tag")
popup.transient(root)
popup.grab_set()
ttk.Label(popup, text="Select a tag:").grid(row=0, column=0, padx=8, pady=8, sticky=tk.W)
tag_var = tk.StringVar()
combo = ttk.Combobox(popup, textvariable=tag_var, values=available_tags, width=24)
combo.grid(row=1, column=0, padx=8, pady=(0,8), sticky=(tk.W, tk.E))
combo.focus_set()
def confirm():
tag_name = tag_var.get().strip()
if not tag_name:
popup.destroy()
return
# Get or create tag ID
if tag_name in tag_name_to_id:
tag_id = tag_name_to_id[tag_name]
else:
# Create new tag in database
with self.get_db_connection() as conn:
cursor = conn.cursor()
cursor.execute('INSERT OR IGNORE INTO tags (tag_name) VALUES (?)', (tag_name,))
cursor.execute('SELECT id FROM tags WHERE tag_name = ?', (tag_name,))
tag_id = cursor.fetchone()[0]
# Update mappings
tag_name_to_id[tag_name] = tag_id
tag_id_to_name[tag_id] = tag_name
if tag_name not in existing_tags:
existing_tags.append(tag_name)
existing_tags.sort()
# Add to pending changes instead of saving immediately
if photo_id not in pending_tag_changes:
pending_tag_changes[photo_id] = []
# Check if tag already exists (compare tag IDs)
existing_tag_ids = self._get_existing_tag_ids_for_photo(photo_id)
all_existing_tag_ids = existing_tag_ids + pending_tag_changes[photo_id]
if tag_id not in all_existing_tag_ids:
pending_tag_changes[photo_id].append(tag_id)
# Update the display immediately - combine existing and pending, removing duplicates
existing_tags_list = self._parse_tags_string(photo_tags)
pending_tag_names = [tag_id_to_name.get(tid, f"Unknown {tid}") for tid in pending_tag_changes[photo_id]]
all_tags = existing_tags_list + pending_tag_names
unique_tags = self._deduplicate_tags(all_tags)
current_tags = ", ".join(unique_tags)
label_widget.configure(text=current_tags)
popup.destroy()
ttk.Button(popup, text="Add", command=confirm).grid(row=2, column=0, padx=8, pady=(0,8), sticky=tk.E)
return handler
add_btn = tk.Button(tags_frame, text="+", width=2, command=make_add_tag_handler(photo['id'], tags_text, photo['tags'], existing_tags))
add_btn.pack(side=tk.LEFT, padx=(6,0))
# Use shared tag buttons frame for icon view
create_tag_buttons_frame(row_frame, photo['id'], photo['tags'], existing_tags, use_grid=True, row=0, col=col_idx)
col_idx += 1
continue
@ -5525,84 +5478,8 @@ class PhotoTagger:
elif key == 'faces':
text = str(photo['face_count'])
elif key == 'tags':
# Tags cell with inline '+' to add tags
tags_frame = ttk.Frame(row_frame)
tags_frame.grid(row=0, column=col_idx, padx=5, sticky=tk.W)
# Show current tags + pending changes
current_display = photo['tags'] or ""
if photo['id'] in pending_tag_changes:
# Convert pending tag IDs to names for display
pending_tag_names = [tag_id_to_name.get(tag_id, f"Unknown {tag_id}") for tag_id in pending_tag_changes[photo['id']]]
existing_tags_list = self._parse_tags_string(current_display)
all_tags = existing_tags_list + pending_tag_names
current_display = ", ".join(all_tags)
if not current_display:
current_display = "None"
tags_text = ttk.Label(tags_frame, text=current_display)
tags_text.pack(side=tk.LEFT)
def make_add_tag_handler(photo_id, label_widget, photo_tags, available_tags):
def handler():
popup = tk.Toplevel(root)
popup.title("Add Tag")
popup.transient(root)
popup.grab_set()
ttk.Label(popup, text="Select a tag:").grid(row=0, column=0, padx=8, pady=8, sticky=tk.W)
tag_var = tk.StringVar()
combo = ttk.Combobox(popup, textvariable=tag_var, values=available_tags, width=24)
combo.grid(row=1, column=0, padx=8, pady=(0,8), sticky=(tk.W, tk.E))
combo.focus_set()
def confirm():
tag_name = tag_var.get().strip()
if not tag_name:
popup.destroy()
return
# Get or create tag ID
if tag_name in tag_name_to_id:
tag_id = tag_name_to_id[tag_name]
else:
# Create new tag in database
with self.get_db_connection() as conn:
cursor = conn.cursor()
cursor.execute('INSERT OR IGNORE INTO tags (tag_name) VALUES (?)', (tag_name,))
cursor.execute('SELECT id FROM tags WHERE tag_name = ?', (tag_name,))
tag_id = cursor.fetchone()[0]
# Update mappings
tag_name_to_id[tag_name] = tag_id
tag_id_to_name[tag_id] = tag_name
if tag_name not in existing_tags:
existing_tags.append(tag_name)
existing_tags.sort()
# Check if tag already exists (compare tag IDs) before adding to pending changes
existing_tag_ids = self._get_existing_tag_ids_for_photo(photo_id)
pending_tag_ids = pending_tag_changes.get(photo_id, [])
all_existing_tag_ids = existing_tag_ids + pending_tag_ids
if tag_id not in all_existing_tag_ids:
# Only add to pending changes if tag is actually new
if photo_id not in pending_tag_changes:
pending_tag_changes[photo_id] = []
pending_tag_changes[photo_id].append(tag_id)
# Update the display immediately - combine existing and pending, removing duplicates
existing_tags_list = self._parse_tags_string(photo_tags)
pending_tag_names = [tag_id_to_name.get(tid, f"Unknown {tid}") for tid in pending_tag_changes[photo_id]]
all_tags = existing_tags_list + pending_tag_names
unique_tags = self._deduplicate_tags(all_tags)
current_tags = ", ".join(unique_tags)
label_widget.configure(text=current_tags)
update_save_button_text()
popup.destroy()
ttk.Button(popup, text="Add", command=confirm).grid(row=2, column=0, padx=8, pady=(0,8), sticky=tk.E)
return handler
add_btn = tk.Button(tags_frame, text="+", width=2, command=make_add_tag_handler(photo['id'], tags_text, photo['tags'], existing_tags))
add_btn.pack(side=tk.LEFT, padx=(6,0))
# Use shared tag buttons frame for compact view
create_tag_buttons_frame(row_frame, photo['id'], photo['tags'], existing_tags, use_grid=True, row=0, col=col_idx)
col_idx += 1
continue