Add multi-person event selection

- Add _get_all_persons_for_event helper to get all persons for an event
  (e.g., both spouses for marriage events)
- Add _format_color_for_display helper to format colors for display
- Modify event click handler to select all persons involved in an event
- When clicking a marriage event, both spouses are now selected together
- Event lines are shown for all selected persons
This commit is contained in:
Daniel Viegas 2025-11-30 16:40:59 +01:00
parent cdba794cda
commit 137de07052

View File

@ -2759,6 +2759,111 @@ class MyTimelineView(NavigationView):
self.person_colors[person_handle] = color
return color
def _format_color_for_display(self, color: Tuple[float, float, float, float]) -> str:
"""
Format color tuple for display in tooltips.
Args:
color: RGBA color tuple (values 0-1).
Returns:
str: Formatted color string like "RGB(255, 128, 64)".
"""
r, g, b = color[0], color[1], color[2] # Ignore alpha for display
r_int = int(r * 255)
g_int = int(g * 255)
b_int = int(b * 255)
return f"RGB({r_int}, {g_int}, {b_int})"
def _get_all_persons_for_event(self, event: 'Event', event_data: 'TimelineEvent') -> List['Person']:
"""
Get all persons associated with an event.
For marriage events, returns both spouses.
For other events, returns the associated person (if any).
Args:
event: The event object.
event_data: The TimelineEvent object containing event data.
Returns:
List[Person]: List of all persons associated with the event.
"""
persons: List['Person'] = []
if not event or not self.dbstate.is_open():
return persons
# Check if this is a marriage event
try:
actual_event_type = event.get_type()
event_type_val = int(actual_event_type) if hasattr(actual_event_type, '__int__') else None
marriage_type_val = int(EventType.MARRIAGE) if hasattr(EventType.MARRIAGE, '__int__') else None
is_marriage = False
if event_type_val is not None and marriage_type_val is not None:
is_marriage = (event_type_val == marriage_type_val)
elif actual_event_type == EventType.MARRIAGE:
is_marriage = True
elif "Marriage" in str(actual_event_type) or "MARRIAGE" in str(actual_event_type):
is_marriage = True
except (ValueError, TypeError, AttributeError):
is_marriage = False
# For marriage events, get both spouses from the family
if is_marriage:
try:
event_handle = event.get_handle()
# Use cached family lookup
family = self._event_to_family_cache.get(event_handle)
# If not in cache, try to find the family by searching
if not family:
for family_handle in self.dbstate.db.get_family_handles():
try:
test_family = self.dbstate.db.get_family_from_handle(family_handle)
if not test_family:
continue
family_event_refs = test_family.get_event_ref_list()
for event_ref in family_event_refs:
if event_ref.ref == event_handle:
family = test_family
# Cache it for next time
self._event_to_family_cache[event_handle] = family
break
if family:
break
except (AttributeError, KeyError):
continue
# If we found a family, get both spouses
if family:
father_handle = family.get_father_handle()
mother_handle = family.get_mother_handle()
if father_handle:
try:
father = self.dbstate.db.get_person_from_handle(father_handle)
if father:
persons.append(father)
except (AttributeError, KeyError):
pass
if mother_handle:
try:
mother = self.dbstate.db.get_person_from_handle(mother_handle)
if mother:
persons.append(mother)
except (AttributeError, KeyError):
pass
except (AttributeError, KeyError):
pass
# For other events, add the associated person if available
if not persons and event_data.person:
persons.append(event_data.person)
return persons
def _get_person_vertical_line_x(self, person_index: int) -> float:
"""
Calculate the X position for a person's vertical connection line.
@ -3126,19 +3231,30 @@ class MyTimelineView(NavigationView):
# Navigate to this event in the view
self.uistate.set_active(event_handle, "Event")
# Toggle person selection (always toggle, even if event is same)
if clicked_event_data.person:
person_handle = clicked_event_data.person.get_handle()
if person_handle in self.selected_person_handles:
# Remove from selection
self.selected_person_handles.remove(person_handle)
# Clean up color if no longer selected
if person_handle in self.person_colors:
del self.person_colors[person_handle]
# Get all persons for this event (e.g., both spouses for marriage)
all_persons = self._get_all_persons_for_event(clicked_event_data.event, clicked_event_data)
# Toggle selection for all persons involved in the event
if all_persons:
# Check if all persons are already selected
all_selected = all(person.get_handle() in self.selected_person_handles for person in all_persons)
if all_selected:
# Remove all persons from selection
for person in all_persons:
person_handle = person.get_handle()
if person_handle in self.selected_person_handles:
self.selected_person_handles.remove(person_handle)
# Clean up color if no longer selected
if person_handle in self.person_colors:
del self.person_colors[person_handle]
else:
# Add to selection
self.selected_person_handles.add(person_handle)
# Color will be assigned when drawing
# Add all persons to selection
for person in all_persons:
person_handle = person.get_handle()
if person_handle not in self.selected_person_handles:
self.selected_person_handles.add(person_handle)
# Color will be assigned when drawing
self._queue_draw()
return False