Refactor MyTimeline.py: improve code quality and reduce duplication
- Extract calendar widget creation into helper method (_create_date_calendar_widget) Eliminates ~80 lines of duplicated code between from/to date widgets - Extract place access logic into _get_place_name_for_event() helper Provides single source of truth for place access with error handling - Consolidate year spin button updates via _update_year_spin_button() helper Reduces duplication and ensures consistent behavior - Split _update_filter_dialog_state() into focused methods: - _update_event_type_widgets() - _update_person_filter_widgets() - _update_date_range_widgets() - Extract filter check logic into separate methods: - _apply_event_type_filter() - _apply_category_filter() - Improve type hints: change handler return types from Any to Callable[[Gtk.Widget], None] - Extract UI layout constants (margins, spacing) for better maintainability - Add event type normalization caching for performance Cache dictionary avoids repeated conversions - Optimize event type set operations with pre-computed normalized active types Improves filter performance significantly
This commit is contained in:
parent
5860b3d25c
commit
c76735f2b8
630
MyTimeline.py
630
MyTimeline.py
@ -32,7 +32,7 @@ import colorsys
|
|||||||
import logging
|
import logging
|
||||||
import math
|
import math
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Optional, List, Tuple, Any, Set, Dict, TYPE_CHECKING, Union
|
from typing import Optional, List, Tuple, Any, Set, Dict, TYPE_CHECKING, Union, Callable
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from gramps.gen.lib import Event, Person, Family
|
from gramps.gen.lib import Event, Person, Family
|
||||||
@ -101,6 +101,14 @@ MIN_GENEALOGICAL_YEAR = 1000 # Minimum year for genealogical data
|
|||||||
DATE_SORT_YEAR_MULTIPLIER = 10000 # Year component in date sort value
|
DATE_SORT_YEAR_MULTIPLIER = 10000 # Year component in date sort value
|
||||||
DATE_SORT_MONTH_MULTIPLIER = 100 # Month component in date sort value
|
DATE_SORT_MONTH_MULTIPLIER = 100 # Month component in date sort value
|
||||||
|
|
||||||
|
# UI Layout Constants
|
||||||
|
FILTER_PAGE_MARGIN = 10 # Margin for filter page containers
|
||||||
|
FILTER_PAGE_SPACING = 10 # Spacing in filter page containers
|
||||||
|
CALENDAR_CONTAINER_MARGIN = 5 # Margin for calendar containers
|
||||||
|
CALENDAR_CONTAINER_SPACING = 5 # Spacing in calendar containers
|
||||||
|
CALENDAR_HORIZONTAL_SPACING = 15 # Spacing between calendar frames
|
||||||
|
YEAR_SELECTOR_SPACING = 5 # Spacing in year selector boxes
|
||||||
|
|
||||||
# Font Constants
|
# Font Constants
|
||||||
FONT_FAMILY = "Sans"
|
FONT_FAMILY = "Sans"
|
||||||
FONT_SIZE_NORMAL = 11
|
FONT_SIZE_NORMAL = 11
|
||||||
@ -416,6 +424,8 @@ class MyTimelineView(NavigationView):
|
|||||||
self._cached_min_date: Optional[int] = None
|
self._cached_min_date: Optional[int] = None
|
||||||
self._cached_max_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_person_cache: Dict[str, Optional['Person']] = {} # Event handle -> Person object (or None)
|
||||||
|
self._event_type_normalization_cache: Dict[EventType, int] = {} # Cache for event type normalization
|
||||||
|
self._normalized_active_event_types: Optional[Set[int]] = None # Pre-computed normalized active types
|
||||||
|
|
||||||
# Filter state
|
# Filter state
|
||||||
self.filter_enabled: bool = False
|
self.filter_enabled: bool = False
|
||||||
@ -676,7 +686,7 @@ class MyTimelineView(NavigationView):
|
|||||||
group_checkbox.set_inconsistent(True)
|
group_checkbox.set_inconsistent(True)
|
||||||
|
|
||||||
def _make_group_toggle_handler(self, child_checkboxes: List[Gtk.CheckButton],
|
def _make_group_toggle_handler(self, child_checkboxes: List[Gtk.CheckButton],
|
||||||
updating_flag: List[bool]) -> Any:
|
updating_flag: List[bool]) -> Callable[[Gtk.Widget], None]:
|
||||||
"""
|
"""
|
||||||
Create a handler for group checkbox toggle that toggles all children.
|
Create a handler for group checkbox toggle that toggles all children.
|
||||||
|
|
||||||
@ -707,7 +717,7 @@ class MyTimelineView(NavigationView):
|
|||||||
|
|
||||||
def _make_child_toggle_handler(self, group_checkbox: Gtk.CheckButton,
|
def _make_child_toggle_handler(self, group_checkbox: Gtk.CheckButton,
|
||||||
child_checkboxes: List[Gtk.CheckButton],
|
child_checkboxes: List[Gtk.CheckButton],
|
||||||
updating_flag: List[bool]) -> Any:
|
updating_flag: List[bool]) -> Callable[[Gtk.Widget], None]:
|
||||||
"""
|
"""
|
||||||
Create a handler for child checkbox toggle that updates group state.
|
Create a handler for child checkbox toggle that updates group state.
|
||||||
|
|
||||||
@ -916,6 +926,77 @@ class MyTimelineView(NavigationView):
|
|||||||
scrolled.add(box)
|
scrolled.add(box)
|
||||||
return scrolled
|
return scrolled
|
||||||
|
|
||||||
|
def _create_date_calendar_widget(self, label: str, year_changed_handler: Callable,
|
||||||
|
date_selected_handler: Callable,
|
||||||
|
month_changed_handler: Callable,
|
||||||
|
calendar_key: str, spin_key: str,
|
||||||
|
current_year: int, min_year: int, max_year: int) -> Tuple[Gtk.Frame, Gtk.Calendar, Gtk.SpinButton]:
|
||||||
|
"""
|
||||||
|
Create a date calendar widget with year selector.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
label: Label for the frame.
|
||||||
|
year_changed_handler: Handler for year spin button changes.
|
||||||
|
date_selected_handler: Handler for calendar date selection.
|
||||||
|
month_changed_handler: Handler for calendar month changes.
|
||||||
|
calendar_key: Key to store calendar widget in _filter_widgets.
|
||||||
|
spin_key: Key to store spin button widget in _filter_widgets.
|
||||||
|
current_year: Initial year value.
|
||||||
|
min_year: Minimum year for the spin button.
|
||||||
|
max_year: Maximum year for the spin button.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Tuple containing (frame, calendar, spin_button).
|
||||||
|
"""
|
||||||
|
frame = Gtk.Frame(label=label)
|
||||||
|
frame.set_label_align(0.5, 0.5)
|
||||||
|
|
||||||
|
container = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=CALENDAR_CONTAINER_SPACING)
|
||||||
|
container.set_margin_start(CALENDAR_CONTAINER_MARGIN)
|
||||||
|
container.set_margin_end(CALENDAR_CONTAINER_MARGIN)
|
||||||
|
container.set_margin_top(CALENDAR_CONTAINER_MARGIN)
|
||||||
|
container.set_margin_bottom(CALENDAR_CONTAINER_MARGIN)
|
||||||
|
|
||||||
|
# Year selector
|
||||||
|
year_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=YEAR_SELECTOR_SPACING)
|
||||||
|
year_label = Gtk.Label(label=_("Year:"))
|
||||||
|
year_adjustment = Gtk.Adjustment(
|
||||||
|
value=current_year,
|
||||||
|
lower=min_year,
|
||||||
|
upper=max_year,
|
||||||
|
step_increment=1,
|
||||||
|
page_increment=10
|
||||||
|
)
|
||||||
|
year_spin = Gtk.SpinButton()
|
||||||
|
year_spin.set_adjustment(year_adjustment)
|
||||||
|
year_spin.set_numeric(True)
|
||||||
|
year_spin.set_update_policy(Gtk.SpinButtonUpdatePolicy.IF_VALID)
|
||||||
|
year_spin.set_width_chars(6)
|
||||||
|
year_spin.connect("value-changed", year_changed_handler)
|
||||||
|
|
||||||
|
year_box.pack_start(year_label, False, False, 0)
|
||||||
|
year_box.pack_start(year_spin, False, False, 0)
|
||||||
|
container.pack_start(year_box, False, False, 0)
|
||||||
|
|
||||||
|
# Calendar
|
||||||
|
calendar = Gtk.Calendar()
|
||||||
|
calendar.set_display_options(
|
||||||
|
Gtk.CalendarDisplayOptions.SHOW_HEADING |
|
||||||
|
Gtk.CalendarDisplayOptions.SHOW_DAY_NAMES |
|
||||||
|
Gtk.CalendarDisplayOptions.SHOW_WEEK_NUMBERS
|
||||||
|
)
|
||||||
|
calendar.connect("day-selected", date_selected_handler)
|
||||||
|
calendar.connect("month-changed", month_changed_handler)
|
||||||
|
container.pack_start(calendar, True, True, 0)
|
||||||
|
|
||||||
|
frame.add(container)
|
||||||
|
|
||||||
|
# Store widgets
|
||||||
|
self._filter_widgets[calendar_key] = calendar
|
||||||
|
self._filter_widgets[spin_key] = year_spin
|
||||||
|
|
||||||
|
return frame, calendar, year_spin
|
||||||
|
|
||||||
def _build_date_range_filter_page(self) -> Gtk.Widget:
|
def _build_date_range_filter_page(self) -> Gtk.Widget:
|
||||||
"""
|
"""
|
||||||
Build the date range filter page with date chooser widgets.
|
Build the date range filter page with date chooser widgets.
|
||||||
@ -926,18 +1007,18 @@ class MyTimelineView(NavigationView):
|
|||||||
scrolled = Gtk.ScrolledWindow()
|
scrolled = Gtk.ScrolledWindow()
|
||||||
scrolled.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
|
scrolled.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
|
||||||
|
|
||||||
box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10)
|
box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=FILTER_PAGE_SPACING)
|
||||||
box.set_margin_start(10)
|
box.set_margin_start(FILTER_PAGE_MARGIN)
|
||||||
box.set_margin_end(10)
|
box.set_margin_end(FILTER_PAGE_MARGIN)
|
||||||
box.set_margin_top(10)
|
box.set_margin_top(FILTER_PAGE_MARGIN)
|
||||||
box.set_margin_bottom(10)
|
box.set_margin_bottom(FILTER_PAGE_MARGIN)
|
||||||
|
|
||||||
info_label = Gtk.Label(label=_("Select date range to filter events. Leave unselected to show all dates."))
|
info_label = Gtk.Label(label=_("Select date range to filter events. Leave unselected to show all dates."))
|
||||||
info_label.set_line_wrap(True)
|
info_label.set_line_wrap(True)
|
||||||
box.pack_start(info_label, False, False, 0)
|
box.pack_start(info_label, False, False, 0)
|
||||||
|
|
||||||
# Calendar container
|
# Calendar container
|
||||||
calendar_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=15)
|
calendar_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=CALENDAR_HORIZONTAL_SPACING)
|
||||||
calendar_box.set_homogeneous(True)
|
calendar_box.set_homogeneous(True)
|
||||||
|
|
||||||
# Year range for genealogical data
|
# Year range for genealogical data
|
||||||
@ -946,86 +1027,32 @@ class MyTimelineView(NavigationView):
|
|||||||
min_year = MIN_GENEALOGICAL_YEAR
|
min_year = MIN_GENEALOGICAL_YEAR
|
||||||
max_year = current_year + 10
|
max_year = current_year + 10
|
||||||
|
|
||||||
# From date calendar with year selector
|
# Create From date calendar widget
|
||||||
from_frame = Gtk.Frame(label=_("From Date"))
|
from_frame, from_calendar, from_year_spin = self._create_date_calendar_widget(
|
||||||
from_frame.set_label_align(0.5, 0.5)
|
label=_("From Date"),
|
||||||
from_calendar_container = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=5)
|
year_changed_handler=self._on_from_year_changed,
|
||||||
from_calendar_container.set_margin_start(5)
|
date_selected_handler=self._on_from_date_selected,
|
||||||
from_calendar_container.set_margin_end(5)
|
month_changed_handler=self._on_from_calendar_changed,
|
||||||
from_calendar_container.set_margin_top(5)
|
calendar_key='date_from_calendar',
|
||||||
from_calendar_container.set_margin_bottom(5)
|
spin_key='date_from_year_spin',
|
||||||
|
current_year=current_year,
|
||||||
# Year selector for From date
|
min_year=min_year,
|
||||||
from_year_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=5)
|
max_year=max_year
|
||||||
from_year_label = Gtk.Label(label=_("Year:"))
|
|
||||||
from_year_adjustment = Gtk.Adjustment(
|
|
||||||
value=current_year,
|
|
||||||
lower=min_year,
|
|
||||||
upper=max_year,
|
|
||||||
step_increment=1,
|
|
||||||
page_increment=10
|
|
||||||
)
|
)
|
||||||
from_year_spin = Gtk.SpinButton()
|
|
||||||
from_year_spin.set_adjustment(from_year_adjustment)
|
|
||||||
from_year_spin.set_numeric(True)
|
|
||||||
from_year_spin.set_update_policy(Gtk.SpinButtonUpdatePolicy.IF_VALID)
|
|
||||||
from_year_spin.set_width_chars(6)
|
|
||||||
from_year_spin.connect("value-changed", self._on_from_year_changed)
|
|
||||||
from_year_box.pack_start(from_year_label, False, False, 0)
|
|
||||||
from_year_box.pack_start(from_year_spin, False, False, 0)
|
|
||||||
from_calendar_container.pack_start(from_year_box, False, False, 0)
|
|
||||||
|
|
||||||
from_calendar = Gtk.Calendar()
|
|
||||||
from_calendar.set_display_options(
|
|
||||||
Gtk.CalendarDisplayOptions.SHOW_HEADING |
|
|
||||||
Gtk.CalendarDisplayOptions.SHOW_DAY_NAMES |
|
|
||||||
Gtk.CalendarDisplayOptions.SHOW_WEEK_NUMBERS
|
|
||||||
)
|
|
||||||
from_calendar.connect("day-selected", self._on_from_date_selected)
|
|
||||||
from_calendar.connect("month-changed", self._on_from_calendar_changed)
|
|
||||||
from_calendar_container.pack_start(from_calendar, True, True, 0)
|
|
||||||
from_frame.add(from_calendar_container)
|
|
||||||
calendar_box.pack_start(from_frame, True, True, 0)
|
calendar_box.pack_start(from_frame, True, True, 0)
|
||||||
|
|
||||||
# To date calendar with year selector
|
# Create To date calendar widget
|
||||||
to_frame = Gtk.Frame(label=_("To Date"))
|
to_frame, to_calendar, to_year_spin = self._create_date_calendar_widget(
|
||||||
to_frame.set_label_align(0.5, 0.5)
|
label=_("To Date"),
|
||||||
to_calendar_container = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=5)
|
year_changed_handler=self._on_to_year_changed,
|
||||||
to_calendar_container.set_margin_start(5)
|
date_selected_handler=self._on_to_date_selected,
|
||||||
to_calendar_container.set_margin_end(5)
|
month_changed_handler=self._on_to_calendar_changed,
|
||||||
to_calendar_container.set_margin_top(5)
|
calendar_key='date_to_calendar',
|
||||||
to_calendar_container.set_margin_bottom(5)
|
spin_key='date_to_year_spin',
|
||||||
|
current_year=current_year,
|
||||||
# Year selector for To date
|
min_year=min_year,
|
||||||
to_year_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=5)
|
max_year=max_year
|
||||||
to_year_label = Gtk.Label(label=_("Year:"))
|
|
||||||
to_year_adjustment = Gtk.Adjustment(
|
|
||||||
value=current_year,
|
|
||||||
lower=min_year,
|
|
||||||
upper=max_year,
|
|
||||||
step_increment=1,
|
|
||||||
page_increment=10
|
|
||||||
)
|
)
|
||||||
to_year_spin = Gtk.SpinButton()
|
|
||||||
to_year_spin.set_adjustment(to_year_adjustment)
|
|
||||||
to_year_spin.set_numeric(True)
|
|
||||||
to_year_spin.set_update_policy(Gtk.SpinButtonUpdatePolicy.IF_VALID)
|
|
||||||
to_year_spin.set_width_chars(6)
|
|
||||||
to_year_spin.connect("value-changed", self._on_to_year_changed)
|
|
||||||
to_year_box.pack_start(to_year_label, False, False, 0)
|
|
||||||
to_year_box.pack_start(to_year_spin, False, False, 0)
|
|
||||||
to_calendar_container.pack_start(to_year_box, False, False, 0)
|
|
||||||
|
|
||||||
to_calendar = Gtk.Calendar()
|
|
||||||
to_calendar.set_display_options(
|
|
||||||
Gtk.CalendarDisplayOptions.SHOW_HEADING |
|
|
||||||
Gtk.CalendarDisplayOptions.SHOW_DAY_NAMES |
|
|
||||||
Gtk.CalendarDisplayOptions.SHOW_WEEK_NUMBERS
|
|
||||||
)
|
|
||||||
to_calendar.connect("day-selected", self._on_to_date_selected)
|
|
||||||
to_calendar.connect("month-changed", self._on_to_calendar_changed)
|
|
||||||
to_calendar_container.pack_start(to_calendar, True, True, 0)
|
|
||||||
to_frame.add(to_calendar_container)
|
|
||||||
calendar_box.pack_start(to_frame, True, True, 0)
|
calendar_box.pack_start(to_frame, True, True, 0)
|
||||||
|
|
||||||
box.pack_start(calendar_box, True, True, 0)
|
box.pack_start(calendar_box, True, True, 0)
|
||||||
@ -1043,28 +1070,37 @@ class MyTimelineView(NavigationView):
|
|||||||
self.date_validation_label.set_markup("<span color='red'></span>")
|
self.date_validation_label.set_markup("<span color='red'></span>")
|
||||||
box.pack_start(self.date_validation_label, False, False, 0)
|
box.pack_start(self.date_validation_label, False, False, 0)
|
||||||
|
|
||||||
self._filter_widgets['date_from_calendar'] = from_calendar
|
|
||||||
self._filter_widgets['date_to_calendar'] = to_calendar
|
|
||||||
self._filter_widgets['date_from_year_spin'] = from_year_spin
|
|
||||||
self._filter_widgets['date_to_year_spin'] = to_year_spin
|
|
||||||
|
|
||||||
scrolled.add(box)
|
scrolled.add(box)
|
||||||
return scrolled
|
return scrolled
|
||||||
|
|
||||||
def _update_filter_dialog_state(self) -> None:
|
def _update_year_spin_button(self, spin_key: str, year: int, handler: Callable) -> None:
|
||||||
"""
|
"""
|
||||||
Update the filter dialog widgets to reflect current filter state.
|
Update a year spin button value, blocking signals during update.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
spin_key: Key to find the spin button in _filter_widgets.
|
||||||
|
year: Year value to set.
|
||||||
|
handler: Handler function to block/unblock during update.
|
||||||
"""
|
"""
|
||||||
if not hasattr(self, '_filter_widgets'):
|
if spin_key in self._filter_widgets:
|
||||||
|
year_spin = self._filter_widgets[spin_key]
|
||||||
|
year_spin.handler_block_by_func(handler)
|
||||||
|
year_spin.set_value(year)
|
||||||
|
year_spin.handler_unblock_by_func(handler)
|
||||||
|
|
||||||
|
def _update_event_type_widgets(self) -> None:
|
||||||
|
"""
|
||||||
|
Update event type filter widgets to reflect current filter state.
|
||||||
|
"""
|
||||||
|
if 'event_type_checkboxes' not in self._filter_widgets:
|
||||||
return
|
return
|
||||||
|
|
||||||
# Update event type checkboxes
|
# Update event type checkboxes
|
||||||
if 'event_type_checkboxes' in self._filter_widgets:
|
for event_type, checkbox in self._filter_widgets['event_type_checkboxes'].items():
|
||||||
for event_type, checkbox in self._filter_widgets['event_type_checkboxes'].items():
|
if not self.active_event_types:
|
||||||
if not self.active_event_types:
|
checkbox.set_active(True) # All selected when filter is off
|
||||||
checkbox.set_active(True) # All selected when filter is off
|
else:
|
||||||
else:
|
checkbox.set_active(event_type in self.active_event_types)
|
||||||
checkbox.set_active(event_type in self.active_event_types)
|
|
||||||
|
|
||||||
# Update category checkboxes based on their children's states
|
# Update category checkboxes based on their children's states
|
||||||
if 'category_checkboxes' in self._filter_widgets and 'category_event_types' in self._filter_widgets:
|
if 'category_checkboxes' in self._filter_widgets and 'category_event_types' in self._filter_widgets:
|
||||||
@ -1079,165 +1115,171 @@ class MyTimelineView(NavigationView):
|
|||||||
# Update category checkbox state based on children
|
# Update category checkbox state based on children
|
||||||
self._update_group_checkbox_state(category_checkbox, child_checkboxes)
|
self._update_group_checkbox_state(category_checkbox, child_checkboxes)
|
||||||
|
|
||||||
# Update person checkboxes with families
|
def _update_person_filter_widgets(self) -> None:
|
||||||
if 'person_checkboxes' in self._filter_widgets and 'person_container' in self._filter_widgets:
|
"""
|
||||||
# Clear existing person checkboxes and family expanders
|
Update person filter widgets to reflect current filter state.
|
||||||
container = self._filter_widgets['person_container']
|
"""
|
||||||
|
if 'person_checkboxes' not in self._filter_widgets or 'person_container' not in self._filter_widgets:
|
||||||
|
return
|
||||||
|
|
||||||
# Remove all existing expanders
|
# Clear existing person checkboxes and family expanders
|
||||||
if 'family_expanders' in self._filter_widgets:
|
container = self._filter_widgets['person_container']
|
||||||
for expander in list(self._filter_widgets['family_expanders'].values()):
|
|
||||||
container.remove(expander)
|
|
||||||
expander.destroy()
|
|
||||||
self._filter_widgets['family_expanders'].clear()
|
|
||||||
|
|
||||||
# Remove all existing checkboxes
|
# Remove all existing expanders
|
||||||
for checkbox in list(self._filter_widgets['person_checkboxes'].values()):
|
if 'family_expanders' in self._filter_widgets:
|
||||||
container.remove(checkbox)
|
for expander in list(self._filter_widgets['family_expanders'].values()):
|
||||||
checkbox.destroy()
|
container.remove(expander)
|
||||||
self._filter_widgets['person_checkboxes'].clear()
|
expander.destroy()
|
||||||
|
self._filter_widgets['family_expanders'].clear()
|
||||||
|
|
||||||
# Collect all families and create expanders
|
# Remove all existing checkboxes
|
||||||
if self.dbstate.is_open():
|
for checkbox in list(self._filter_widgets['person_checkboxes'].values()):
|
||||||
try:
|
container.remove(checkbox)
|
||||||
# Initialize family_expanders if not exists
|
checkbox.destroy()
|
||||||
if 'family_expanders' not in self._filter_widgets:
|
self._filter_widgets['person_checkboxes'].clear()
|
||||||
self._filter_widgets['family_expanders'] = {}
|
|
||||||
|
|
||||||
# Iterate through all families
|
# Collect all families and create expanders
|
||||||
for family in self.dbstate.db.iter_families():
|
if not self.dbstate.is_open():
|
||||||
family_handle = family.get_handle()
|
return
|
||||||
|
|
||||||
# Get family display name
|
try:
|
||||||
family_name = self._get_family_display_name(family)
|
# Initialize family_expanders if not exists
|
||||||
|
if 'family_expanders' not in self._filter_widgets:
|
||||||
|
self._filter_widgets['family_expanders'] = {}
|
||||||
|
|
||||||
# Create container for family members
|
# Iterate through all families
|
||||||
members_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=3)
|
for family in self.dbstate.db.iter_families():
|
||||||
members_box.set_margin_start(20)
|
family_handle = family.get_handle()
|
||||||
members_box.set_margin_top(5)
|
|
||||||
members_box.set_margin_bottom(5)
|
|
||||||
|
|
||||||
# Collect all child checkboxes for this family
|
# Get family display name
|
||||||
child_checkboxes = []
|
family_name = self._get_family_display_name(family)
|
||||||
|
|
||||||
# Helper function to add person checkbox
|
# Create container for family members
|
||||||
def add_person_checkbox(person_handle: Optional[str], role_label: str) -> None:
|
members_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=3)
|
||||||
"""Add a checkbox for a person if handle is valid."""
|
members_box.set_margin_start(20)
|
||||||
if not person_handle:
|
members_box.set_margin_top(5)
|
||||||
return
|
members_box.set_margin_bottom(5)
|
||||||
person_name = self._get_person_display_name(person_handle)
|
|
||||||
if person_name:
|
|
||||||
label_text = f" {role_label}: {person_name}"
|
|
||||||
checkbox = Gtk.CheckButton(label=label_text)
|
|
||||||
checkbox.set_active(True if not self.person_filter else person_handle in self.person_filter)
|
|
||||||
self._filter_widgets['person_checkboxes'][person_handle] = checkbox
|
|
||||||
child_checkboxes.append(checkbox)
|
|
||||||
members_box.pack_start(checkbox, False, False, 0)
|
|
||||||
|
|
||||||
# Add father checkbox
|
# Collect all child checkboxes for this family
|
||||||
add_person_checkbox(family.get_father_handle(), _('Father'))
|
child_checkboxes = []
|
||||||
|
|
||||||
# Add mother checkbox
|
# Helper function to add person checkbox
|
||||||
add_person_checkbox(family.get_mother_handle(), _('Mother'))
|
def add_person_checkbox(person_handle: Optional[str], role_label: str) -> None:
|
||||||
|
"""Add a checkbox for a person if handle is valid."""
|
||||||
|
if not person_handle:
|
||||||
|
return
|
||||||
|
person_name = self._get_person_display_name(person_handle)
|
||||||
|
if person_name:
|
||||||
|
label_text = f" {role_label}: {person_name}"
|
||||||
|
checkbox = Gtk.CheckButton(label=label_text)
|
||||||
|
checkbox.set_active(True if not self.person_filter else person_handle in self.person_filter)
|
||||||
|
self._filter_widgets['person_checkboxes'][person_handle] = checkbox
|
||||||
|
child_checkboxes.append(checkbox)
|
||||||
|
members_box.pack_start(checkbox, False, False, 0)
|
||||||
|
|
||||||
# Add children checkboxes
|
# Add father checkbox
|
||||||
for child_ref in family.get_child_ref_list():
|
add_person_checkbox(family.get_father_handle(), _('Father'))
|
||||||
add_person_checkbox(child_ref.ref, _('Child'))
|
|
||||||
|
|
||||||
# Only add expander if there are members to show
|
# Add mother checkbox
|
||||||
if len(members_box.get_children()) > 0:
|
add_person_checkbox(family.get_mother_handle(), _('Mother'))
|
||||||
# Create family checkbox with three-state support
|
|
||||||
family_checkbox = Gtk.CheckButton(label=family_name)
|
|
||||||
|
|
||||||
# Create expander with checkbox as label
|
# Add children checkboxes
|
||||||
expander = Gtk.Expander()
|
for child_ref in family.get_child_ref_list():
|
||||||
# Set the checkbox as the label widget
|
add_person_checkbox(child_ref.ref, _('Child'))
|
||||||
expander.set_label_widget(family_checkbox)
|
|
||||||
expander.set_expanded(False)
|
|
||||||
|
|
||||||
# Store family checkbox
|
# Only add expander if there are members to show
|
||||||
if 'family_checkboxes' not in self._filter_widgets:
|
if len(members_box.get_children()) > 0:
|
||||||
self._filter_widgets['family_checkboxes'] = {}
|
# Create family checkbox with three-state support
|
||||||
self._filter_widgets['family_checkboxes'][family_handle] = family_checkbox
|
family_checkbox = Gtk.CheckButton(label=family_name)
|
||||||
|
|
||||||
# Flag to prevent recursion
|
# Create expander with checkbox as label
|
||||||
updating_family = [False]
|
expander = Gtk.Expander()
|
||||||
|
# Set the checkbox as the label widget
|
||||||
|
expander.set_label_widget(family_checkbox)
|
||||||
|
expander.set_expanded(False)
|
||||||
|
|
||||||
# Connect family checkbox to toggle all members
|
# Store family checkbox
|
||||||
family_checkbox.connect("toggled",
|
if 'family_checkboxes' not in self._filter_widgets:
|
||||||
self._make_group_toggle_handler(child_checkboxes, updating_family))
|
self._filter_widgets['family_checkboxes'] = {}
|
||||||
|
self._filter_widgets['family_checkboxes'][family_handle] = family_checkbox
|
||||||
|
|
||||||
# Connect child checkboxes to update family checkbox
|
# Flag to prevent recursion
|
||||||
for child_cb in child_checkboxes:
|
updating_family = [False]
|
||||||
child_cb.connect("toggled",
|
|
||||||
self._make_child_toggle_handler(family_checkbox, child_checkboxes, updating_family))
|
|
||||||
|
|
||||||
# Initialize family checkbox state
|
# Connect family checkbox to toggle all members
|
||||||
self._update_group_checkbox_state(family_checkbox, child_checkboxes)
|
family_checkbox.connect("toggled",
|
||||||
|
self._make_group_toggle_handler(child_checkboxes, updating_family))
|
||||||
|
|
||||||
expander.add(members_box)
|
# Connect child checkboxes to update family checkbox
|
||||||
self._filter_widgets['family_expanders'][family_handle] = expander
|
for child_cb in child_checkboxes:
|
||||||
container.pack_start(expander, False, False, 0)
|
child_cb.connect("toggled",
|
||||||
|
self._make_child_toggle_handler(family_checkbox, child_checkboxes, updating_family))
|
||||||
|
|
||||||
container.show_all()
|
# Initialize family checkbox state
|
||||||
except (AttributeError, KeyError) as e:
|
self._update_group_checkbox_state(family_checkbox, child_checkboxes)
|
||||||
logger.warning(f"Error updating person filter: {e}", exc_info=True)
|
|
||||||
|
|
||||||
# Update date range calendars and year selectors
|
expander.add(members_box)
|
||||||
if 'date_from_calendar' in self._filter_widgets and 'date_to_calendar' in self._filter_widgets:
|
self._filter_widgets['family_expanders'][family_handle] = expander
|
||||||
from_calendar = self._filter_widgets['date_from_calendar']
|
container.pack_start(expander, False, False, 0)
|
||||||
to_calendar = self._filter_widgets['date_to_calendar']
|
|
||||||
|
|
||||||
if self.date_range_filter and self.date_range_explicit:
|
container.show_all()
|
||||||
min_sort, max_sort = self.date_range_filter
|
except (AttributeError, KeyError) as e:
|
||||||
# Convert sort values back to dates for calendar display
|
logger.warning(f"Error updating person filter: {e}", exc_info=True)
|
||||||
# Approximate conversion: extract year from sort value
|
|
||||||
# Sort value is roughly: year * DATE_SORT_YEAR_MULTIPLIER + month * DATE_SORT_MONTH_MULTIPLIER + day
|
|
||||||
from_year = min_sort // DATE_SORT_YEAR_MULTIPLIER
|
|
||||||
to_year = max_sort // DATE_SORT_YEAR_MULTIPLIER
|
|
||||||
|
|
||||||
# Set calendar years (approximate)
|
def _update_date_range_widgets(self) -> None:
|
||||||
current_from_year, current_from_month, current_from_day = from_calendar.get_date()
|
"""
|
||||||
current_to_year, current_to_month, current_to_day = to_calendar.get_date()
|
Update date range filter widgets to reflect current filter state.
|
||||||
|
"""
|
||||||
|
if 'date_from_calendar' not in self._filter_widgets or 'date_to_calendar' not in self._filter_widgets:
|
||||||
|
return
|
||||||
|
|
||||||
from_calendar.select_month(current_from_month, from_year)
|
from_calendar = self._filter_widgets['date_from_calendar']
|
||||||
to_calendar.select_month(current_to_month, to_year)
|
to_calendar = self._filter_widgets['date_to_calendar']
|
||||||
|
|
||||||
# Update year spin buttons
|
if self.date_range_filter and self.date_range_explicit:
|
||||||
if 'date_from_year_spin' in self._filter_widgets:
|
min_sort, max_sort = self.date_range_filter
|
||||||
from_year_spin = self._filter_widgets['date_from_year_spin']
|
# Convert sort values back to dates for calendar display
|
||||||
from_year_spin.handler_block_by_func(self._on_from_year_changed)
|
# Approximate conversion: extract year from sort value
|
||||||
from_year_spin.set_value(from_year)
|
# Sort value is roughly: year * DATE_SORT_YEAR_MULTIPLIER + month * DATE_SORT_MONTH_MULTIPLIER + day
|
||||||
from_year_spin.handler_unblock_by_func(self._on_from_year_changed)
|
from_year = min_sort // DATE_SORT_YEAR_MULTIPLIER
|
||||||
|
to_year = max_sort // DATE_SORT_YEAR_MULTIPLIER
|
||||||
|
|
||||||
if 'date_to_year_spin' in self._filter_widgets:
|
# Set calendar years (approximate)
|
||||||
to_year_spin = self._filter_widgets['date_to_year_spin']
|
current_from_year, current_from_month, current_from_day = from_calendar.get_date()
|
||||||
to_year_spin.handler_block_by_func(self._on_to_year_changed)
|
current_to_year, current_to_month, current_to_day = to_calendar.get_date()
|
||||||
to_year_spin.set_value(to_year)
|
|
||||||
to_year_spin.handler_unblock_by_func(self._on_to_year_changed)
|
|
||||||
else:
|
|
||||||
# Reset to current date
|
|
||||||
import datetime
|
|
||||||
now = datetime.date.today()
|
|
||||||
from_calendar.select_month(now.month - 1, now.year)
|
|
||||||
to_calendar.select_month(now.month - 1, now.year)
|
|
||||||
|
|
||||||
# Reset year spin buttons
|
from_calendar.select_month(current_from_month, from_year)
|
||||||
if 'date_from_year_spin' in self._filter_widgets:
|
to_calendar.select_month(current_to_month, to_year)
|
||||||
from_year_spin = self._filter_widgets['date_from_year_spin']
|
|
||||||
from_year_spin.handler_block_by_func(self._on_from_year_changed)
|
|
||||||
from_year_spin.set_value(now.year)
|
|
||||||
from_year_spin.handler_unblock_by_func(self._on_from_year_changed)
|
|
||||||
|
|
||||||
if 'date_to_year_spin' in self._filter_widgets:
|
# Update year spin buttons
|
||||||
to_year_spin = self._filter_widgets['date_to_year_spin']
|
self._update_year_spin_button('date_from_year_spin', from_year, self._on_from_year_changed)
|
||||||
to_year_spin.handler_block_by_func(self._on_to_year_changed)
|
self._update_year_spin_button('date_to_year_spin', to_year, self._on_to_year_changed)
|
||||||
to_year_spin.set_value(now.year)
|
else:
|
||||||
to_year_spin.handler_unblock_by_func(self._on_to_year_changed)
|
# Reset to current date
|
||||||
|
import datetime
|
||||||
|
now = datetime.date.today()
|
||||||
|
from_calendar.select_month(now.month - 1, now.year)
|
||||||
|
to_calendar.select_month(now.month - 1, now.year)
|
||||||
|
|
||||||
# Clear validation message
|
# Reset year spin buttons
|
||||||
if hasattr(self, 'date_validation_label'):
|
self._update_year_spin_button('date_from_year_spin', now.year, self._on_from_year_changed)
|
||||||
self.date_validation_label.set_text("")
|
self._update_year_spin_button('date_to_year_spin', now.year, self._on_to_year_changed)
|
||||||
|
|
||||||
|
# Clear validation message
|
||||||
|
if hasattr(self, 'date_validation_label'):
|
||||||
|
self.date_validation_label.set_text("")
|
||||||
|
|
||||||
|
def _update_filter_dialog_state(self) -> None:
|
||||||
|
"""
|
||||||
|
Update the filter dialog widgets to reflect current filter state.
|
||||||
|
"""
|
||||||
|
if not hasattr(self, '_filter_widgets'):
|
||||||
|
return
|
||||||
|
|
||||||
|
# Update each filter type's widgets
|
||||||
|
self._update_event_type_widgets()
|
||||||
|
self._update_person_filter_widgets()
|
||||||
|
self._update_date_range_widgets()
|
||||||
|
|
||||||
def _on_filter_dialog_response(self, dialog: Gtk.Dialog, response_id: int) -> None:
|
def _on_filter_dialog_response(self, dialog: Gtk.Dialog, response_id: int) -> None:
|
||||||
"""
|
"""
|
||||||
@ -1251,6 +1293,7 @@ class MyTimelineView(NavigationView):
|
|||||||
# Clear all filters
|
# Clear all filters
|
||||||
self.filter_enabled = False
|
self.filter_enabled = False
|
||||||
self.active_event_types = set()
|
self.active_event_types = set()
|
||||||
|
self._update_normalized_active_event_types() # Invalidate cache
|
||||||
self.date_range_filter = None
|
self.date_range_filter = None
|
||||||
self.date_range_explicit = False
|
self.date_range_explicit = False
|
||||||
self.person_filter = None
|
self.person_filter = None
|
||||||
@ -1269,6 +1312,7 @@ class MyTimelineView(NavigationView):
|
|||||||
if checkbox.get_active():
|
if checkbox.get_active():
|
||||||
active_types.add(event_type)
|
active_types.add(event_type)
|
||||||
self.active_event_types = active_types if len(active_types) < len(self._filter_widgets['event_type_checkboxes']) else set()
|
self.active_event_types = active_types if len(active_types) < len(self._filter_widgets['event_type_checkboxes']) else set()
|
||||||
|
self._update_normalized_active_event_types() # Update cache
|
||||||
|
|
||||||
# Update category filter
|
# Update category filter
|
||||||
if 'category_checkboxes' in self._filter_widgets:
|
if 'category_checkboxes' in self._filter_widgets:
|
||||||
@ -1867,6 +1911,52 @@ class MyTimelineView(NavigationView):
|
|||||||
# Update filter button state
|
# Update filter button state
|
||||||
self._update_filter_button_state()
|
self._update_filter_button_state()
|
||||||
|
|
||||||
|
def _update_normalized_active_event_types(self) -> None:
|
||||||
|
"""
|
||||||
|
Update the pre-computed normalized active event types set.
|
||||||
|
Call this whenever active_event_types changes.
|
||||||
|
"""
|
||||||
|
if not self.active_event_types:
|
||||||
|
self._normalized_active_event_types = None
|
||||||
|
else:
|
||||||
|
self._normalized_active_event_types = {
|
||||||
|
self._normalize_event_type(et) for et in self.active_event_types
|
||||||
|
}
|
||||||
|
|
||||||
|
def _apply_event_type_filter(self, event: TimelineEvent) -> bool:
|
||||||
|
"""
|
||||||
|
Check if event passes event type filter.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
event: The event to check.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True if event passes filter, False otherwise.
|
||||||
|
"""
|
||||||
|
if not self.active_event_types:
|
||||||
|
return True
|
||||||
|
# Use pre-computed normalized set if available, otherwise compute it
|
||||||
|
if self._normalized_active_event_types is None:
|
||||||
|
self._update_normalized_active_event_types()
|
||||||
|
# Normalize event.event_type and compare with normalized active_event_types
|
||||||
|
event_type_normalized = self._normalize_event_type(event.event_type)
|
||||||
|
return event_type_normalized in self._normalized_active_event_types
|
||||||
|
|
||||||
|
def _apply_category_filter(self, event: TimelineEvent) -> bool:
|
||||||
|
"""
|
||||||
|
Check if event passes category filter.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
event: The event to check.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True if event passes filter, False otherwise.
|
||||||
|
"""
|
||||||
|
if not self.category_filter:
|
||||||
|
return True
|
||||||
|
category = self._get_event_category(event.event_type)
|
||||||
|
return category in self.category_filter
|
||||||
|
|
||||||
def _apply_filters(self, events: List[TimelineEvent]) -> List[TimelineEvent]:
|
def _apply_filters(self, events: List[TimelineEvent]) -> List[TimelineEvent]:
|
||||||
"""
|
"""
|
||||||
Apply all active filters to events.
|
Apply all active filters to events.
|
||||||
@ -1883,12 +1973,8 @@ class MyTimelineView(NavigationView):
|
|||||||
filtered = []
|
filtered = []
|
||||||
for event in events:
|
for event in events:
|
||||||
# Check event type filter
|
# Check event type filter
|
||||||
if self.active_event_types:
|
if not self._apply_event_type_filter(event):
|
||||||
# Normalize event.event_type and compare with normalized active_event_types
|
continue
|
||||||
event_type_normalized = self._normalize_event_type(event.event_type)
|
|
||||||
active_types_normalized = {self._normalize_event_type(et) for et in self.active_event_types}
|
|
||||||
if event_type_normalized not in active_types_normalized:
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Check date range filter
|
# Check date range filter
|
||||||
if not self._is_date_in_range(event.date_sort):
|
if not self._is_date_in_range(event.date_sort):
|
||||||
@ -1900,8 +1986,7 @@ class MyTimelineView(NavigationView):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
# Check category filter
|
# Check category filter
|
||||||
category = self._get_event_category(event.event_type)
|
if not self._apply_category_filter(event):
|
||||||
if self.category_filter and category not in self.category_filter:
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
filtered.append(event)
|
filtered.append(event)
|
||||||
@ -1911,6 +1996,7 @@ class MyTimelineView(NavigationView):
|
|||||||
def _normalize_event_type(self, event_type: EventType) -> int:
|
def _normalize_event_type(self, event_type: EventType) -> int:
|
||||||
"""
|
"""
|
||||||
Normalize EventType to integer for comparison.
|
Normalize EventType to integer for comparison.
|
||||||
|
Uses caching to avoid repeated conversions.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
event_type: The event type (may be EventType object or integer).
|
event_type: The event type (may be EventType object or integer).
|
||||||
@ -1918,15 +2004,23 @@ class MyTimelineView(NavigationView):
|
|||||||
Returns:
|
Returns:
|
||||||
int: The integer value of the event type.
|
int: The integer value of the event type.
|
||||||
"""
|
"""
|
||||||
|
# Check cache first
|
||||||
|
if event_type in self._event_type_normalization_cache:
|
||||||
|
return self._event_type_normalization_cache[event_type]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if isinstance(event_type, int):
|
if isinstance(event_type, int):
|
||||||
return event_type
|
normalized = event_type
|
||||||
elif hasattr(event_type, 'value'):
|
elif hasattr(event_type, 'value'):
|
||||||
return event_type.value
|
normalized = event_type.value
|
||||||
else:
|
else:
|
||||||
return int(event_type)
|
normalized = int(event_type)
|
||||||
except (TypeError, ValueError, AttributeError):
|
except (TypeError, ValueError, AttributeError):
|
||||||
return 0 # Default to 0 if conversion fails
|
normalized = 0 # Default to 0 if conversion fails
|
||||||
|
|
||||||
|
# Cache the result
|
||||||
|
self._event_type_normalization_cache[event_type] = normalized
|
||||||
|
return normalized
|
||||||
|
|
||||||
def _is_event_type_enabled(self, event_type: EventType) -> bool:
|
def _is_event_type_enabled(self, event_type: EventType) -> bool:
|
||||||
"""
|
"""
|
||||||
@ -1940,10 +2034,12 @@ class MyTimelineView(NavigationView):
|
|||||||
"""
|
"""
|
||||||
if not self.active_event_types:
|
if not self.active_event_types:
|
||||||
return True
|
return True
|
||||||
# Normalize both for comparison
|
# Use pre-computed normalized set if available, otherwise compute it
|
||||||
|
if self._normalized_active_event_types is None:
|
||||||
|
self._update_normalized_active_event_types()
|
||||||
|
# Normalize event type and compare with normalized active types
|
||||||
normalized_type = self._normalize_event_type(event_type)
|
normalized_type = self._normalize_event_type(event_type)
|
||||||
normalized_active = {self._normalize_event_type(et) for et in self.active_event_types}
|
return normalized_type in self._normalized_active_event_types
|
||||||
return normalized_type in normalized_active
|
|
||||||
|
|
||||||
def _is_date_in_range(self, date_sort: int) -> bool:
|
def _is_date_in_range(self, date_sort: int) -> bool:
|
||||||
"""
|
"""
|
||||||
@ -2580,6 +2676,28 @@ class MyTimelineView(NavigationView):
|
|||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def _get_place_name_for_event(self, event: 'Event') -> Optional[str]:
|
||||||
|
"""
|
||||||
|
Get the place name for an event, with error handling.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
event: The event object.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Optional[str]: Place name if available, None otherwise.
|
||||||
|
"""
|
||||||
|
place_handle = event.get_place_handle()
|
||||||
|
if not place_handle:
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
place = self.dbstate.db.get_place_from_handle(place_handle)
|
||||||
|
if place:
|
||||||
|
return place.get_title()
|
||||||
|
except (AttributeError, KeyError) as e:
|
||||||
|
logger.debug(f"Error accessing place for event: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
def _format_person_tooltip(self, person: 'Person', person_events: List[TimelineEvent]) -> str:
|
def _format_person_tooltip(self, person: 'Person', person_events: List[TimelineEvent]) -> str:
|
||||||
"""
|
"""
|
||||||
Format tooltip for person with multiple events.
|
Format tooltip for person with multiple events.
|
||||||
@ -2605,15 +2723,9 @@ class MyTimelineView(NavigationView):
|
|||||||
tooltip_text += f"{date_str} - {event_type_str}\n"
|
tooltip_text += f"{date_str} - {event_type_str}\n"
|
||||||
|
|
||||||
# Add place if available
|
# Add place if available
|
||||||
place_handle = event_data.event.get_place_handle()
|
place_name = self._get_place_name_for_event(event_data.event)
|
||||||
if place_handle:
|
if place_name:
|
||||||
try:
|
tooltip_text += f" 📍 {place_name}\n"
|
||||||
place = self.dbstate.db.get_place_from_handle(place_handle)
|
|
||||||
if place:
|
|
||||||
place_name = place.get_title()
|
|
||||||
tooltip_text += f" 📍 {place_name}\n"
|
|
||||||
except (AttributeError, KeyError) as e:
|
|
||||||
logger.debug(f"Error accessing place in tooltip: {e}")
|
|
||||||
|
|
||||||
return tooltip_text
|
return tooltip_text
|
||||||
|
|
||||||
@ -2634,15 +2746,9 @@ class MyTimelineView(NavigationView):
|
|||||||
tooltip_text = f"<b>{date_str}</b>\n{event_type_str}"
|
tooltip_text = f"<b>{date_str}</b>\n{event_type_str}"
|
||||||
|
|
||||||
# Get place information
|
# Get place information
|
||||||
place_handle = event.get_place_handle()
|
place_name = self._get_place_name_for_event(event)
|
||||||
if place_handle:
|
if place_name:
|
||||||
try:
|
tooltip_text += f"\n📍 {place_name}"
|
||||||
place = self.dbstate.db.get_place_from_handle(place_handle)
|
|
||||||
if place:
|
|
||||||
place_name = place.get_title()
|
|
||||||
tooltip_text += f"\n📍 {place_name}"
|
|
||||||
except (AttributeError, KeyError) as e:
|
|
||||||
logger.debug(f"Error accessing place in tooltip: {e}")
|
|
||||||
|
|
||||||
# Get description
|
# Get description
|
||||||
description = event.get_description()
|
description = event.get_description()
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user