Fix marriage event display in timeline
- Add event-to-family cache for efficient family lookup - Improve marriage event detection using multiple comparison methods - Show both spouses for marriage events (e.g., 'Date - Marriage - Person1 & Person2') - Fix label rendering by using set_text instead of set_markup for plain text - Add fallback logic to ensure marriage events always show date and event type - Improve family event indexing to associate family events with spouses
This commit is contained in:
parent
f0d52456bc
commit
cdba794cda
186
MyTimeline.py
186
MyTimeline.py
@ -480,6 +480,7 @@ class MyTimelineView(NavigationView):
|
||||
self._cached_min_date: Optional[int] = None
|
||||
self._cached_max_date: Optional[int] = None
|
||||
self._event_to_person_cache: Dict[str, Optional['Person']] = {} # Event handle -> Person object (or None)
|
||||
self._event_to_family_cache: Dict[str, Optional['Family']] = {} # Event handle -> Family object (or None)
|
||||
self._event_type_normalization_cache: Dict[int, int] = {} # Cache for event type normalization (key: hash/id, value: normalized int)
|
||||
self._normalized_active_event_types: Optional[Set[int]] = None # Pre-computed normalized active types
|
||||
|
||||
@ -588,8 +589,9 @@ class MyTimelineView(NavigationView):
|
||||
# Clear selected persons and colors
|
||||
self.selected_person_handles.clear()
|
||||
self.person_colors.clear()
|
||||
# Clear event-to-person cache
|
||||
# Clear event-to-person and event-to-family caches
|
||||
self._event_to_person_cache.clear()
|
||||
self._event_to_family_cache.clear()
|
||||
|
||||
# Connect to new database signals
|
||||
if db and db.is_open():
|
||||
@ -2236,6 +2238,7 @@ class MyTimelineView(NavigationView):
|
||||
"""
|
||||
Build a reverse index mapping event handles to person objects.
|
||||
This is much more efficient than searching through all persons for each event.
|
||||
Also indexes family events (like marriages) to one of the spouses.
|
||||
"""
|
||||
if not self.dbstate.is_open():
|
||||
return
|
||||
@ -2258,6 +2261,49 @@ class MyTimelineView(NavigationView):
|
||||
|
||||
except (AttributeError, KeyError):
|
||||
continue
|
||||
|
||||
# Also index family events (marriages, etc.) to one of the spouses
|
||||
# Also build event-to-family cache for efficient lookup
|
||||
for family_handle in self.dbstate.db.get_family_handles():
|
||||
try:
|
||||
family = self.dbstate.db.get_family_from_handle(family_handle)
|
||||
if not family:
|
||||
continue
|
||||
|
||||
# Get family event references
|
||||
family_event_refs = family.get_event_ref_list()
|
||||
if not family_event_refs:
|
||||
continue
|
||||
|
||||
# Cache event-to-family mapping
|
||||
for event_ref in family_event_refs:
|
||||
event_handle = event_ref.ref
|
||||
if event_handle not in self._event_to_family_cache:
|
||||
self._event_to_family_cache[event_handle] = family
|
||||
|
||||
# Try to get father first, then mother, as the person to associate with the event
|
||||
person_for_family_event = None
|
||||
father_handle = family.get_father_handle()
|
||||
if father_handle:
|
||||
try:
|
||||
person_for_family_event = self.dbstate.db.get_person_from_handle(father_handle)
|
||||
except (AttributeError, KeyError):
|
||||
pass
|
||||
|
||||
if not person_for_family_event:
|
||||
mother_handle = family.get_mother_handle()
|
||||
if mother_handle:
|
||||
try:
|
||||
person_for_family_event = self.dbstate.db.get_person_from_handle(mother_handle)
|
||||
except (AttributeError, KeyError):
|
||||
pass
|
||||
|
||||
# Index family events to the person (if we found one)
|
||||
if person_for_family_event:
|
||||
self._index_event_refs(family_event_refs, person_for_family_event)
|
||||
|
||||
except (AttributeError, KeyError):
|
||||
continue
|
||||
except (AttributeError, KeyError) as e:
|
||||
logger.warning(f"Error building event-to-person index from database: {e}", exc_info=True)
|
||||
|
||||
@ -2785,6 +2831,7 @@ class MyTimelineView(NavigationView):
|
||||
def _get_event_label_text(self, event: 'Event', person: Optional['Person'], event_type: EventType) -> str:
|
||||
"""
|
||||
Generate label text for an event. Centralized logic.
|
||||
For marriage events, tries to show both spouses.
|
||||
|
||||
Args:
|
||||
event: The event object.
|
||||
@ -2794,14 +2841,120 @@ class MyTimelineView(NavigationView):
|
||||
Returns:
|
||||
str: The formatted label text.
|
||||
"""
|
||||
date_str = get_date(event)
|
||||
event_type_str = str(event_type)
|
||||
if not event:
|
||||
return "Event"
|
||||
|
||||
# Always get date and event type string first
|
||||
date_str = get_date(event) or ""
|
||||
|
||||
# Get event type directly from event object to ensure accuracy
|
||||
actual_event_type = event.get_type() if event else event_type
|
||||
event_type_str = str(actual_event_type) if actual_event_type else (str(event_type) if event_type else "")
|
||||
|
||||
# Check if this is a marriage event by comparing the actual event type
|
||||
# Try to get the integer value for comparison
|
||||
is_marriage = False
|
||||
try:
|
||||
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
|
||||
|
||||
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 event_type_str or "MARRIAGE" in event_type_str:
|
||||
is_marriage = True
|
||||
except (ValueError, TypeError, AttributeError):
|
||||
# Fallback: string check
|
||||
if "Marriage" in event_type_str or "MARRIAGE" in event_type_str:
|
||||
is_marriage = True
|
||||
|
||||
# For marriage events, try to get both spouses from the family
|
||||
if is_marriage and self.dbstate.is_open():
|
||||
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:
|
||||
spouse_names = []
|
||||
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:
|
||||
spouse_names.append(name_displayer.display(father))
|
||||
except (AttributeError, KeyError):
|
||||
pass
|
||||
|
||||
if mother_handle:
|
||||
try:
|
||||
mother = self.dbstate.db.get_person_from_handle(mother_handle)
|
||||
if mother:
|
||||
spouse_names.append(name_displayer.display(mother))
|
||||
except (AttributeError, KeyError):
|
||||
pass
|
||||
|
||||
# Build label with date, event type, and spouses
|
||||
parts = []
|
||||
if date_str:
|
||||
parts.append(date_str)
|
||||
if event_type_str:
|
||||
parts.append(event_type_str)
|
||||
if len(spouse_names) == 2:
|
||||
parts.append(f"{spouse_names[0]} & {spouse_names[1]}")
|
||||
elif len(spouse_names) == 1:
|
||||
parts.append(spouse_names[0])
|
||||
elif person:
|
||||
# Fallback to associated person if no spouses found
|
||||
person_name = name_displayer.display(person)
|
||||
if person_name:
|
||||
parts.append(person_name)
|
||||
|
||||
result = " - ".join(parts) if parts else (f"{date_str} - {event_type_str}" if date_str or event_type_str else "Marriage")
|
||||
if result and result.strip():
|
||||
return result
|
||||
except Exception as e:
|
||||
logger.debug(f"Error getting marriage event label: {e}")
|
||||
|
||||
# Default: show single person or just event type
|
||||
# Always include date and event type
|
||||
parts = []
|
||||
if date_str:
|
||||
parts.append(date_str)
|
||||
if event_type_str:
|
||||
parts.append(event_type_str)
|
||||
|
||||
if person:
|
||||
person_name = name_displayer.display(person)
|
||||
return f"{date_str} - {event_type_str} - {person_name}"
|
||||
else:
|
||||
return f"{date_str} - {event_type_str}"
|
||||
if person_name:
|
||||
parts.append(person_name)
|
||||
|
||||
# Return joined parts, or at least event type if nothing else
|
||||
result = " - ".join(parts) if parts else (event_type_str or "Event")
|
||||
return result if result.strip() else "Event"
|
||||
|
||||
def _calculate_adjusted_positions(self, context: cairo.Context,
|
||||
events_with_y_pos: List[TimelineEvent],
|
||||
@ -3790,7 +3943,26 @@ class MyTimelineView(NavigationView):
|
||||
# Build label text using centralized method
|
||||
label_text = self._get_event_label_text(event, person, event_type)
|
||||
|
||||
layout.set_markup(label_text, -1)
|
||||
# Ensure we have a valid label text (should never be empty after our fixes, but just in case)
|
||||
if not label_text or not label_text.strip():
|
||||
# Fallback: create basic label
|
||||
date_str = get_date(event) or ""
|
||||
event_type_str = str(event.get_type()) if event else (str(event_type) if event_type else "Event")
|
||||
if person:
|
||||
person_name = name_displayer.display(person)
|
||||
if date_str:
|
||||
label_text = f"{date_str} - {event_type_str} - {person_name}"
|
||||
else:
|
||||
label_text = f"{event_type_str} - {person_name}"
|
||||
else:
|
||||
if date_str:
|
||||
label_text = f"{date_str} - {event_type_str}"
|
||||
else:
|
||||
label_text = event_type_str
|
||||
|
||||
# Escape any markup characters in the text (set_markup expects markup, but we want plain text)
|
||||
# Actually, let's use set_text instead of set_markup for plain text
|
||||
layout.set_text(label_text, -1)
|
||||
layout.set_width(-1) # No width limit
|
||||
|
||||
# Draw background for hovered events
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user