From 137de07052273d566dd826ac2c782ef95b309ee4 Mon Sep 17 00:00:00 2001 From: Daniel Viegas Date: Sun, 30 Nov 2025 16:40:59 +0100 Subject: [PATCH] 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 --- MyTimeline.py | 140 +++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 128 insertions(+), 12 deletions(-) diff --git a/MyTimeline.py b/MyTimeline.py index 078d200..c2e04a2 100644 --- a/MyTimeline.py +++ b/MyTimeline.py @@ -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