Add year selector controls to date range chooser
- Add Gtk.SpinButton widgets above each calendar for quick year selection - Implement bidirectional synchronization between year selectors and calendars - Set year range from 1000 to current year + 10 for genealogical data - Update clear date range and filter state management to handle year selectors - Improve UX by allowing direct year input instead of incrementing one year at a time
This commit is contained in:
parent
9976a95b97
commit
2874fe35d1
476
MyTimeline.py
476
MyTimeline.py
@ -393,6 +393,7 @@ class MyTimelineView(NavigationView):
|
|||||||
self.filter_enabled: bool = False
|
self.filter_enabled: bool = False
|
||||||
self.active_event_types: Set[EventType] = set() # Empty = all enabled
|
self.active_event_types: Set[EventType] = set() # Empty = all enabled
|
||||||
self.date_range_filter: Optional[Tuple[int, int]] = None # (min_date, max_date) in sort values
|
self.date_range_filter: Optional[Tuple[int, int]] = None # (min_date, max_date) in sort values
|
||||||
|
self.date_range_explicit: bool = False # Track if date range was explicitly set
|
||||||
self.person_filter: Optional[Set[str]] = None # Set of person handles to include, None = all
|
self.person_filter: Optional[Set[str]] = None # Set of person handles to include, None = all
|
||||||
self.category_filter: Optional[Set[str]] = None # Set of event categories to include, None = all
|
self.category_filter: Optional[Set[str]] = None # Set of event categories to include, None = all
|
||||||
self.all_events: List[TimelineEvent] = [] # Store all events before filtering
|
self.all_events: List[TimelineEvent] = [] # Store all events before filtering
|
||||||
@ -772,45 +773,138 @@ class MyTimelineView(NavigationView):
|
|||||||
|
|
||||||
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 entry fields.
|
Build the date range filter page with date chooser widgets.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Gtk.Widget: The date range filter page.
|
Gtk.Widget: The date range filter page.
|
||||||
"""
|
"""
|
||||||
|
scrolled = Gtk.ScrolledWindow()
|
||||||
|
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=10)
|
||||||
box.set_margin_start(10)
|
box.set_margin_start(10)
|
||||||
box.set_margin_end(10)
|
box.set_margin_end(10)
|
||||||
box.set_margin_top(10)
|
box.set_margin_top(10)
|
||||||
box.set_margin_bottom(10)
|
box.set_margin_bottom(10)
|
||||||
|
|
||||||
info_label = Gtk.Label(label=_("Enter date range to filter events. Leave empty 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)
|
||||||
|
|
||||||
# From date
|
# Calendar container
|
||||||
from_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=5)
|
calendar_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=15)
|
||||||
from_label = Gtk.Label(label=_("From:"))
|
calendar_box.set_homogeneous(True)
|
||||||
from_label.set_size_request(80, -1)
|
|
||||||
from_entry = Gtk.Entry()
|
|
||||||
from_entry.set_placeholder_text(_("YYYY-MM-DD or YYYY"))
|
|
||||||
from_box.pack_start(from_label, False, False, 0)
|
|
||||||
from_box.pack_start(from_entry, True, True, 0)
|
|
||||||
box.pack_start(from_box, False, False, 0)
|
|
||||||
|
|
||||||
# To date
|
# Year range for genealogical data (1000 to current year + 10)
|
||||||
to_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=5)
|
import datetime
|
||||||
to_label = Gtk.Label(label=_("To:"))
|
current_year = datetime.date.today().year
|
||||||
to_label.set_size_request(80, -1)
|
min_year = 1000
|
||||||
to_entry = Gtk.Entry()
|
max_year = current_year + 10
|
||||||
to_entry.set_placeholder_text(_("YYYY-MM-DD or YYYY"))
|
|
||||||
to_box.pack_start(to_label, False, False, 0)
|
|
||||||
to_box.pack_start(to_entry, True, True, 0)
|
|
||||||
box.pack_start(to_box, False, False, 0)
|
|
||||||
|
|
||||||
self._filter_widgets['date_from_entry'] = from_entry
|
# From date calendar with year selector
|
||||||
self._filter_widgets['date_to_entry'] = to_entry
|
from_frame = Gtk.Frame(label=_("From Date"))
|
||||||
|
from_frame.set_label_align(0.5, 0.5)
|
||||||
|
from_calendar_container = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=5)
|
||||||
|
from_calendar_container.set_margin_start(5)
|
||||||
|
from_calendar_container.set_margin_end(5)
|
||||||
|
from_calendar_container.set_margin_top(5)
|
||||||
|
from_calendar_container.set_margin_bottom(5)
|
||||||
|
|
||||||
return box
|
# Year selector for From date
|
||||||
|
from_year_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=5)
|
||||||
|
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)
|
||||||
|
|
||||||
|
# To date calendar with year selector
|
||||||
|
to_frame = Gtk.Frame(label=_("To Date"))
|
||||||
|
to_frame.set_label_align(0.5, 0.5)
|
||||||
|
to_calendar_container = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=5)
|
||||||
|
to_calendar_container.set_margin_start(5)
|
||||||
|
to_calendar_container.set_margin_end(5)
|
||||||
|
to_calendar_container.set_margin_top(5)
|
||||||
|
to_calendar_container.set_margin_bottom(5)
|
||||||
|
|
||||||
|
# Year selector for To date
|
||||||
|
to_year_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=5)
|
||||||
|
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)
|
||||||
|
|
||||||
|
box.pack_start(calendar_box, True, True, 0)
|
||||||
|
|
||||||
|
# Clear button
|
||||||
|
clear_button = Gtk.Button(label=_("Clear Dates"))
|
||||||
|
clear_button.connect("clicked", self._on_clear_date_range)
|
||||||
|
button_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
|
||||||
|
button_box.pack_start(clear_button, False, False, 0)
|
||||||
|
box.pack_start(button_box, False, False, 0)
|
||||||
|
|
||||||
|
# Validation label
|
||||||
|
self.date_validation_label = Gtk.Label()
|
||||||
|
self.date_validation_label.set_line_wrap(True)
|
||||||
|
self.date_validation_label.set_markup("<span color='red'></span>")
|
||||||
|
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)
|
||||||
|
return scrolled
|
||||||
|
|
||||||
def _update_filter_dialog_state(self) -> None:
|
def _update_filter_dialog_state(self) -> None:
|
||||||
"""
|
"""
|
||||||
@ -882,18 +976,61 @@ class MyTimelineView(NavigationView):
|
|||||||
except (AttributeError, KeyError) as e:
|
except (AttributeError, KeyError) as e:
|
||||||
logger.warning(f"Error updating person filter: {e}", exc_info=True)
|
logger.warning(f"Error updating person filter: {e}", exc_info=True)
|
||||||
|
|
||||||
# Update date range entries
|
# Update date range calendars and year selectors
|
||||||
if 'date_from_entry' in self._filter_widgets and 'date_to_entry' in self._filter_widgets:
|
if 'date_from_calendar' in self._filter_widgets and 'date_to_calendar' in self._filter_widgets:
|
||||||
from_entry = self._filter_widgets['date_from_entry']
|
from_calendar = self._filter_widgets['date_from_calendar']
|
||||||
to_entry = self._filter_widgets['date_to_entry']
|
to_calendar = self._filter_widgets['date_to_calendar']
|
||||||
if self.date_range_filter:
|
|
||||||
min_date, max_date = self.date_range_filter
|
if self.date_range_filter and self.date_range_explicit:
|
||||||
# Convert sort values back to dates for display (simplified)
|
min_sort, max_sort = self.date_range_filter
|
||||||
from_entry.set_text("")
|
# Convert sort values back to dates for calendar display
|
||||||
to_entry.set_text("")
|
# Approximate conversion: extract year from sort value
|
||||||
|
# Sort value is roughly: year * 10000 + month * 100 + day
|
||||||
|
from_year = min_sort // 10000
|
||||||
|
to_year = max_sort // 10000
|
||||||
|
|
||||||
|
# Set calendar years (approximate)
|
||||||
|
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()
|
||||||
|
|
||||||
|
from_calendar.select_month(current_from_month, from_year)
|
||||||
|
to_calendar.select_month(current_to_month, to_year)
|
||||||
|
|
||||||
|
# Update year spin buttons
|
||||||
|
if 'date_from_year_spin' in self._filter_widgets:
|
||||||
|
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(from_year)
|
||||||
|
from_year_spin.handler_unblock_by_func(self._on_from_year_changed)
|
||||||
|
|
||||||
|
if 'date_to_year_spin' in self._filter_widgets:
|
||||||
|
to_year_spin = self._filter_widgets['date_to_year_spin']
|
||||||
|
to_year_spin.handler_block_by_func(self._on_to_year_changed)
|
||||||
|
to_year_spin.set_value(to_year)
|
||||||
|
to_year_spin.handler_unblock_by_func(self._on_to_year_changed)
|
||||||
else:
|
else:
|
||||||
from_entry.set_text("")
|
# Reset to current date
|
||||||
to_entry.set_text("")
|
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
|
||||||
|
if 'date_from_year_spin' in self._filter_widgets:
|
||||||
|
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:
|
||||||
|
to_year_spin = self._filter_widgets['date_to_year_spin']
|
||||||
|
to_year_spin.handler_block_by_func(self._on_to_year_changed)
|
||||||
|
to_year_spin.set_value(now.year)
|
||||||
|
to_year_spin.handler_unblock_by_func(self._on_to_year_changed)
|
||||||
|
|
||||||
|
# Clear validation message
|
||||||
|
if hasattr(self, 'date_validation_label'):
|
||||||
|
self.date_validation_label.set_text("")
|
||||||
|
|
||||||
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:
|
||||||
"""
|
"""
|
||||||
@ -908,6 +1045,7 @@ class MyTimelineView(NavigationView):
|
|||||||
self.filter_enabled = False
|
self.filter_enabled = False
|
||||||
self.active_event_types = set()
|
self.active_event_types = set()
|
||||||
self.date_range_filter = None
|
self.date_range_filter = None
|
||||||
|
self.date_range_explicit = False
|
||||||
self.person_filter = None
|
self.person_filter = None
|
||||||
self.category_filter = None
|
self.category_filter = None
|
||||||
self.apply_filters()
|
self.apply_filters()
|
||||||
@ -943,57 +1081,68 @@ class MyTimelineView(NavigationView):
|
|||||||
all_persons = set(self._filter_widgets['person_checkboxes'].keys())
|
all_persons = set(self._filter_widgets['person_checkboxes'].keys())
|
||||||
self.person_filter = active_persons if active_persons != all_persons else None
|
self.person_filter = active_persons if active_persons != all_persons else None
|
||||||
|
|
||||||
# Update date range filter
|
# Update date range filter from calendar widgets
|
||||||
if 'date_from_entry' in self._filter_widgets and 'date_to_entry' in self._filter_widgets:
|
if 'date_from_calendar' in self._filter_widgets and 'date_to_calendar' in self._filter_widgets:
|
||||||
from_text = self._filter_widgets['date_from_entry'].get_text().strip()
|
from_calendar = self._filter_widgets['date_from_calendar']
|
||||||
to_text = self._filter_widgets['date_to_entry'].get_text().strip()
|
to_calendar = self._filter_widgets['date_to_calendar']
|
||||||
|
|
||||||
if from_text or to_text:
|
# Check if dates were explicitly set (stored in widget data)
|
||||||
# Parse dates using Gramps Date objects
|
# We'll use a simple approach: if user clicked calendars, use the dates
|
||||||
try:
|
# For now, we'll always use calendar dates if they're valid
|
||||||
min_sort = None
|
# The "Clear Dates" button will reset the explicit flag
|
||||||
max_sort = None
|
|
||||||
|
# Get selected dates from calendars
|
||||||
if from_text:
|
# Gtk.Calendar.get_date() returns (year, month, day) where month is 0-11
|
||||||
# Try to parse the date string
|
from_year, from_month, from_day = from_calendar.get_date()
|
||||||
# Support formats: YYYY, YYYY-MM, YYYY-MM-DD
|
to_year, to_month, to_day = to_calendar.get_date()
|
||||||
parts = from_text.split('-')
|
|
||||||
year = int(parts[0])
|
try:
|
||||||
month = int(parts[1]) if len(parts) > 1 else 1
|
# Create Date objects from calendar selections
|
||||||
day = int(parts[2]) if len(parts) > 2 else 1
|
# Note: month from calendar is 0-11, Date.set_yr_mon_day expects 1-12
|
||||||
|
from_date = Date()
|
||||||
from_date = Date()
|
from_date.set_yr_mon_day(from_year, from_month + 1, from_day)
|
||||||
from_date.set_yr_mon_day(year, month, day)
|
min_sort = from_date.get_sort_value()
|
||||||
min_sort = from_date.get_sort_value()
|
|
||||||
|
to_date = Date()
|
||||||
if to_text:
|
to_date.set_yr_mon_day(to_year, to_month + 1, to_day)
|
||||||
# Try to parse the date string
|
max_sort = to_date.get_sort_value()
|
||||||
parts = to_text.split('-')
|
|
||||||
year = int(parts[0])
|
# Validate date range
|
||||||
month = int(parts[1]) if len(parts) > 1 else 12
|
if min_sort > max_sort:
|
||||||
day = int(parts[2]) if len(parts) > 2 else 31
|
# Show error message
|
||||||
|
if hasattr(self, 'date_validation_label'):
|
||||||
to_date = Date()
|
self.date_validation_label.set_markup(
|
||||||
to_date.set_yr_mon_day(year, month, day)
|
f"<span color='red'>{_('Error: From date must be before To date')}</span>"
|
||||||
max_sort = to_date.get_sort_value()
|
)
|
||||||
|
|
||||||
# If only one date is provided, set reasonable defaults
|
|
||||||
if min_sort is None:
|
|
||||||
min_sort = 0
|
|
||||||
if max_sort is None:
|
|
||||||
max_sort = 99999999
|
|
||||||
|
|
||||||
self.date_range_filter = (min_sort, max_sort) if (from_text or to_text) else None
|
|
||||||
except (ValueError, AttributeError, TypeError) as e:
|
|
||||||
logger.warning(f"Error parsing date range: {e}", exc_info=True)
|
|
||||||
self.date_range_filter = None
|
self.date_range_filter = None
|
||||||
else:
|
self.date_range_explicit = False
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
# Clear error message
|
||||||
|
if hasattr(self, 'date_validation_label'):
|
||||||
|
self.date_validation_label.set_text("")
|
||||||
|
|
||||||
|
# Set filter - user has selected dates in calendars
|
||||||
|
self.date_range_filter = (min_sort, max_sort)
|
||||||
|
self.date_range_explicit = True
|
||||||
|
|
||||||
|
except (ValueError, AttributeError, TypeError) as e:
|
||||||
|
logger.warning(f"Error parsing date range: {e}", exc_info=True)
|
||||||
self.date_range_filter = None
|
self.date_range_filter = None
|
||||||
|
self.date_range_explicit = False
|
||||||
|
if hasattr(self, 'date_validation_label'):
|
||||||
|
self.date_validation_label.set_markup(
|
||||||
|
f"<span color='red'>{_('Error: Invalid date')}</span>"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# No calendar widgets, clear filter
|
||||||
|
self.date_range_filter = None
|
||||||
|
self.date_range_explicit = False
|
||||||
|
|
||||||
# Enable filter if any filter is active
|
# Enable filter if any filter is active
|
||||||
self.filter_enabled = (
|
self.filter_enabled = (
|
||||||
self.active_event_types or
|
self.active_event_types or
|
||||||
self.date_range_filter is not None or
|
(self.date_range_filter is not None and self.date_range_explicit) or
|
||||||
self.person_filter is not None or
|
self.person_filter is not None or
|
||||||
self.category_filter is not None
|
self.category_filter is not None
|
||||||
)
|
)
|
||||||
@ -1035,6 +1184,175 @@ class MyTimelineView(NavigationView):
|
|||||||
for checkbox in self._filter_widgets['event_type_checkboxes'].values():
|
for checkbox in self._filter_widgets['event_type_checkboxes'].values():
|
||||||
checkbox.set_active(False)
|
checkbox.set_active(False)
|
||||||
|
|
||||||
|
def _on_from_date_selected(self, calendar: Gtk.Calendar) -> None:
|
||||||
|
"""
|
||||||
|
Handle From date calendar selection.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
calendar: The calendar widget that was selected.
|
||||||
|
"""
|
||||||
|
# Update year spin button to match calendar
|
||||||
|
if 'date_from_year_spin' in self._filter_widgets:
|
||||||
|
year, month, day = calendar.get_date()
|
||||||
|
year_spin = self._filter_widgets['date_from_year_spin']
|
||||||
|
year_spin.handler_block_by_func(self._on_from_year_changed)
|
||||||
|
year_spin.set_value(year)
|
||||||
|
year_spin.handler_unblock_by_func(self._on_from_year_changed)
|
||||||
|
|
||||||
|
self._validate_date_range()
|
||||||
|
|
||||||
|
def _on_to_date_selected(self, calendar: Gtk.Calendar) -> None:
|
||||||
|
"""
|
||||||
|
Handle To date calendar selection.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
calendar: The calendar widget that was selected.
|
||||||
|
"""
|
||||||
|
# Update year spin button to match calendar
|
||||||
|
if 'date_to_year_spin' in self._filter_widgets:
|
||||||
|
year, month, day = calendar.get_date()
|
||||||
|
year_spin = self._filter_widgets['date_to_year_spin']
|
||||||
|
year_spin.handler_block_by_func(self._on_to_year_changed)
|
||||||
|
year_spin.set_value(year)
|
||||||
|
year_spin.handler_unblock_by_func(self._on_to_year_changed)
|
||||||
|
|
||||||
|
self._validate_date_range()
|
||||||
|
|
||||||
|
def _on_from_calendar_changed(self, calendar: Gtk.Calendar) -> None:
|
||||||
|
"""
|
||||||
|
Handle From calendar month/year change.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
calendar: The calendar widget that changed.
|
||||||
|
"""
|
||||||
|
# Update year spin button to match calendar
|
||||||
|
if 'date_from_year_spin' in self._filter_widgets:
|
||||||
|
year, month, day = calendar.get_date()
|
||||||
|
year_spin = self._filter_widgets['date_from_year_spin']
|
||||||
|
year_spin.handler_block_by_func(self._on_from_year_changed)
|
||||||
|
year_spin.set_value(year)
|
||||||
|
year_spin.handler_unblock_by_func(self._on_from_year_changed)
|
||||||
|
|
||||||
|
def _on_to_calendar_changed(self, calendar: Gtk.Calendar) -> None:
|
||||||
|
"""
|
||||||
|
Handle To calendar month/year change.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
calendar: The calendar widget that changed.
|
||||||
|
"""
|
||||||
|
# Update year spin button to match calendar
|
||||||
|
if 'date_to_year_spin' in self._filter_widgets:
|
||||||
|
year, month, day = calendar.get_date()
|
||||||
|
year_spin = self._filter_widgets['date_to_year_spin']
|
||||||
|
year_spin.handler_block_by_func(self._on_to_year_changed)
|
||||||
|
year_spin.set_value(year)
|
||||||
|
year_spin.handler_unblock_by_func(self._on_to_year_changed)
|
||||||
|
|
||||||
|
def _on_from_year_changed(self, spin_button: Gtk.SpinButton) -> None:
|
||||||
|
"""
|
||||||
|
Handle From year spin button change.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
spin_button: The year spin button that changed.
|
||||||
|
"""
|
||||||
|
if 'date_from_calendar' in self._filter_widgets:
|
||||||
|
calendar = self._filter_widgets['date_from_calendar']
|
||||||
|
new_year = int(spin_button.get_value())
|
||||||
|
current_year, current_month, current_day = calendar.get_date()
|
||||||
|
# Update calendar to new year, keeping same month and day
|
||||||
|
calendar.select_month(current_month, new_year)
|
||||||
|
# Trigger validation
|
||||||
|
self._validate_date_range()
|
||||||
|
|
||||||
|
def _on_to_year_changed(self, spin_button: Gtk.SpinButton) -> None:
|
||||||
|
"""
|
||||||
|
Handle To year spin button change.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
spin_button: The year spin button that changed.
|
||||||
|
"""
|
||||||
|
if 'date_to_calendar' in self._filter_widgets:
|
||||||
|
calendar = self._filter_widgets['date_to_calendar']
|
||||||
|
new_year = int(spin_button.get_value())
|
||||||
|
current_year, current_month, current_day = calendar.get_date()
|
||||||
|
# Update calendar to new year, keeping same month and day
|
||||||
|
calendar.select_month(current_month, new_year)
|
||||||
|
# Trigger validation
|
||||||
|
self._validate_date_range()
|
||||||
|
|
||||||
|
def _on_clear_date_range(self, button: Gtk.Button) -> None:
|
||||||
|
"""
|
||||||
|
Clear the date range selection.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
button: The clear button that was clicked.
|
||||||
|
"""
|
||||||
|
if 'date_from_calendar' in self._filter_widgets and 'date_to_calendar' in self._filter_widgets:
|
||||||
|
from_calendar = self._filter_widgets['date_from_calendar']
|
||||||
|
to_calendar = self._filter_widgets['date_to_calendar']
|
||||||
|
|
||||||
|
# Reset to current date (calendars always show a date)
|
||||||
|
# Get current date and set calendars to it
|
||||||
|
import datetime
|
||||||
|
now = datetime.date.today()
|
||||||
|
from_calendar.select_month(now.month - 1, now.year) # month is 0-11
|
||||||
|
from_calendar.select_day(now.day)
|
||||||
|
to_calendar.select_month(now.month - 1, now.year)
|
||||||
|
to_calendar.select_day(now.day)
|
||||||
|
|
||||||
|
# Reset year spin buttons
|
||||||
|
if 'date_from_year_spin' in self._filter_widgets:
|
||||||
|
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:
|
||||||
|
to_year_spin = self._filter_widgets['date_to_year_spin']
|
||||||
|
to_year_spin.handler_block_by_func(self._on_to_year_changed)
|
||||||
|
to_year_spin.set_value(now.year)
|
||||||
|
to_year_spin.handler_unblock_by_func(self._on_to_year_changed)
|
||||||
|
|
||||||
|
# Clear validation message
|
||||||
|
if hasattr(self, 'date_validation_label'):
|
||||||
|
self.date_validation_label.set_text("")
|
||||||
|
|
||||||
|
# Mark that date range should not be applied
|
||||||
|
self.date_range_explicit = False
|
||||||
|
|
||||||
|
def _validate_date_range(self) -> None:
|
||||||
|
"""
|
||||||
|
Validate that From date is not after To date.
|
||||||
|
"""
|
||||||
|
if 'date_from_calendar' not in self._filter_widgets or 'date_to_calendar' not in self._filter_widgets:
|
||||||
|
return
|
||||||
|
|
||||||
|
from_calendar = self._filter_widgets['date_from_calendar']
|
||||||
|
to_calendar = self._filter_widgets['date_to_calendar']
|
||||||
|
|
||||||
|
from_year, from_month, from_day = from_calendar.get_date()
|
||||||
|
to_year, to_month, to_day = to_calendar.get_date()
|
||||||
|
|
||||||
|
try:
|
||||||
|
from_date = Date()
|
||||||
|
from_date.set_yr_mon_day(from_year, from_month + 1, from_day)
|
||||||
|
from_sort = from_date.get_sort_value()
|
||||||
|
|
||||||
|
to_date = Date()
|
||||||
|
to_date.set_yr_mon_day(to_year, to_month + 1, to_day)
|
||||||
|
to_sort = to_date.get_sort_value()
|
||||||
|
|
||||||
|
if from_sort > to_sort:
|
||||||
|
if hasattr(self, 'date_validation_label'):
|
||||||
|
self.date_validation_label.set_markup(
|
||||||
|
f"<span color='red'>{_('Warning: From date is after To date')}</span>"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
if hasattr(self, 'date_validation_label'):
|
||||||
|
self.date_validation_label.set_text("")
|
||||||
|
except (ValueError, AttributeError, TypeError):
|
||||||
|
pass
|
||||||
|
|
||||||
def build_tree(self) -> None:
|
def build_tree(self) -> None:
|
||||||
"""
|
"""
|
||||||
Rebuilds the current display. Called when the view becomes visible.
|
Rebuilds the current display. Called when the view becomes visible.
|
||||||
@ -1307,7 +1625,7 @@ class MyTimelineView(NavigationView):
|
|||||||
Returns:
|
Returns:
|
||||||
bool: True if date is in range (or no filter active), False otherwise.
|
bool: True if date is in range (or no filter active), False otherwise.
|
||||||
"""
|
"""
|
||||||
if not self.date_range_filter:
|
if not self.date_range_filter or not self.date_range_explicit:
|
||||||
return True
|
return True
|
||||||
min_date, max_date = self.date_range_filter
|
min_date, max_date = self.date_range_filter
|
||||||
return min_date <= date_sort <= max_date
|
return min_date <= date_sort <= max_date
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user