From 0642e2031505fa25c18cadd6ea9e840dccbe862b Mon Sep 17 00:00:00 2001 From: Daniel Viegas Date: Fri, 28 Nov 2025 23:34:52 +0100 Subject: [PATCH] Fix mouse position/selection mismatch and improve interaction - Fix mouse coordinate transformation to account for zoom level - Enable whole-line selection (click anywhere on event line to select person) - Update tooltip to show date first, then event type - Make connection lines more visible with increased opacity and width - Improved click detection using scaled coordinates - Expanded clickable area to include both marker and label regions --- MyTimeline.py | 99 +++++++++++++++++++++++++++++++-------------------- 1 file changed, 60 insertions(+), 39 deletions(-) diff --git a/MyTimeline.py b/MyTimeline.py index 1fe5ed2..420888f 100644 --- a/MyTimeline.py +++ b/MyTimeline.py @@ -607,28 +607,31 @@ class MyTimelineView(NavigationView): # Find which event was clicked clicked_index = self.find_event_at_position(event.x, event.y) if clicked_index is not None: - # Check if clicking on marker (for person selection) or label (for expansion) + # Convert mouse coordinates to drawing coordinates (account for zoom) + scaled_x = event.x / self.zoom_level + date_sort, date_obj, clicked_event, clicked_person, event_type, expanded, _y_pos = self.events[clicked_index] # Check if click is on marker area (left side) or label area (right side) timeline_x = TIMELINE_MARGIN_LEFT - marker_area_width = EVENT_MARKER_SIZE * self.zoom_level + 20 + marker_area_width = EVENT_MARKER_SIZE + 20 - if event.x < timeline_x + marker_area_width: - # Click on marker - toggle person selection - if clicked_person: - person_handle = clicked_person.get_handle() - if self.selected_person_handle == person_handle: - # Deselect if clicking same person - self.selected_person_handle = None - else: - # Select this person - self.selected_person_handle = person_handle - else: - # No person for this event, deselect + # Allow person selection from anywhere on the line + # Clicking anywhere on the event line selects the person + if clicked_person: + person_handle = clicked_person.get_handle() + if self.selected_person_handle == person_handle: + # Deselect if clicking same person self.selected_person_handle = None + else: + # Select this person + self.selected_person_handle = person_handle else: - # Click on label - toggle expansion + # No person for this event, deselect + self.selected_person_handle = None + + # Also toggle expansion if clicking on label area (right side) + if scaled_x > timeline_x + marker_area_width: if self.expanded_event_index == clicked_index: self.expanded_event_index = None else: @@ -690,9 +693,13 @@ class MyTimelineView(NavigationView): if not self.events: return None - # Get widget dimensions - width = self.drawing_area.get_allocated_width() - height = self.drawing_area.get_allocated_height() + # Convert mouse coordinates to drawing coordinates (account for zoom) + scaled_x = x / self.zoom_level + scaled_y = y / self.zoom_level + + # Get widget dimensions in drawing coordinates + width = self.drawing_area.get_allocated_width() / self.zoom_level + height = self.drawing_area.get_allocated_height() / self.zoom_level # Calculate date range min_date = min(event[0] for event in self.events) @@ -703,17 +710,34 @@ class MyTimelineView(NavigationView): timeline_x = TIMELINE_MARGIN_LEFT - # Check each event - for i, (date_sort, date_obj, event, person, event_type, expanded, _y_pos) in enumerate(self.events): - # Calculate Y position + # Calculate initial Y positions (same as in on_draw) + events_with_y_pos = [] + for i, event_data in enumerate(self.events): + date_sort, date_obj, event, person, event_type, expanded, _ = event_data y_pos = TIMELINE_MARGIN_TOP + ( (date_sort - min_date) / date_range ) * (height - TIMELINE_MARGIN_TOP - TIMELINE_MARGIN_BOTTOM) + events_with_y_pos.append((i, y_pos, event_data)) + + # Check each event using adjusted positions + # We need to simulate the collision detection, but for simplicity, + # we'll check against the calculated positions and use a wider tolerance + for i, y_pos, event_data in events_with_y_pos: + date_sort, date_obj, event, person, event_type, expanded, _ = event_data - # Check if click is near the marker - marker_size = EVENT_MARKER_SIZE * self.zoom_level - if (abs(x - timeline_x) < marker_size + 10 and - abs(y - y_pos) < marker_size + 20): + # Calculate clickable area - wider to include label area + marker_size = EVENT_MARKER_SIZE + label_x = timeline_x + 25 + + # Estimate label width (we'll use a reasonable default) + # For whole-line selection, check if click is in the event's horizontal band + clickable_width = 600 # Reasonable width for label area + clickable_height = max(marker_size * 2, 30) # At least marker size or 30px + + # Check if click is in the event's area (marker + label) + if (scaled_x >= timeline_x - marker_size - 10 and + scaled_x <= label_x + clickable_width and + abs(scaled_y - y_pos) < clickable_height / 2): return i return None @@ -744,11 +768,11 @@ class MyTimelineView(NavigationView): tooltip_text = f"{person_name}\n" tooltip_text += "─" * 30 + "\n" - # List all events for this person + # List all events for this person (date first) for evt_date_sort, evt_date_obj, evt_event, evt_event_type in person_events: evt_date_str = get_date(evt_event) evt_event_type_str = str(evt_event_type) - tooltip_text += f"{evt_event_type_str} - {evt_date_str}\n" + tooltip_text += f"{evt_date_str} - {evt_event_type_str}\n" # Add place if available evt_place_handle = evt_event.get_place_handle() @@ -761,11 +785,11 @@ class MyTimelineView(NavigationView): except: pass else: - # Family event (no person) - show single event info + # Family event (no person) - show single event info (date first) date_str = get_date(event) event_type_str = str(event_type) - tooltip_text = f"{event_type_str}\n{date_str}" + tooltip_text = f"{date_str}\n{event_type_str}" # Get place information place_handle = event.get_place_handle() @@ -1207,9 +1231,9 @@ class MyTimelineView(NavigationView): context.save() - # Draw connecting lines - context.set_source_rgba(0.2, 0.4, 0.9, 0.5) # Semi-transparent blue - context.set_line_width(2) + # Draw connecting lines - more visible with brighter color and increased opacity + context.set_source_rgba(0.2, 0.5, 1.0, 0.75) # Brighter, more opaque blue + context.set_line_width(3.5) # Increased from 2 context.set_line_cap(cairo.LINE_CAP_ROUND) context.set_line_join(cairo.LINE_JOIN_ROUND) @@ -1219,19 +1243,16 @@ class MyTimelineView(NavigationView): context.line_to(timeline_x + EVENT_MARKER_SIZE * 1.5, y_pos) context.stroke() - # Draw curved path connecting all events (optional - can be commented out if too cluttered) + # Draw vertical connector line on the left side if len(person_events) > 1: - # Draw a subtle curved path connecting all markers - context.set_source_rgba(0.2, 0.4, 0.9, 0.3) # More transparent - context.set_line_width(1.5) - - # Create a smooth curve through all points y_positions = [y for y, _event_data in person_events] min_y = min(y_positions) max_y = max(y_positions) - # Draw a vertical line on the left side connecting the range + # Draw a more visible vertical line connecting the range if max_y - min_y > EVENT_MARKER_SIZE * 2: + context.set_source_rgba(0.2, 0.5, 1.0, 0.6) # Slightly less opaque but still visible + context.set_line_width(2.5) # Thicker than before context.move_to(timeline_x - 15, min_y) context.line_to(timeline_x - 15, max_y) context.stroke()