diff --git a/gtk/gtkeventcontroller.h b/gtk/gtkeventcontroller.h index 38569453f1..2cd8167c03 100644 --- a/gtk/gtkeventcontroller.h +++ b/gtk/gtkeventcontroller.h @@ -48,10 +48,15 @@ typedef struct _GtkCrossingData GtkCrossingData; * @direction: whether this is a focus-in or focus-out event * @mode: the crossing mode * @old_target: the old target + * @old_descendent: the direct child of the receiving widget that + * is an ancestor of @old_target, or %NULL if @old_target is not + * a descendent of the receiving widget * @new_target: the new target + * @new_descendent: the direct child of the receiving widget that + * is an ancestor of @new_target, or %NULL if @new_target is not + * a descendent of the receiving widget * - * The struct that is passed to gtk_event_controller_handle_crossing() - * and is also passed to #GtkEventControllerKey::focus-change. + * The struct that is passed to gtk_event_controller_handle_crossing(). * * The @old_target and @new_target fields are set to the old or new * focus or hover location. @@ -61,7 +66,9 @@ struct _GtkCrossingData { GtkCrossingDirection direction; GdkCrossingMode mode; GtkWidget *old_target; + GtkWidget *old_descendent; GtkWidget *new_target; + GtkWidget *new_descendent; }; GDK_AVAILABLE_IN_ALL diff --git a/gtk/gtkeventcontrollerkey.c b/gtk/gtkeventcontrollerkey.c index a8a5912d33..14a79e7b0c 100644 --- a/gtk/gtkeventcontrollerkey.c +++ b/gtk/gtkeventcontrollerkey.c @@ -548,6 +548,24 @@ gtk_event_controller_key_get_focus_target (GtkEventControllerKey *controller) return controller->current_crossing->new_target; } +GtkWidget * +gtk_event_controller_key_get_old_focus_child (GtkEventControllerKey *controller) +{ + g_return_val_if_fail (GTK_IS_EVENT_CONTROLLER_KEY (controller), NULL); + g_return_val_if_fail (controller->current_crossing != NULL, NULL); + + return controller->current_crossing->old_descendent; +} + +GtkWidget * +gtk_event_controller_key_get_new_focus_child (GtkEventControllerKey *controller) +{ + g_return_val_if_fail (GTK_IS_EVENT_CONTROLLER_KEY (controller), NULL); + g_return_val_if_fail (controller->current_crossing != NULL, NULL); + + return controller->current_crossing->new_descendent; +} + /** * gtk_event_controller_key_contains_focus: * @self: a #GtkEventControllerKey diff --git a/gtk/gtkeventcontrollerkey.h b/gtk/gtkeventcontrollerkey.h index 755947d255..4aa22777d6 100644 --- a/gtk/gtkeventcontrollerkey.h +++ b/gtk/gtkeventcontrollerkey.h @@ -62,6 +62,10 @@ GDK_AVAILABLE_IN_ALL GtkWidget * gtk_event_controller_key_get_focus_origin (GtkEventControllerKey *controller); GDK_AVAILABLE_IN_ALL GtkWidget * gtk_event_controller_key_get_focus_target (GtkEventControllerKey *controller); +GDK_AVAILABLE_IN_ALL +GtkWidget * gtk_event_controller_key_get_old_focus_child (GtkEventControllerKey *controller); +GDK_AVAILABLE_IN_ALL +GtkWidget * gtk_event_controller_key_get_new_focus_child (GtkEventControllerKey *controller); GDK_AVAILABLE_IN_ALL gboolean gtk_event_controller_key_contains_focus (GtkEventControllerKey *self); diff --git a/gtk/gtkwindow.c b/gtk/gtkwindow.c index 736d76441b..d399d3c7d7 100644 --- a/gtk/gtkwindow.c +++ b/gtk/gtkwindow.c @@ -6346,15 +6346,49 @@ gtk_window_move_focus (GtkWidget *widget, gtk_window_set_focus (GTK_WINDOW (widget), NULL); } +static void +check_crossing_invariants (GtkWidget *widget, + GtkCrossingData *crossing) +{ + if (crossing->old_target == NULL) + g_assert (crossing->old_descendent == NULL); + else if (crossing->old_descendent == NULL) + g_assert (crossing->old_target == widget || !gtk_widget_is_ancestor (crossing->old_target, widget)); + else + { + g_assert (gtk_widget_get_parent (crossing->old_descendent) == widget); + g_assert (gtk_widget_is_ancestor (crossing->old_descendent, widget)); + g_assert (crossing->old_target == crossing->old_descendent || gtk_widget_is_ancestor (crossing->old_target, crossing->old_descendent)); + } + if (crossing->new_target == NULL) + g_assert (crossing->new_descendent == NULL); + else if (crossing->new_descendent == NULL) + g_assert (crossing->new_target == widget || !gtk_widget_is_ancestor (crossing->new_target, widget)); + else + { + g_assert (gtk_widget_get_parent (crossing->new_descendent) == widget); + g_assert (gtk_widget_is_ancestor (crossing->new_descendent, widget)); + g_assert (crossing->new_target == crossing->new_descendent || gtk_widget_is_ancestor (crossing->new_target, crossing->new_descendent)); + } +} + static void synthesize_focus_change_events (GtkWindow *window, GtkWidget *old_focus, GtkWidget *new_focus) { GtkCrossingData crossing; + GtkWidget *ancestor; GtkWidget *widget, *focus_child; GList *list, *l; GtkStateFlags flags; + GtkWidget *prev; + gboolean seen_ancestor; + + if (old_focus && new_focus) + ancestor = gtk_widget_common_ancestor (old_focus, new_focus); + else + ancestor = NULL; flags = GTK_STATE_FLAG_FOCUSED; if (gtk_window_get_focus_visible (GTK_WINDOW (window))) @@ -6363,29 +6397,52 @@ synthesize_focus_change_events (GtkWindow *window, crossing.type = GTK_CROSSING_FOCUS; crossing.mode = GDK_CROSSING_NORMAL; crossing.old_target = old_focus; + crossing.old_descendent = NULL; crossing.new_target = new_focus; + crossing.new_descendent = NULL; crossing.direction = GTK_CROSSING_OUT; + prev = NULL; + seen_ancestor = FALSE; widget = old_focus; while (widget) { + crossing.old_descendent = prev; + if (seen_ancestor) + { + crossing.new_descendent = new_focus ? prev : NULL; + } + else if (widget == ancestor) + { + GtkWidget *w; + + crossing.new_descendent = NULL; + for (w = new_focus; w != ancestor; w = gtk_widget_get_parent (w)) + crossing.new_descendent = w; + + seen_ancestor = TRUE; + } + else + { + crossing.new_descendent = NULL; + } + + check_crossing_invariants (widget, &crossing); gtk_widget_handle_crossing (widget, &crossing, 0, 0); gtk_widget_unset_state_flags (widget, flags); gtk_widget_set_focus_child (widget, NULL); + prev = widget; widget = gtk_widget_get_parent (widget); } list = NULL; - widget = new_focus; - while (widget) - { - list = g_list_prepend (list, widget); - widget = gtk_widget_get_parent (widget); - } + for (widget = new_focus; widget; widget = gtk_widget_get_parent (widget)) + list = g_list_prepend (list, widget); crossing.direction = GTK_CROSSING_IN; + seen_ancestor = FALSE; for (l = list; l; l = l->next) { widget = l->data; @@ -6393,6 +6450,29 @@ synthesize_focus_change_events (GtkWindow *window, focus_child = l->next->data; else focus_child = NULL; + + crossing.new_descendent = focus_child; + if (seen_ancestor) + { + crossing.old_descendent = NULL; + } + else if (widget == ancestor) + { + GtkWidget *w; + + crossing.old_descendent = NULL; + for (w = old_focus; w != ancestor; w = gtk_widget_get_parent (w)) + { + crossing.old_descendent = w; + } + + seen_ancestor = TRUE; + } + else + { + crossing.old_descendent = old_focus ? focus_child : NULL; + } + check_crossing_invariants (widget, &crossing); gtk_widget_handle_crossing (widget, &crossing, 0, 0); gtk_widget_set_state_flags (widget, flags, FALSE); gtk_widget_set_focus_child (widget, focus_child);