Enhance database and GUI for case-insensitive tag management
This commit updates the DatabaseManager to support case-insensitive tag lookups and additions, ensuring consistent tag handling. The SearchGUI and TagManagerGUI have been modified to reflect these changes, allowing for improved user experience when managing tags. Additionally, the search logic in SearchStats and TagManagement has been adjusted for case-insensitive tag ID retrieval, enhancing overall functionality and reliability in tag management across the application.
This commit is contained in:
parent
d92f5750b7
commit
40ffc0692e
23
database.py
23
database.py
@ -144,16 +144,17 @@ class DatabaseManager:
|
||||
print(f"✅ Database initialized: {self.db_path}")
|
||||
|
||||
def load_tag_mappings(self) -> Tuple[Dict[int, str], Dict[str, int]]:
|
||||
"""Load tag name to ID and ID to name mappings from database"""
|
||||
"""Load tag name to ID and ID to name mappings from database (case-insensitive)"""
|
||||
with self.get_db_connection() as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute('SELECT id, tag_name FROM tags ORDER BY tag_name')
|
||||
cursor.execute('SELECT id, tag_name FROM tags ORDER BY LOWER(tag_name)')
|
||||
tag_id_to_name = {}
|
||||
tag_name_to_id = {}
|
||||
for row in cursor.fetchall():
|
||||
tag_id, tag_name = row
|
||||
tag_id_to_name[tag_id] = tag_name
|
||||
tag_name_to_id[tag_name] = tag_id
|
||||
# Use lowercase for case-insensitive lookups
|
||||
tag_name_to_id[tag_name.lower()] = tag_id
|
||||
return tag_id_to_name, tag_name_to_id
|
||||
|
||||
def get_existing_tag_ids_for_photo(self, photo_id: int) -> List[int]:
|
||||
@ -258,13 +259,23 @@ class DatabaseManager:
|
||||
return result[0] if result else None
|
||||
|
||||
def add_tag(self, tag_name: str) -> int:
|
||||
"""Add a tag to the database and return its ID"""
|
||||
"""Add a tag to the database and return its ID (case-insensitive)"""
|
||||
# Normalize tag name to lowercase for consistency
|
||||
normalized_tag_name = tag_name.lower().strip()
|
||||
|
||||
with self.get_db_connection() as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute('INSERT OR IGNORE INTO tags (tag_name) VALUES (?)', (tag_name,))
|
||||
# Check if tag already exists (case-insensitive)
|
||||
cursor.execute('SELECT id FROM tags WHERE LOWER(tag_name) = ?', (normalized_tag_name,))
|
||||
existing = cursor.fetchone()
|
||||
if existing:
|
||||
return existing[0]
|
||||
|
||||
# Insert new tag with original case
|
||||
cursor.execute('INSERT INTO tags (tag_name) VALUES (?)', (tag_name.strip(),))
|
||||
|
||||
# Get the tag ID
|
||||
cursor.execute('SELECT id FROM tags WHERE tag_name = ?', (tag_name,))
|
||||
cursor.execute('SELECT id FROM tags WHERE LOWER(tag_name) = ?', (normalized_tag_name,))
|
||||
result = cursor.fetchone()
|
||||
return result[0] if result else None
|
||||
|
||||
|
||||
131
search_gui.py
131
search_gui.py
@ -104,20 +104,20 @@ class SearchGUI:
|
||||
results_frame.pack(fill=tk.BOTH, expand=True)
|
||||
ttk.Label(results_frame, text="Results:", font=("Arial", 10, "bold")).pack(anchor="w")
|
||||
|
||||
columns = ("person", "tags", "open_dir", "open_photo", "path", "select")
|
||||
columns = ("select", "person", "tags", "open_dir", "open_photo", "path")
|
||||
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("open_dir", text="📁")
|
||||
tree.heading("open_photo", text="👤")
|
||||
tree.heading("path", text="Photo path", command=lambda: sort_treeview("path"))
|
||||
tree.heading("select", text="☑")
|
||||
tree.column("select", width=50, anchor="center")
|
||||
tree.column("person", width=180, anchor="w")
|
||||
tree.column("tags", width=200, anchor="w")
|
||||
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("select", width=50, anchor="center")
|
||||
tree.pack(fill=tk.BOTH, expand=True, pady=(4, 0))
|
||||
|
||||
# Buttons
|
||||
@ -198,7 +198,7 @@ class SearchGUI:
|
||||
tree.column("person", width=180, minwidth=50, anchor="w")
|
||||
tree.heading("person", text="Person", command=lambda: sort_treeview("person"))
|
||||
# Restore all columns to display
|
||||
tree["displaycolumns"] = ("person", "tags", "open_dir", "open_photo", "path", "select")
|
||||
tree["displaycolumns"] = ("select", "person", "tags", "open_dir", "open_photo", "path")
|
||||
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))
|
||||
@ -209,7 +209,7 @@ class SearchGUI:
|
||||
tree.column("person", width=0, minwidth=0, anchor="w")
|
||||
tree.heading("person", text="")
|
||||
# Also hide the column from display
|
||||
tree["displaycolumns"] = ("tags", "open_dir", "open_photo", "path", "select")
|
||||
tree["displaycolumns"] = ("select", "tags", "open_dir", "open_photo", "path")
|
||||
else:
|
||||
planned_label.pack(anchor="w")
|
||||
name_entry.configure(state="disabled")
|
||||
@ -220,7 +220,7 @@ class SearchGUI:
|
||||
tree.column("person", width=180, minwidth=50, anchor="w")
|
||||
tree.heading("person", text="Person", command=lambda: sort_treeview("person"))
|
||||
# Restore all columns to display
|
||||
tree["displaycolumns"] = ("person", "tags", "open_dir", "open_photo", "path", "select")
|
||||
tree["displaycolumns"] = ("select", "person", "tags", "open_dir", "open_photo", "path")
|
||||
|
||||
def clear_results():
|
||||
for i in tree.get_children():
|
||||
@ -243,13 +243,13 @@ class SearchGUI:
|
||||
# Show ALL tags for the photo, not just matching ones
|
||||
path, tag_info = row
|
||||
photo_tags = get_photo_tags_for_display(path)
|
||||
tree.insert("", tk.END, values=("", photo_tags, "📁", "👤", path, "☐"))
|
||||
tree.insert("", tk.END, values=("☐", "", photo_tags, "📁", "👤", path))
|
||||
else:
|
||||
# For name search: (full_name, path) - show person column
|
||||
full_name, p = row
|
||||
# Get tags for this photo
|
||||
photo_tags = get_photo_tags_for_display(p)
|
||||
tree.insert("", tk.END, values=(full_name, photo_tags, "📁", "👤", p, "☐"))
|
||||
tree.insert("", tk.END, values=("☐", full_name, photo_tags, "📁", "👤", p))
|
||||
|
||||
# Sort by appropriate column by default when results are first loaded
|
||||
if rows and self.sort_column is None:
|
||||
@ -321,13 +321,13 @@ class SearchGUI:
|
||||
"""Toggle checkbox selection for a photo."""
|
||||
if len(vals) < 6:
|
||||
return
|
||||
path = vals[4] # Photo path is now in column 4
|
||||
current_state = vals[5] # Checkbox is now in column 5
|
||||
current_state = vals[0] # Checkbox is now in column 0 (first)
|
||||
path = vals[5] # Photo path is now in column 5 (last)
|
||||
if current_state == "☐":
|
||||
# Select photo
|
||||
new_state = "☑"
|
||||
self.selected_photos[path] = {
|
||||
'person': vals[0],
|
||||
'person': vals[1], # Person is now in column 1
|
||||
'path': path
|
||||
}
|
||||
else:
|
||||
@ -338,7 +338,7 @@ class SearchGUI:
|
||||
|
||||
# Update the treeview
|
||||
new_vals = list(vals)
|
||||
new_vals[5] = new_state
|
||||
new_vals[0] = new_state
|
||||
tree.item(row_id, values=new_vals)
|
||||
|
||||
def tag_selected_photos():
|
||||
@ -375,9 +375,9 @@ 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[5] == "☑":
|
||||
if len(vals) >= 6 and vals[0] == "☑":
|
||||
new_vals = list(vals)
|
||||
new_vals[5] = "☐"
|
||||
new_vals[0] = "☐"
|
||||
tree.item(item, values=new_vals)
|
||||
|
||||
|
||||
@ -644,28 +644,25 @@ class SearchGUI:
|
||||
if not tag_name:
|
||||
return
|
||||
|
||||
# Resolve or create tag id
|
||||
if tag_name in tag_name_to_id:
|
||||
tag_id = tag_name_to_id[tag_name]
|
||||
# Resolve or create tag id (case-insensitive)
|
||||
normalized_tag_name = tag_name.lower().strip()
|
||||
if normalized_tag_name in tag_name_to_id:
|
||||
tag_id = tag_name_to_id[normalized_tag_name]
|
||||
else:
|
||||
# Create new tag in database
|
||||
with self.db.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,))
|
||||
result = cursor.fetchone()
|
||||
if result:
|
||||
tag_id = result[0]
|
||||
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()
|
||||
# Update the combobox values to include the new tag
|
||||
combo['values'] = existing_tags
|
||||
else:
|
||||
messagebox.showerror("Error", f"Failed to create tag '{tag_name}'", parent=popup)
|
||||
return
|
||||
# Create new tag in database using the database method
|
||||
tag_id = self.db.add_tag(tag_name)
|
||||
if tag_id:
|
||||
# Update mappings
|
||||
tag_name_to_id[normalized_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()
|
||||
# Update the combobox values to include the new tag
|
||||
combo['values'] = existing_tags
|
||||
else:
|
||||
messagebox.showerror("Error", f"Failed to create tag '{tag_name}'", parent=popup)
|
||||
return
|
||||
|
||||
# Add tag to all selected photos with single linkage type (0)
|
||||
affected = 0
|
||||
@ -776,7 +773,7 @@ class SearchGUI:
|
||||
# Display tag name with status information
|
||||
type_label = 'single' if linkage_type == 0 else 'bulk'
|
||||
photo_count = common_tag_data[tag_id]['photo_count']
|
||||
status_text = f" (saved {type_label}, on {photo_count}/{len(photo_ids)} photos)"
|
||||
status_text = f" (saved {type_label})"
|
||||
status_color = "black" if can_select else "gray"
|
||||
ttk.Label(frame, text=tag_name + status_text, foreground=status_color).pack(side=tk.LEFT)
|
||||
|
||||
@ -831,13 +828,13 @@ class SearchGUI:
|
||||
for item in tree.get_children():
|
||||
vals = tree.item(item, "values")
|
||||
if len(vals) >= 6:
|
||||
photo_path = vals[4] # Photo path is at index 4
|
||||
photo_path = vals[5] # Photo path is at index 5
|
||||
if photo_path in affected_photo_paths:
|
||||
# Get current tags for this photo from cache
|
||||
current_tags = get_photo_tags_for_display(photo_path)
|
||||
# Update the tags column (index 1)
|
||||
# Update the tags column (index 2)
|
||||
new_vals = list(vals)
|
||||
new_vals[1] = current_tags
|
||||
new_vals[2] = current_tags
|
||||
tree.item(item, values=new_vals)
|
||||
|
||||
def close_dialog():
|
||||
@ -865,19 +862,19 @@ class SearchGUI:
|
||||
# Determine column offsets based on search type
|
||||
is_tag_search = (search_type_var.get() == self.SEARCH_TYPES[2])
|
||||
if is_tag_search:
|
||||
# Tag search: person column is hidden, so all columns shift left by 1
|
||||
open_dir_col = "#2" # open_dir is now column 2
|
||||
face_col = "#3" # open_photo is now column 3
|
||||
path_col = "#4" # path is now column 4
|
||||
select_col = "#5" # select is now column 5
|
||||
path_index = 4 # path is at index 4 in values array
|
||||
# Tag search: person column is hidden, select is first
|
||||
select_col = "#1" # select is now column 1
|
||||
open_dir_col = "#3" # open_dir is now column 3
|
||||
face_col = "#4" # open_photo is now column 4
|
||||
path_col = "#5" # path is now column 5
|
||||
path_index = 5 # path is at index 5 in values array
|
||||
else:
|
||||
# Name search: all columns visible
|
||||
open_dir_col = "#3" # open_dir is column 3
|
||||
face_col = "#4" # open_photo is column 4
|
||||
path_col = "#5" # path is column 5
|
||||
select_col = "#6" # select is column 6
|
||||
path_index = 4 # path is at index 4 in values array
|
||||
# Name search: all columns visible, select is first
|
||||
select_col = "#1" # select is now 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 = vals[path_index] # Photo path
|
||||
if col_id == open_dir_col: # Open directory column
|
||||
@ -935,27 +932,27 @@ class SearchGUI:
|
||||
# Determine column offsets based on search type
|
||||
is_tag_search = (search_type_var.get() == self.SEARCH_TYPES[2])
|
||||
if is_tag_search:
|
||||
# Tag search: person column is hidden, so all columns shift left by 1
|
||||
tags_col = "#1" # tags is now column 1
|
||||
open_dir_col = "#2" # open_dir is now column 2
|
||||
face_col = "#3" # open_photo is now column 3
|
||||
path_col = "#4" # path is now column 4
|
||||
path_index = 4 # path is at index 4 in values array
|
||||
# Tag search: person column is hidden, select is first
|
||||
tags_col = "#2" # tags is now column 2
|
||||
open_dir_col = "#3" # open_dir is now column 3
|
||||
face_col = "#4" # open_photo is now column 4
|
||||
path_col = "#5" # path is now column 5
|
||||
path_index = 5 # path is at index 5 in values array
|
||||
else:
|
||||
# Name search: all columns 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 = 4 # path is at index 4 in values array
|
||||
# Name search: all columns visible, select is first
|
||||
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
|
||||
|
||||
if col_id == tags_col: # Tags column
|
||||
tree.config(cursor="")
|
||||
# Show tags tooltip
|
||||
if row_id:
|
||||
vals = tree.item(row_id, "values")
|
||||
if len(vals) >= 2:
|
||||
tags_text = vals[1] # Tags are at index 1
|
||||
if len(vals) >= 3:
|
||||
tags_text = vals[2] # Tags are at index 2 (after select and person)
|
||||
show_tooltip(tree, event.x_root, event.y_root, f"Tags: {tags_text}")
|
||||
elif col_id == open_dir_col: # Open directory column
|
||||
tree.config(cursor="hand2")
|
||||
@ -989,8 +986,8 @@ class SearchGUI:
|
||||
|
||||
# Show and center
|
||||
root.update_idletasks()
|
||||
# Widened to ensure the checkbox column is visible by default
|
||||
self.gui_core.center_window(root, 900, 520)
|
||||
# Widened to ensure all columns are visible by default
|
||||
self.gui_core.center_window(root, 1000, 520)
|
||||
root.deiconify()
|
||||
root.mainloop()
|
||||
return 0
|
||||
|
||||
@ -203,13 +203,15 @@ class SearchStats:
|
||||
if not tags:
|
||||
return []
|
||||
|
||||
# Get tag IDs for the provided tag names
|
||||
# Get tag IDs for the provided tag names (case-insensitive)
|
||||
tag_id_to_name, tag_name_to_id = self.db.load_tag_mappings()
|
||||
tag_ids = []
|
||||
|
||||
for tag_name in tags:
|
||||
if tag_name in tag_name_to_id:
|
||||
tag_ids.append(tag_name_to_id[tag_name])
|
||||
# Convert to lowercase for case-insensitive lookup
|
||||
normalized_tag_name = tag_name.lower().strip()
|
||||
if normalized_tag_name in tag_name_to_id:
|
||||
tag_ids.append(tag_name_to_id[normalized_tag_name])
|
||||
|
||||
if not tag_ids:
|
||||
return []
|
||||
|
||||
@ -206,8 +206,10 @@ class TagManager:
|
||||
tag_ids = []
|
||||
|
||||
for tag_name in tags:
|
||||
if tag_name in tag_name_to_id:
|
||||
tag_ids.append(tag_name_to_id[tag_name])
|
||||
# Convert to lowercase for case-insensitive lookup
|
||||
normalized_tag_name = tag_name.lower().strip()
|
||||
if normalized_tag_name in tag_name_to_id:
|
||||
tag_ids.append(tag_name_to_id[normalized_tag_name])
|
||||
|
||||
if not tag_ids:
|
||||
return []
|
||||
|
||||
@ -178,14 +178,13 @@ class TagManagerGUI:
|
||||
if not tag_name:
|
||||
return
|
||||
try:
|
||||
with self.db.get_db_connection() as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute('INSERT OR IGNORE INTO tags (tag_name) VALUES (?)', (tag_name,))
|
||||
conn.commit()
|
||||
new_tag_var.set("")
|
||||
refresh_tag_list()
|
||||
load_existing_tags()
|
||||
switch_view_mode(view_mode_var.get())
|
||||
# Use database method for case-insensitive tag creation
|
||||
tag_id = self.db.add_tag(tag_name)
|
||||
if tag_id:
|
||||
new_tag_var.set("")
|
||||
refresh_tag_list()
|
||||
load_existing_tags()
|
||||
switch_view_mode(view_mode_var.get())
|
||||
except Exception as e:
|
||||
messagebox.showerror("Error", f"Failed to add tag: {e}")
|
||||
|
||||
@ -502,6 +501,28 @@ class TagManagerGUI:
|
||||
]
|
||||
}
|
||||
|
||||
def get_people_names_for_photo(photo_id: int) -> str:
|
||||
"""Get people names for a photo as a formatted string for tooltip"""
|
||||
nonlocal people_names_cache
|
||||
people_names = people_names_cache.get(photo_id, [])
|
||||
if not people_names:
|
||||
return "No people identified"
|
||||
|
||||
# Remove commas from names (convert "Last, First" to "Last First")
|
||||
formatted_names = []
|
||||
for name in people_names:
|
||||
if ', ' in name:
|
||||
# Convert "Last, First" to "Last First"
|
||||
formatted_name = name.replace(', ', ' ')
|
||||
else:
|
||||
formatted_name = name
|
||||
formatted_names.append(formatted_name)
|
||||
|
||||
if len(formatted_names) <= 5:
|
||||
return f"People: {', '.join(formatted_names)}"
|
||||
else:
|
||||
return f"People: {', '.join(formatted_names[:5])}... (+{len(formatted_names)-5} more)"
|
||||
|
||||
def show_people_names_popup(photo_id: int, photo_filename: str):
|
||||
"""Show a popup window with the names of people identified in this photo"""
|
||||
nonlocal people_names_cache
|
||||
@ -658,19 +679,21 @@ class TagManagerGUI:
|
||||
if not tag_name:
|
||||
return
|
||||
# Resolve or create tag id
|
||||
if tag_name in tag_name_to_id:
|
||||
tag_id = tag_name_to_id[tag_name]
|
||||
# Case-insensitive tag lookup
|
||||
normalized_tag_name = tag_name.lower().strip()
|
||||
if normalized_tag_name in tag_name_to_id:
|
||||
tag_id = tag_name_to_id[normalized_tag_name]
|
||||
else:
|
||||
with self.db.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]
|
||||
tag_name_to_id[tag_name] = tag_id
|
||||
# Create new tag using database method
|
||||
tag_id = self.db.add_tag(tag_name)
|
||||
if tag_id:
|
||||
tag_name_to_id[normalized_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()
|
||||
else:
|
||||
return # Failed to create tag
|
||||
|
||||
affected = 0
|
||||
for photo in folder_info.get('photos', []):
|
||||
@ -895,15 +918,15 @@ class TagManagerGUI:
|
||||
tag_name = tag_var.get().strip()
|
||||
if not tag_name:
|
||||
return
|
||||
if tag_name in tag_name_to_id:
|
||||
tag_id = tag_name_to_id[tag_name]
|
||||
# Case-insensitive tag lookup
|
||||
normalized_tag_name = tag_name.lower().strip()
|
||||
if normalized_tag_name in tag_name_to_id:
|
||||
tag_id = tag_name_to_id[normalized_tag_name]
|
||||
else:
|
||||
with self.db.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]
|
||||
tag_name_to_id[tag_name] = tag_id
|
||||
# Create new tag using database method
|
||||
tag_id = self.db.add_tag(tag_name)
|
||||
if tag_id:
|
||||
tag_name_to_id[normalized_tag_name] = tag_id
|
||||
tag_id_to_name[tag_id] = tag_name
|
||||
if tag_name not in existing_tags:
|
||||
existing_tags.append(tag_name)
|
||||
@ -1106,19 +1129,21 @@ class TagManagerGUI:
|
||||
tag_name = tag_var.get().strip()
|
||||
if not tag_name:
|
||||
return
|
||||
if tag_name in tag_name_to_id:
|
||||
tag_id = tag_name_to_id[tag_name]
|
||||
# Case-insensitive tag lookup
|
||||
normalized_tag_name = tag_name.lower().strip()
|
||||
if normalized_tag_name in tag_name_to_id:
|
||||
tag_id = tag_name_to_id[normalized_tag_name]
|
||||
else:
|
||||
with self.db.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]
|
||||
tag_name_to_id[tag_name] = tag_id
|
||||
# Create new tag using database method
|
||||
tag_id = self.db.add_tag(tag_name)
|
||||
if tag_id:
|
||||
tag_name_to_id[normalized_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()
|
||||
else:
|
||||
return # Failed to create tag
|
||||
saved_types = get_saved_tag_types_for_photo(photo_id)
|
||||
existing_tag_ids = list(saved_types.keys())
|
||||
pending_tag_ids = pending_tag_changes.get(photo_id, [])
|
||||
@ -1324,9 +1349,10 @@ class TagManagerGUI:
|
||||
elif key == 'faces' and photo['face_count'] > 0:
|
||||
lbl = ttk.Label(row_frame, text=text, foreground="blue", cursor="hand2", wraplength=col['width'], anchor='w', justify='left')
|
||||
lbl.grid(row=0, column=i, padx=5, sticky=tk.W)
|
||||
lbl.bind("<Button-1>", lambda e, pid=photo['id'], fname=photo['filename']: show_people_names_popup(pid, fname))
|
||||
# Show people names in tooltip on hover instead of popup on click
|
||||
try:
|
||||
_ToolTip(lbl, "Click to see people in this photo")
|
||||
people_tooltip_text = get_people_names_for_photo(photo['id'])
|
||||
_ToolTip(lbl, people_tooltip_text)
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
@ -1409,9 +1435,10 @@ class TagManagerGUI:
|
||||
elif key == 'faces' and photo['face_count'] > 0:
|
||||
lbl = ttk.Label(row_frame, text=text, foreground="blue", cursor="hand2", wraplength=col['width'], anchor='w', justify='left')
|
||||
lbl.grid(row=0, column=col_idx, padx=5, sticky=tk.W)
|
||||
lbl.bind("<Button-1>", lambda e, pid=photo['id'], fname=photo['filename']: show_people_names_popup(pid, fname))
|
||||
# Show people names in tooltip on hover instead of popup on click
|
||||
try:
|
||||
_ToolTip(lbl, "Click to see people in this photo")
|
||||
people_tooltip_text = get_people_names_for_photo(photo['id'])
|
||||
_ToolTip(lbl, people_tooltip_text)
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
@ -1468,9 +1495,10 @@ class TagManagerGUI:
|
||||
elif key == 'faces' and photo['face_count'] > 0:
|
||||
lbl = ttk.Label(row_frame, text=text, foreground="blue", cursor="hand2", wraplength=col['width'], anchor='w', justify='left')
|
||||
lbl.grid(row=0, column=col_idx, padx=5, sticky=tk.W)
|
||||
lbl.bind("<Button-1>", lambda e, pid=photo['id'], fname=photo['filename']: show_people_names_popup(pid, fname))
|
||||
# Show people names in tooltip on hover instead of popup on click
|
||||
try:
|
||||
_ToolTip(lbl, "Click to see people in this photo")
|
||||
people_tooltip_text = get_people_names_for_photo(photo['id'])
|
||||
_ToolTip(lbl, people_tooltip_text)
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user