Enhance PhotoTagger to include comprehensive person details in the database and GUI. Update data handling to support middle names, maiden names, and date of birth, improving user experience and data integrity. Revise database queries and UI components for better data entry and display, ensuring all relevant information is captured during identification.
This commit is contained in:
parent
ee3638b929
commit
5ecfe1121e
499
photo_tagger.py
499
photo_tagger.py
@ -2592,12 +2592,40 @@ class PhotoTagger:
|
||||
with self.get_db_connection() as conn:
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Pre-fetch all person names
|
||||
# Pre-fetch all person names and details
|
||||
person_ids = list(matches_by_matched.keys())
|
||||
if person_ids:
|
||||
placeholders = ','.join('?' * len(person_ids))
|
||||
cursor.execute(f'SELECT id, first_name, last_name FROM people WHERE id IN ({placeholders})', person_ids)
|
||||
data_cache['person_names'] = {row[0]: f"{row[2]}, {row[1]}".strip(", ").strip() for row in cursor.fetchall()}
|
||||
cursor.execute(f'SELECT id, first_name, last_name, middle_name, maiden_name, date_of_birth FROM people WHERE id IN ({placeholders})', person_ids)
|
||||
data_cache['person_details'] = {}
|
||||
for row in cursor.fetchall():
|
||||
person_id = row[0]
|
||||
first_name = row[1] or ''
|
||||
last_name = row[2] or ''
|
||||
middle_name = row[3] or ''
|
||||
maiden_name = row[4] or ''
|
||||
date_of_birth = row[5] or ''
|
||||
|
||||
# Create full name display
|
||||
name_parts = []
|
||||
if first_name:
|
||||
name_parts.append(first_name)
|
||||
if middle_name:
|
||||
name_parts.append(middle_name)
|
||||
if last_name:
|
||||
name_parts.append(last_name)
|
||||
if maiden_name:
|
||||
name_parts.append(f"({maiden_name})")
|
||||
|
||||
full_name = ' '.join(name_parts)
|
||||
data_cache['person_details'][person_id] = {
|
||||
'full_name': full_name,
|
||||
'first_name': first_name,
|
||||
'last_name': last_name,
|
||||
'middle_name': middle_name,
|
||||
'maiden_name': maiden_name,
|
||||
'date_of_birth': date_of_birth
|
||||
}
|
||||
|
||||
# Pre-fetch all photo paths (both matched and unidentified)
|
||||
all_photo_ids = set()
|
||||
@ -2612,7 +2640,7 @@ class PhotoTagger:
|
||||
cursor.execute(f'SELECT id, path FROM photos WHERE id IN ({placeholders})', photo_ids_list)
|
||||
data_cache['photo_paths'] = {row[0]: row[1] for row in cursor.fetchall()}
|
||||
|
||||
print(f"✅ Pre-fetched {len(data_cache.get('person_names', {}))} person names and {len(data_cache.get('photo_paths', {}))} photo paths")
|
||||
print(f"✅ Pre-fetched {len(data_cache.get('person_details', {}))} person details and {len(data_cache.get('photo_paths', {}))} photo paths")
|
||||
|
||||
identified_count = 0
|
||||
|
||||
@ -2772,7 +2800,8 @@ class PhotoTagger:
|
||||
)
|
||||
|
||||
# Use cached person name instead of database query
|
||||
person_name = data_cache['person_names'].get(match['person_id'], "Unknown")
|
||||
person_details = data_cache['person_details'].get(match['person_id'], {})
|
||||
person_name = person_details.get('full_name', "Unknown")
|
||||
|
||||
# Track this face as identified for this person
|
||||
identified_faces_per_person[matched_id].add(match['unidentified_id'])
|
||||
@ -2874,7 +2903,8 @@ class PhotoTagger:
|
||||
if matches_for_current_person:
|
||||
person_id = matches_for_current_person[0]['person_id']
|
||||
# Use cached person name instead of database query
|
||||
person_name = data_cache['person_names'].get(person_id, "Unknown")
|
||||
person_details = data_cache['person_details'].get(person_id, {})
|
||||
person_name = person_details.get('full_name', "Unknown")
|
||||
save_btn.config(text=f"💾 Save changes for {person_name}")
|
||||
else:
|
||||
save_btn.config(text="💾 Save Changes")
|
||||
@ -2934,11 +2964,22 @@ class PhotoTagger:
|
||||
first_match = matches_for_this_person[0]
|
||||
|
||||
# Use cached data instead of database queries
|
||||
person_name = data_cache['person_names'].get(first_match['person_id'], "Unknown")
|
||||
person_details = data_cache['person_details'].get(first_match['person_id'], {})
|
||||
person_name = person_details.get('full_name', "Unknown")
|
||||
date_of_birth = person_details.get('date_of_birth', '')
|
||||
matched_photo_path = data_cache['photo_paths'].get(first_match['matched_photo_id'], None)
|
||||
|
||||
# Create detailed person info display
|
||||
person_info_lines = [f"👤 Person: {person_name}"]
|
||||
if date_of_birth:
|
||||
person_info_lines.append(f"📅 Born: {date_of_birth}")
|
||||
person_info_lines.extend([
|
||||
f"📁 Photo: {first_match['matched_filename']}",
|
||||
f"📍 Face location: {first_match['matched_location']}"
|
||||
])
|
||||
|
||||
# Update matched person info
|
||||
matched_info_label.config(text=f"👤 Person: {person_name}\n📁 Photo: {first_match['matched_filename']}\n📍 Face location: {first_match['matched_location']}")
|
||||
matched_info_label.config(text="\n".join(person_info_lines))
|
||||
|
||||
# Display matched person face
|
||||
matched_canvas.delete("all")
|
||||
@ -3244,30 +3285,43 @@ class PhotoTagger:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(
|
||||
"""
|
||||
SELECT p.id, p.first_name, p.last_name, COUNT(f.id) as face_count
|
||||
SELECT p.id, p.first_name, p.last_name, p.middle_name, p.maiden_name, p.date_of_birth, COUNT(f.id) as face_count
|
||||
FROM people p
|
||||
JOIN faces f ON f.person_id = p.id
|
||||
GROUP BY p.id, p.last_name, p.first_name
|
||||
GROUP BY p.id, p.last_name, p.first_name, p.middle_name, p.maiden_name, p.date_of_birth
|
||||
HAVING face_count > 0
|
||||
ORDER BY p.last_name, p.first_name COLLATE NOCASE
|
||||
"""
|
||||
)
|
||||
people_data = []
|
||||
for (pid, first_name, last_name, count) in cursor.fetchall():
|
||||
if last_name and first_name:
|
||||
display_name = f"{last_name}, {first_name}"
|
||||
elif last_name:
|
||||
display_name = last_name
|
||||
elif first_name:
|
||||
display_name = first_name
|
||||
else:
|
||||
display_name = "Unknown"
|
||||
for (pid, first_name, last_name, middle_name, maiden_name, date_of_birth, count) in cursor.fetchall():
|
||||
# Create full name display with all available information
|
||||
name_parts = []
|
||||
if first_name:
|
||||
name_parts.append(first_name)
|
||||
if middle_name:
|
||||
name_parts.append(middle_name)
|
||||
if last_name:
|
||||
name_parts.append(last_name)
|
||||
if maiden_name:
|
||||
name_parts.append(f"({maiden_name})")
|
||||
|
||||
full_name = ' '.join(name_parts) if name_parts else "Unknown"
|
||||
|
||||
# Create detailed display with date of birth if available
|
||||
display_name = full_name
|
||||
if date_of_birth:
|
||||
display_name += f" - Born: {date_of_birth}"
|
||||
|
||||
people_data.append({
|
||||
'id': pid,
|
||||
'name': display_name,
|
||||
'full_name': full_name,
|
||||
'first_name': first_name or "",
|
||||
'last_name': last_name or "",
|
||||
'middle_name': middle_name or "",
|
||||
'maiden_name': maiden_name or "",
|
||||
'date_of_birth': date_of_birth or "",
|
||||
'count': count
|
||||
})
|
||||
|
||||
@ -3475,43 +3529,324 @@ class PhotoTagger:
|
||||
# Use pre-loaded data instead of database query
|
||||
cur_first = person_record.get('first_name', '')
|
||||
cur_last = person_record.get('last_name', '')
|
||||
cur_middle = person_record.get('middle_name', '')
|
||||
cur_maiden = person_record.get('maiden_name', '')
|
||||
cur_dob = person_record.get('date_of_birth', '')
|
||||
|
||||
# Create a container frame for the text boxes and labels
|
||||
# Create a larger container frame for the text boxes and labels
|
||||
edit_container = ttk.Frame(row_frame)
|
||||
edit_container.pack(side=tk.LEFT)
|
||||
edit_container.pack(side=tk.LEFT, fill=tk.X, expand=True)
|
||||
|
||||
# Text boxes row
|
||||
text_frame = ttk.Frame(edit_container)
|
||||
text_frame.pack(side=tk.TOP)
|
||||
|
||||
# Separate Last name and First name inputs
|
||||
last_var = tk.StringVar(value=cur_last)
|
||||
last_entry = ttk.Entry(text_frame, textvariable=last_var, width=12)
|
||||
last_entry.pack(side=tk.LEFT)
|
||||
# Create a grid layout for better organization
|
||||
# First name field with label
|
||||
first_frame = ttk.Frame(edit_container)
|
||||
first_frame.grid(row=0, column=0, padx=(0, 10), pady=(0, 5), sticky=tk.W)
|
||||
|
||||
first_var = tk.StringVar(value=cur_first)
|
||||
first_entry = ttk.Entry(text_frame, textvariable=first_var, width=12)
|
||||
first_entry.pack(side=tk.LEFT, padx=(5, 0))
|
||||
first_entry = ttk.Entry(first_frame, textvariable=first_var, width=15)
|
||||
first_entry.pack(side=tk.TOP)
|
||||
first_entry.focus_set()
|
||||
|
||||
# Help text labels row (below text boxes)
|
||||
help_frame = ttk.Frame(edit_container)
|
||||
help_frame.pack(side=tk.TOP, pady=(2, 0))
|
||||
first_label = ttk.Label(first_frame, text="First Name", font=("Arial", 8), foreground="gray")
|
||||
first_label.pack(side=tk.TOP, pady=(2, 0))
|
||||
|
||||
last_label = ttk.Label(help_frame, text="Last", font=("Arial", 8), foreground="gray")
|
||||
last_label.pack(side=tk.LEFT)
|
||||
# Last name field with label
|
||||
last_frame = ttk.Frame(edit_container)
|
||||
last_frame.grid(row=0, column=1, padx=(0, 10), pady=(0, 5), sticky=tk.W)
|
||||
|
||||
first_label = ttk.Label(help_frame, text="First", font=("Arial", 8), foreground="gray")
|
||||
first_label.pack(side=tk.LEFT, padx=(5, 0))
|
||||
last_var = tk.StringVar(value=cur_last)
|
||||
last_entry = ttk.Entry(last_frame, textvariable=last_var, width=15)
|
||||
last_entry.pack(side=tk.TOP)
|
||||
|
||||
last_label = ttk.Label(last_frame, text="Last Name", font=("Arial", 8), foreground="gray")
|
||||
last_label.pack(side=tk.TOP, pady=(2, 0))
|
||||
|
||||
# Middle name field with label
|
||||
middle_frame = ttk.Frame(edit_container)
|
||||
middle_frame.grid(row=0, column=2, padx=(0, 10), pady=(0, 5), sticky=tk.W)
|
||||
|
||||
middle_var = tk.StringVar(value=cur_middle)
|
||||
middle_entry = ttk.Entry(middle_frame, textvariable=middle_var, width=15)
|
||||
middle_entry.pack(side=tk.TOP)
|
||||
|
||||
middle_label = ttk.Label(middle_frame, text="Middle Name", font=("Arial", 8), foreground="gray")
|
||||
middle_label.pack(side=tk.TOP, pady=(2, 0))
|
||||
|
||||
# Maiden name field with label
|
||||
maiden_frame = ttk.Frame(edit_container)
|
||||
maiden_frame.grid(row=1, column=0, padx=(0, 10), pady=(0, 5), sticky=tk.W)
|
||||
|
||||
maiden_var = tk.StringVar(value=cur_maiden)
|
||||
maiden_entry = ttk.Entry(maiden_frame, textvariable=maiden_var, width=15)
|
||||
maiden_entry.pack(side=tk.TOP)
|
||||
|
||||
maiden_label = ttk.Label(maiden_frame, text="Maiden Name", font=("Arial", 8), foreground="gray")
|
||||
maiden_label.pack(side=tk.TOP, pady=(2, 0))
|
||||
|
||||
# Date of birth field with label and calendar button
|
||||
dob_frame = ttk.Frame(edit_container)
|
||||
dob_frame.grid(row=1, column=1, columnspan=2, padx=(0, 10), pady=(0, 5), sticky=tk.W)
|
||||
|
||||
# Create a frame for the date picker
|
||||
date_picker_frame = ttk.Frame(dob_frame)
|
||||
date_picker_frame.pack(side=tk.TOP)
|
||||
|
||||
dob_var = tk.StringVar(value=cur_dob)
|
||||
dob_entry = ttk.Entry(date_picker_frame, textvariable=dob_var, width=20, state='readonly')
|
||||
dob_entry.pack(side=tk.LEFT, fill=tk.X, expand=True)
|
||||
|
||||
# Calendar button
|
||||
calendar_btn = ttk.Button(date_picker_frame, text="📅", width=3, command=lambda: open_calendar())
|
||||
calendar_btn.pack(side=tk.RIGHT, padx=(5, 0))
|
||||
|
||||
dob_label = ttk.Label(dob_frame, text="Date of Birth", font=("Arial", 8), foreground="gray")
|
||||
dob_label.pack(side=tk.TOP, pady=(2, 0))
|
||||
|
||||
def open_calendar():
|
||||
"""Open a visual calendar dialog to select date of birth"""
|
||||
from datetime import datetime, date, timedelta
|
||||
import calendar
|
||||
|
||||
# Create calendar window
|
||||
calendar_window = tk.Toplevel(root)
|
||||
calendar_window.title("Select Date of Birth")
|
||||
calendar_window.resizable(False, False)
|
||||
calendar_window.transient(root)
|
||||
calendar_window.grab_set()
|
||||
|
||||
# Calculate center position before showing the window
|
||||
window_width = 350
|
||||
window_height = 400
|
||||
screen_width = calendar_window.winfo_screenwidth()
|
||||
screen_height = calendar_window.winfo_screenheight()
|
||||
x = (screen_width // 2) - (window_width // 2)
|
||||
y = (screen_height // 2) - (window_height // 2)
|
||||
|
||||
# Set geometry with center position before showing
|
||||
calendar_window.geometry(f"{window_width}x{window_height}+{x}+{y}")
|
||||
|
||||
# Calendar variables
|
||||
current_date = datetime.now()
|
||||
|
||||
# Check if there's already a date selected
|
||||
existing_date_str = dob_var.get().strip()
|
||||
if existing_date_str:
|
||||
try:
|
||||
existing_date = datetime.strptime(existing_date_str, '%Y-%m-%d').date()
|
||||
display_year = existing_date.year
|
||||
display_month = existing_date.month
|
||||
selected_date = existing_date
|
||||
except ValueError:
|
||||
# If existing date is invalid, use default
|
||||
display_year = current_date.year - 25
|
||||
display_month = 1
|
||||
selected_date = None
|
||||
else:
|
||||
# Default to 25 years ago
|
||||
display_year = current_date.year - 25
|
||||
display_month = 1
|
||||
selected_date = None
|
||||
|
||||
# Month names
|
||||
month_names = ["January", "February", "March", "April", "May", "June",
|
||||
"July", "August", "September", "October", "November", "December"]
|
||||
|
||||
# Configure custom styles for better visual highlighting
|
||||
style = ttk.Style()
|
||||
|
||||
# Selected date style - bright blue background with white text
|
||||
style.configure("Selected.TButton",
|
||||
background="#0078d4",
|
||||
foreground="white",
|
||||
font=("Arial", 9, "bold"),
|
||||
relief="raised",
|
||||
borderwidth=2)
|
||||
style.map("Selected.TButton",
|
||||
background=[("active", "#106ebe")],
|
||||
relief=[("pressed", "sunken")])
|
||||
|
||||
# Today's date style - orange background
|
||||
style.configure("Today.TButton",
|
||||
background="#ff8c00",
|
||||
foreground="white",
|
||||
font=("Arial", 9, "bold"),
|
||||
relief="raised",
|
||||
borderwidth=1)
|
||||
style.map("Today.TButton",
|
||||
background=[("active", "#e67e00")],
|
||||
relief=[("pressed", "sunken")])
|
||||
|
||||
# Calendar-specific normal button style (don't affect global TButton)
|
||||
style.configure("Calendar.TButton",
|
||||
font=("Arial", 9),
|
||||
relief="flat")
|
||||
style.map("Calendar.TButton",
|
||||
background=[("active", "#e1e1e1")],
|
||||
relief=[("pressed", "sunken")])
|
||||
|
||||
# Main frame
|
||||
main_cal_frame = ttk.Frame(calendar_window, padding="10")
|
||||
main_cal_frame.pack(fill=tk.BOTH, expand=True)
|
||||
|
||||
# Header frame with navigation
|
||||
header_frame = ttk.Frame(main_cal_frame)
|
||||
header_frame.pack(fill=tk.X, pady=(0, 10))
|
||||
|
||||
# Month/Year display and navigation
|
||||
nav_frame = ttk.Frame(header_frame)
|
||||
nav_frame.pack()
|
||||
|
||||
def update_calendar():
|
||||
"""Update the calendar display"""
|
||||
# Clear existing calendar
|
||||
for widget in calendar_frame.winfo_children():
|
||||
widget.destroy()
|
||||
|
||||
# Update header
|
||||
month_year_label.config(text=f"{month_names[display_month-1]} {display_year}")
|
||||
|
||||
# Get calendar data
|
||||
cal = calendar.monthcalendar(display_year, display_month)
|
||||
|
||||
# Day headers
|
||||
day_headers = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
|
||||
for i, day in enumerate(day_headers):
|
||||
label = ttk.Label(calendar_frame, text=day, font=("Arial", 9, "bold"))
|
||||
label.grid(row=0, column=i, padx=1, pady=1, sticky="nsew")
|
||||
|
||||
# Calendar days
|
||||
for week_num, week in enumerate(cal):
|
||||
for day_num, day in enumerate(week):
|
||||
if day == 0:
|
||||
# Empty cell
|
||||
label = ttk.Label(calendar_frame, text="")
|
||||
label.grid(row=week_num+1, column=day_num, padx=1, pady=1, sticky="nsew")
|
||||
else:
|
||||
# Day button
|
||||
def make_day_handler(day_value):
|
||||
def select_day():
|
||||
nonlocal selected_date
|
||||
selected_date = date(display_year, display_month, day_value)
|
||||
# Reset all buttons to normal calendar style
|
||||
for widget in calendar_frame.winfo_children():
|
||||
if isinstance(widget, ttk.Button):
|
||||
widget.config(style="Calendar.TButton")
|
||||
# Highlight selected day with prominent style
|
||||
for widget in calendar_frame.winfo_children():
|
||||
if isinstance(widget, ttk.Button) and widget.cget("text") == str(day_value):
|
||||
widget.config(style="Selected.TButton")
|
||||
return select_day
|
||||
|
||||
day_btn = ttk.Button(calendar_frame, text=str(day),
|
||||
command=make_day_handler(day),
|
||||
width=3, style="Calendar.TButton")
|
||||
day_btn.grid(row=week_num+1, column=day_num, padx=1, pady=1, sticky="nsew")
|
||||
|
||||
# Check if this day should be highlighted
|
||||
is_today = (display_year == current_date.year and
|
||||
display_month == current_date.month and
|
||||
day == current_date.day)
|
||||
is_selected = (selected_date and
|
||||
selected_date.year == display_year and
|
||||
selected_date.month == display_month and
|
||||
selected_date.day == day)
|
||||
|
||||
if is_selected:
|
||||
day_btn.config(style="Selected.TButton")
|
||||
elif is_today:
|
||||
day_btn.config(style="Today.TButton")
|
||||
|
||||
# Navigation functions
|
||||
def prev_year():
|
||||
nonlocal display_year
|
||||
display_year = max(1900, display_year - 1)
|
||||
update_calendar()
|
||||
|
||||
def next_year():
|
||||
nonlocal display_year
|
||||
display_year = min(current_date.year, display_year + 1)
|
||||
update_calendar()
|
||||
|
||||
def prev_month():
|
||||
nonlocal display_month, display_year
|
||||
if display_month > 1:
|
||||
display_month -= 1
|
||||
else:
|
||||
display_month = 12
|
||||
display_year = max(1900, display_year - 1)
|
||||
update_calendar()
|
||||
|
||||
def next_month():
|
||||
nonlocal display_month, display_year
|
||||
if display_month < 12:
|
||||
display_month += 1
|
||||
else:
|
||||
display_month = 1
|
||||
display_year = min(current_date.year, display_year + 1)
|
||||
update_calendar()
|
||||
|
||||
# Navigation buttons
|
||||
prev_year_btn = ttk.Button(nav_frame, text="<<", width=3, command=prev_year)
|
||||
prev_year_btn.pack(side=tk.LEFT, padx=(0, 2))
|
||||
|
||||
prev_month_btn = ttk.Button(nav_frame, text="<", width=3, command=prev_month)
|
||||
prev_month_btn.pack(side=tk.LEFT, padx=(0, 5))
|
||||
|
||||
month_year_label = ttk.Label(nav_frame, text="", font=("Arial", 12, "bold"))
|
||||
month_year_label.pack(side=tk.LEFT, padx=5)
|
||||
|
||||
next_month_btn = ttk.Button(nav_frame, text=">", width=3, command=next_month)
|
||||
next_month_btn.pack(side=tk.LEFT, padx=(5, 2))
|
||||
|
||||
next_year_btn = ttk.Button(nav_frame, text=">>", width=3, command=next_year)
|
||||
next_year_btn.pack(side=tk.LEFT)
|
||||
|
||||
# Calendar grid frame
|
||||
calendar_frame = ttk.Frame(main_cal_frame)
|
||||
calendar_frame.pack(fill=tk.BOTH, expand=True, pady=(0, 10))
|
||||
|
||||
# Configure grid weights
|
||||
for i in range(7):
|
||||
calendar_frame.columnconfigure(i, weight=1)
|
||||
for i in range(7):
|
||||
calendar_frame.rowconfigure(i, weight=1)
|
||||
|
||||
# Buttons frame
|
||||
buttons_frame = ttk.Frame(main_cal_frame)
|
||||
buttons_frame.pack(fill=tk.X)
|
||||
|
||||
def select_date():
|
||||
"""Select the date and close calendar"""
|
||||
if selected_date:
|
||||
date_str = selected_date.strftime('%Y-%m-%d')
|
||||
dob_var.set(date_str)
|
||||
calendar_window.destroy()
|
||||
else:
|
||||
messagebox.showwarning("No Date Selected", "Please select a date from the calendar.")
|
||||
|
||||
def cancel_selection():
|
||||
"""Cancel date selection"""
|
||||
calendar_window.destroy()
|
||||
|
||||
# Buttons
|
||||
ttk.Button(buttons_frame, text="Select", command=select_date).pack(side=tk.LEFT, padx=(0, 5))
|
||||
ttk.Button(buttons_frame, text="Cancel", command=cancel_selection).pack(side=tk.LEFT)
|
||||
|
||||
# Initialize calendar
|
||||
update_calendar()
|
||||
|
||||
def save_rename():
|
||||
new_first = first_var.get().strip()
|
||||
new_last = last_var.get().strip()
|
||||
new_middle = middle_var.get().strip()
|
||||
new_maiden = maiden_var.get().strip()
|
||||
new_dob = dob_var.get().strip()
|
||||
|
||||
if not new_first and not new_last:
|
||||
messagebox.showwarning("Invalid name", "At least one of First or Last name must be provided.")
|
||||
return
|
||||
|
||||
# Check for duplicates in local data first
|
||||
# Check for duplicates in local data first (based on first and last name only)
|
||||
for person in people_data:
|
||||
if person['id'] != person_record['id'] and person['first_name'] == new_first and person['last_name'] == new_last:
|
||||
display_name = f"{new_last}, {new_first}".strip(", ").strip()
|
||||
@ -3521,13 +3856,37 @@ class PhotoTagger:
|
||||
# Single database access - save to database
|
||||
with self.get_db_connection() as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute('UPDATE people SET first_name = ?, last_name = ? WHERE id = ?', (new_first, new_last, person_record['id']))
|
||||
cursor.execute('UPDATE people SET first_name = ?, last_name = ?, middle_name = ?, maiden_name = ?, date_of_birth = ? WHERE id = ?',
|
||||
(new_first, new_last, new_middle, new_maiden, new_dob, person_record['id']))
|
||||
conn.commit()
|
||||
|
||||
# Update local data structure
|
||||
person_record['first_name'] = new_first
|
||||
person_record['last_name'] = new_last
|
||||
person_record['name'] = f"{new_last}, {new_first}".strip(", ").strip() if new_first or new_last else "Unknown"
|
||||
person_record['middle_name'] = new_middle
|
||||
person_record['maiden_name'] = new_maiden
|
||||
person_record['date_of_birth'] = new_dob
|
||||
|
||||
# Recreate the full display name with all available information
|
||||
name_parts = []
|
||||
if new_first:
|
||||
name_parts.append(new_first)
|
||||
if new_middle:
|
||||
name_parts.append(new_middle)
|
||||
if new_last:
|
||||
name_parts.append(new_last)
|
||||
if new_maiden:
|
||||
name_parts.append(f"({new_maiden})")
|
||||
|
||||
full_name = ' '.join(name_parts) if name_parts else "Unknown"
|
||||
|
||||
# Create detailed display with date of birth if available
|
||||
display_name = full_name
|
||||
if new_dob:
|
||||
display_name += f" - Born: {new_dob}"
|
||||
|
||||
person_record['name'] = display_name
|
||||
person_record['full_name'] = full_name
|
||||
|
||||
# Refresh list
|
||||
current_selected_id = person_record['id']
|
||||
@ -3556,16 +3915,64 @@ class PhotoTagger:
|
||||
w.destroy()
|
||||
rebuild_row(row_frame, person_record, row_index)
|
||||
|
||||
save_btn = ttk.Button(row_frame, text="💾 Save", command=save_rename)
|
||||
save_btn = ttk.Button(row_frame, text="💾", width=3, command=save_rename)
|
||||
save_btn.pack(side=tk.LEFT, padx=(5, 0))
|
||||
cancel_btn = ttk.Button(row_frame, text="✖ Cancel", command=cancel_edit)
|
||||
cancel_btn = ttk.Button(row_frame, text="✖", width=3, command=cancel_edit)
|
||||
cancel_btn.pack(side=tk.LEFT, padx=(5, 0))
|
||||
|
||||
# Keyboard shortcuts
|
||||
first_entry.bind('<Return>', lambda e: save_rename())
|
||||
last_entry.bind('<Return>', lambda e: save_rename())
|
||||
# Configure custom disabled button style for better visibility
|
||||
style = ttk.Style()
|
||||
style.configure("Disabled.TButton",
|
||||
background="#d3d3d3", # Light gray background
|
||||
foreground="#808080", # Dark gray text
|
||||
relief="flat",
|
||||
borderwidth=1)
|
||||
|
||||
def validate_save_button():
|
||||
"""Enable/disable save button based on required fields"""
|
||||
first_val = first_var.get().strip()
|
||||
last_val = last_var.get().strip()
|
||||
dob_val = dob_var.get().strip()
|
||||
|
||||
# Enable save button only if both name fields and date of birth are provided
|
||||
has_first = bool(first_val)
|
||||
has_last = bool(last_val)
|
||||
has_dob = bool(dob_val)
|
||||
|
||||
if has_first and has_last and has_dob:
|
||||
save_btn.config(state="normal")
|
||||
# Reset to normal styling when enabled
|
||||
save_btn.config(style="TButton")
|
||||
else:
|
||||
save_btn.config(state="disabled")
|
||||
# Apply custom disabled styling for better visibility
|
||||
save_btn.config(style="Disabled.TButton")
|
||||
|
||||
# Set up validation callbacks for all input fields
|
||||
first_var.trace('w', lambda *args: validate_save_button())
|
||||
last_var.trace('w', lambda *args: validate_save_button())
|
||||
middle_var.trace('w', lambda *args: validate_save_button())
|
||||
maiden_var.trace('w', lambda *args: validate_save_button())
|
||||
dob_var.trace('w', lambda *args: validate_save_button())
|
||||
|
||||
# Initial validation
|
||||
validate_save_button()
|
||||
|
||||
# Keyboard shortcuts (only work when save button is enabled)
|
||||
def try_save():
|
||||
if save_btn.cget('state') == 'normal':
|
||||
save_rename()
|
||||
|
||||
first_entry.bind('<Return>', lambda e: try_save())
|
||||
last_entry.bind('<Return>', lambda e: try_save())
|
||||
middle_entry.bind('<Return>', lambda e: try_save())
|
||||
maiden_entry.bind('<Return>', lambda e: try_save())
|
||||
dob_entry.bind('<Return>', lambda e: try_save())
|
||||
first_entry.bind('<Escape>', lambda e: cancel_edit())
|
||||
last_entry.bind('<Escape>', lambda e: cancel_edit())
|
||||
middle_entry.bind('<Escape>', lambda e: cancel_edit())
|
||||
maiden_entry.bind('<Escape>', lambda e: cancel_edit())
|
||||
dob_entry.bind('<Escape>', lambda e: cancel_edit())
|
||||
|
||||
def rebuild_row(row_frame, p, i):
|
||||
# Edit button (on the left)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user