From 0c8724f26a5bc5d851c4d444695373a248c06114 Mon Sep 17 00:00:00 2001 From: Vladimir Tyrtov Date: Wed, 19 Nov 2025 21:09:41 +0300 Subject: [PATCH] Fix filmstrip: keyboard shortcuts apply to wrong image Fixes #19775 Problem: When the mouse cursor is over a thumbnail in the filmstrip (especially over overlay elements like stars or reject button), keyboard shortcuts for rating, color labels, etc. were being applied to the currently open image in darkroom instead of the image under the cursor. Additionally, fast transitions between overlay elements of different thumbnails didn't update mouse_over_id correctly. Root cause: Keyboard events trigger spurious GDK_LEAVE_NOTIFY events with GDK_NOTIFY_ANCESTOR detail in GTK. The thumbnail's leave event handlers were resetting mouse_over_id to NO_IMGID when receiving these events, causing dt_act_on_get_images() to fall back to using active_images instead of the hovered image. before updating mouse_over_id, which prevented updates during fast transitions between overlay elements of different thumbnails. Solution: 1. Remove mouse_over_id resets from thumbnail-level leave handlers. The proper place to reset mouse_over_id is in thumbtable's leave handler (_event_leave_notify in thumbtable.c), which correctly distinguishes between leaving the thumbnail for another thumbnail (GDK_NOTIFY_INFERIOR) and actually leaving the thumbtable area. ensure mouse_over_id is updated on every enter event, enabling proper tracking during fast mouse movements between thumbnails. 3. Add mouse_over_id update to _event_btn_enter_leave for overlay buttons (reject, color labels, etc.) to handle transitions to these elements. 4. Add proper darktable.control->element reset in leave handler for reject button to ensure hover state is cleared correctly. Changes: _event_image_enter_leave, and _event_star_enter - Added mouse_over_id update to _event_btn_enter_leave enter handler - Added darktable.control->element reset to _event_btn_enter_leave leave handler for reject button - Removed _event_main_leave function and its signal connection entirely as it served no purpose after removing the mouse_over_id reset --- src/dtgtk/thumbnail.c | 58 ++++++++++++++----------------------------- 1 file changed, 19 insertions(+), 39 deletions(-) diff --git a/src/dtgtk/thumbnail.c b/src/dtgtk/thumbnail.c index 2abc1e8f2b6b..f7efc3583657 100644 --- a/src/dtgtk/thumbnail.c +++ b/src/dtgtk/thumbnail.c @@ -1239,14 +1239,8 @@ static gboolean _event_box_enter_leave(GtkWidget *widget, gpointer user_data) { dt_thumbnail_t *thumb = (dt_thumbnail_t *)user_data; - // if we leave for ancestor, that means we leave for blank thumbtable area - if(event->type == GDK_LEAVE_NOTIFY - && event->detail == GDK_NOTIFY_ANCESTOR) - dt_control_set_mouse_over_id(NO_IMGID); - - if(!thumb->mouse_over - && event->type == GDK_ENTER_NOTIFY - && !thumb->disable_mouseover) + + if(event->type == GDK_ENTER_NOTIFY && !thumb->disable_mouseover) dt_control_set_mouse_over_id(thumb->imgid); _set_flag(widget, GTK_STATE_FLAG_PRELIGHT, (event->type == GDK_ENTER_NOTIFY)); @@ -1261,9 +1255,7 @@ static gboolean _event_image_enter_leave(GtkWidget *widget, { dt_thumbnail_t *thumb = (dt_thumbnail_t *)user_data; - // we ensure that the image has mouse over - if(!thumb->mouse_over && event->type == GDK_ENTER_NOTIFY - && !thumb->disable_mouseover) + if(event->type == GDK_ENTER_NOTIFY && !thumb->disable_mouseover) dt_control_set_mouse_over_id(thumb->imgid); _set_flag(thumb->w_image_box, GTK_STATE_FLAG_PRELIGHT, @@ -1277,23 +1269,25 @@ static gboolean _event_btn_enter_leave(GtkWidget *widget, { dt_thumbnail_t *thumb = (dt_thumbnail_t *)user_data; - darktable.control->element = - (event->type == GDK_ENTER_NOTIFY && widget == thumb->w_reject) - ? DT_VIEW_REJECT - : -1; - - // if we leave for ancestor, that means we leave for blank thumbtable area - if(event->type == GDK_LEAVE_NOTIFY - && event->detail == GDK_NOTIFY_ANCESTOR) - dt_control_set_mouse_over_id(NO_IMGID); - - if(thumb->disable_actions) - return TRUE; if(event->type == GDK_ENTER_NOTIFY) { + if(widget == thumb->w_reject) + darktable.control->element = DT_VIEW_REJECT; + + if(thumb->disable_actions) + return TRUE; + + if(!thumb->disable_mouseover) + dt_control_set_mouse_over_id(thumb->imgid); _set_flag(thumb->w_image_box, GTK_STATE_FLAG_PRELIGHT, TRUE); _thumb_update_tags_tooltip(thumb); } + else if(event->type == GDK_LEAVE_NOTIFY) + { + if(widget == thumb->w_reject) + darktable.control->element = -1; + } + return FALSE; } @@ -1303,7 +1297,8 @@ static gboolean _event_star_enter(GtkWidget *widget, { dt_thumbnail_t *thumb = (dt_thumbnail_t *)user_data; if(thumb->disable_actions) return TRUE; - if(!thumb->mouse_over && !thumb->disable_mouseover) + + if(!thumb->disable_mouseover) dt_control_set_mouse_over_id(thumb->imgid); _set_flag(thumb->w_bottom_eb, GTK_STATE_FLAG_PRELIGHT, TRUE); @@ -1328,10 +1323,6 @@ static gboolean _event_star_leave(GtkWidget *widget, gpointer user_data) { dt_thumbnail_t *thumb = (dt_thumbnail_t *)user_data; - // if we leave for ancestor, that means we leave for blank thumbtable area - if(event->type == GDK_LEAVE_NOTIFY - && event->detail == GDK_NOTIFY_ANCESTOR) - dt_control_set_mouse_over_id(NO_IMGID); if(thumb->disable_actions) return TRUE; for(int i = 0; i < MAX_STARS; i++) @@ -1342,15 +1333,6 @@ static gboolean _event_star_leave(GtkWidget *widget, return TRUE; } -static gboolean _event_main_leave(GtkWidget *widget, - GdkEventCrossing *event, - gpointer user_data) -{ - // if we leave for ancestor, that means we leave for blank thumbtable area - if(event->detail == GDK_NOTIFY_ANCESTOR) dt_control_set_mouse_over_id(NO_IMGID); - return FALSE; -} - // we only want to specify that the mouse is hovereing the thumbnail static gboolean _event_main_drag_motion(GtkWidget *widget, GdkDragContext *dc, @@ -1422,8 +1404,6 @@ GtkWidget *dt_thumbnail_create_widget(dt_thumbnail_t *thumb, gtk_widget_set_name(thumb->w_back, "thumb-back"); g_signal_connect(G_OBJECT(thumb->w_back), "motion-notify-event", G_CALLBACK(_event_main_motion), thumb); - g_signal_connect(G_OBJECT(thumb->w_back), "leave-notify-event", - G_CALLBACK(_event_main_leave), thumb); gtk_widget_show(thumb->w_back); gtk_container_add(GTK_CONTAINER(thumb->w_main), thumb->w_back);