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:
parent
4602c252e8
commit
639b283c0c
351
photo_tagger.py
351
photo_tagger.py
@ -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
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user