[gtk/readonly-events-1] Split off GtkEventControllerFocus



commit b9a0a1d65a09dd05a76920e14c54a8904d7cf83c
Author: Matthias Clasen <mclasen redhat com>
Date:   Wed Feb 19 20:51:03 2020 -0500

    Split off GtkEventControllerFocus
    
    Split the focus tracking into a separate
    GtkEventControllerFocus, and change the API one more time.
    We are back to having ::focus-in and ::focus-out signals.
    
    Update all users.

 gtk/gtk.h                     |   1 +
 gtk/gtkcalendar.c             |  18 ++-
 gtk/gtkentrycompletion.c      |  12 +-
 gtk/gtkentryprivate.h         |   1 +
 gtk/gtkeventcontrollerfocus.c | 361 ++++++++++++++++++++++++++++++++++++++++++
 gtk/gtkeventcontrollerfocus.h |  66 ++++++++
 gtk/gtkeventcontrollerkey.c   | 244 ----------------------------
 gtk/gtkeventcontrollerkey.h   |  14 --
 gtk/gtkfilechooserentry.c     |  13 +-
 gtk/gtkmodelbutton.c          |  21 ++-
 gtk/gtkpopovermenu.c          |  14 +-
 gtk/gtkspinbutton.c           |  15 +-
 gtk/gtktext.c                 |  77 +++++----
 gtk/gtktextview.c             | 100 ++++++------
 gtk/gtktreeview.c             |  29 ++--
 gtk/gtktreeviewcolumn.c       |   9 +-
 gtk/gtkwindow.c               |  54 ++++---
 gtk/meson.build               |   1 +
 testsuite/gtk/focus.c         |  73 +++++----
 19 files changed, 662 insertions(+), 461 deletions(-)
---
diff --git a/gtk/gtk.h b/gtk/gtk.h
index 07236ce5f1..46c7aabacc 100644
--- a/gtk/gtk.h
+++ b/gtk/gtk.h
@@ -101,6 +101,7 @@
 #include <gtk/gtkentrycompletion.h>
 #include <gtk/gtkenums.h>
 #include <gtk/gtkeventcontroller.h>
+#include <gtk/gtkeventcontrollerfocus.h>
 #include <gtk/gtkeventcontrollerkey.h>
 #include <gtk/gtkeventcontrollerlegacy.h>
 #include <gtk/gtkeventcontrollermotion.h>
diff --git a/gtk/gtkcalendar.c b/gtk/gtkcalendar.c
index 95e5b19aaa..09eca8b12c 100644
--- a/gtk/gtkcalendar.c
+++ b/gtk/gtkcalendar.c
@@ -109,6 +109,7 @@
 #include "gtkgesturedrag.h"
 #include "gtkeventcontrollerscroll.h"
 #include "gtkeventcontrollerkey.h"
+#include "gtkeventcontrollerfocus.h"
 #include "gtkdragsource.h"
 #include "gtknative.h"
 #include "gtkicontheme.h"
@@ -280,8 +281,7 @@ static gboolean gtk_calendar_key_controller_key_pressed (GtkEventControllerKey *
                                                          guint                  keycode,
                                                          GdkModifierType        state,
                                                          GtkWidget             *widget);
-static void     gtk_calendar_key_controller_focus       (GtkEventController    *controller,
-                                                         GtkCrossingDirection   direction,
+static void     gtk_calendar_focus_controller_focus     (GtkEventController    *controller,
                                                          GtkWidget             *widget);
 static void     gtk_calendar_state_flags_changed  (GtkWidget     *widget,
                                                    GtkStateFlags  previous_state);
@@ -593,8 +593,13 @@ gtk_calendar_init (GtkCalendar *calendar)
   g_signal_connect (controller, "key-pressed",
                     G_CALLBACK (gtk_calendar_key_controller_key_pressed),
                     calendar);
-  g_signal_connect (controller, "focus-change",
-                    G_CALLBACK (gtk_calendar_key_controller_focus),
+  gtk_widget_add_controller (GTK_WIDGET (calendar), controller);
+  controller = gtk_event_controller_focus_new ();
+  g_signal_connect (controller, "enter",
+                    G_CALLBACK (gtk_calendar_focus_controller_focus),
+                    calendar);
+  g_signal_connect (controller, "leave",
+                    G_CALLBACK (gtk_calendar_focus_controller_focus),
                     calendar);
   gtk_widget_add_controller (GTK_WIDGET (calendar), controller);
 
@@ -1398,9 +1403,8 @@ gtk_calendar_key_controller_key_pressed (GtkEventControllerKey *controller,
 }
 
 static void
-gtk_calendar_key_controller_focus (GtkEventController     *controller,
-                                   GtkCrossingDirection    direction,
-                                   GtkWidget              *widget)
+gtk_calendar_focus_controller_focus (GtkEventController     *controller,
+                                     GtkWidget              *widget)
 {
   GtkCalendar *calendar = GTK_CALENDAR (widget);
   GtkCalendarPrivate *priv = gtk_calendar_get_instance_private (calendar);
diff --git a/gtk/gtkentrycompletion.c b/gtk/gtkentrycompletion.c
index 4167b7e104..a3c642edc8 100644
--- a/gtk/gtkentrycompletion.c
+++ b/gtk/gtkentrycompletion.c
@@ -83,6 +83,7 @@
 #include "gtkentry.h"
 #include "gtkmain.h"
 #include "gtkmarshalers.h"
+#include "gtkeventcontrollerfocus.h"
 #include "gtkeventcontrollerkey.h"
 #include "gtkgestureclick.h"
 
@@ -2308,11 +2309,9 @@ accept_completion_callback (GtkEntryCompletion *completion)
 }
 
 static void
-text_focus_change (GtkEntryCompletion   *completion,
-                   GtkCrossingDirection  direction) 
+text_focus_out (GtkEntryCompletion *completion)
 {
-  if (direction == GTK_CROSSING_OUT &&
-      !gtk_widget_get_mapped (completion->priv->popup_window))
+  if (!gtk_widget_get_mapped (completion->priv->popup_window))
     accept_completion_callback (completion);
 }
 
@@ -2349,7 +2348,9 @@ connect_completion_signals (GtkEntryCompletion *completion)
   controller = priv->entry_key_controller = gtk_event_controller_key_new ();
   g_signal_connect (controller, "key-pressed",
                     G_CALLBACK (gtk_entry_completion_key_pressed), completion);
-  g_signal_connect_swapped (controller, "focus-change", G_CALLBACK (text_focus_change), completion);
+  gtk_widget_add_controller (GTK_WIDGET (text), controller);
+  controller = priv->entry_focus_controller = gtk_event_controller_focus_new ();
+  g_signal_connect_swapped (controller, "leave", G_CALLBACK (text_focus_out), completion);
   gtk_widget_add_controller (GTK_WIDGET (text), controller);
 
   completion->priv->changed_id =
@@ -2397,6 +2398,7 @@ disconnect_completion_signals (GtkEntryCompletion *completion)
   GtkText *text = gtk_entry_get_text_widget (GTK_ENTRY (completion->priv->entry));
 
   gtk_widget_remove_controller (GTK_WIDGET (text), completion->priv->entry_key_controller);
+  gtk_widget_remove_controller (GTK_WIDGET (text), completion->priv->entry_focus_controller);
 
   if (completion->priv->changed_id > 0 &&
       g_signal_handler_is_connected (text, completion->priv->changed_id))
diff --git a/gtk/gtkentryprivate.h b/gtk/gtkentryprivate.h
index 2f7d8dc695..de765d9b49 100644
--- a/gtk/gtkentryprivate.h
+++ b/gtk/gtkentryprivate.h
@@ -78,6 +78,7 @@ struct _GtkEntryCompletionPrivate
   gchar *case_normalized_key;
 
   GtkEventController *entry_key_controller;
+  GtkEventController *entry_focus_controller;
 
   /* only used by GtkEntry when attached: */
   GtkWidget *popup_window;
diff --git a/gtk/gtkeventcontrollerfocus.c b/gtk/gtkeventcontrollerfocus.c
new file mode 100644
index 0000000000..24d2a4eca0
--- /dev/null
+++ b/gtk/gtkeventcontrollerfocus.c
@@ -0,0 +1,361 @@
+/* GTK - The GIMP Toolkit
+ * Copyright (C) 2020, Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author(s): Matthias Clasen <mclasen redhat com>
+ */
+
+/**
+ * SECTION:gtkeventcontrollerfocus
+ * @Short_description: Event controller for focus
+ * @Title: GtkEventControllerFocus
+ * @See_also: #GtkEventController
+ *
+ * #GtkEventControllerFocus is an event controller meant for situations
+ * where you need to know where the focusboard focus is.
+ **/
+
+#include "config.h"
+
+#include "gtkintl.h"
+#include "gtkmarshalers.h"
+#include "gtkprivate.h"
+#include "gtkwidgetprivate.h"
+#include "gtkeventcontrollerprivate.h"
+#include "gtkeventcontrollerfocus.h"
+#include "gtkbindings.h"
+#include "gtkenums.h"
+#include "gtkmain.h"
+#include "gtktypebuiltins.h"
+
+#include <gdk/gdk.h>
+
+struct _GtkEventControllerFocus
+{
+  GtkEventController parent_instance;
+
+  const GtkCrossingData *current_crossing;
+
+  guint is_focus       : 1;
+  guint contains_focus : 1;
+};
+
+struct _GtkEventControllerFocusClass
+{
+  GtkEventControllerClass parent_class;
+};
+
+enum {
+  ENTER,
+  LEAVE,
+  N_SIGNALS
+};
+
+static guint signals[N_SIGNALS] = { 0 };
+
+enum {
+  PROP_IS_FOCUS = 1,
+  PROP_CONTAINS_FOCUS,
+  NUM_PROPERTIES
+};
+
+static GParamSpec *props[NUM_PROPERTIES] = { NULL, };
+
+G_DEFINE_TYPE (GtkEventControllerFocus, gtk_event_controller_focus,
+               GTK_TYPE_EVENT_CONTROLLER)
+
+static void
+gtk_event_controller_focus_finalize (GObject *object)
+{
+  //GtkEventControllerFocus *focus = GTK_EVENT_CONTROLLER_FOCUS (object);
+
+  G_OBJECT_CLASS (gtk_event_controller_focus_parent_class)->finalize (object);
+}
+
+static void
+update_focus (GtkEventController    *controller,
+              const GtkCrossingData *crossing)
+{
+  GtkEventControllerFocus *focus = GTK_EVENT_CONTROLLER_FOCUS (controller);
+  GtkWidget *widget = gtk_event_controller_get_widget (controller);
+  gboolean is_focus = FALSE;
+  gboolean contains_focus = FALSE;
+  gboolean enter = FALSE;
+  gboolean leave = FALSE;
+
+  if (crossing->direction == GTK_CROSSING_IN)
+    {
+      if (crossing->new_target == widget)
+        is_focus = TRUE;
+      if (crossing->new_target != NULL)
+        contains_focus = TRUE;
+    }
+
+  if (focus->contains_focus != contains_focus)
+    {
+      if (contains_focus)
+        enter = TRUE;
+      else
+        leave = TRUE;
+    }
+
+  if (enter)
+    g_signal_emit (controller, signals[ENTER], 0);
+
+  g_object_freeze_notify (G_OBJECT (focus));
+  if (focus->is_focus != is_focus)
+    {
+      focus->is_focus = is_focus;
+      g_object_notify (G_OBJECT (focus), "is-focus");
+    }
+
+  if (focus->contains_focus != contains_focus)
+    {
+      focus->contains_focus = contains_focus;
+      g_object_notify (G_OBJECT (focus), "contains-focus");
+    }
+  g_object_thaw_notify (G_OBJECT (focus));
+
+  if (leave)
+    g_signal_emit (controller, signals[LEAVE], 0);
+}
+
+static void
+gtk_event_controller_focus_handle_crossing (GtkEventController    *controller,
+                                            const GtkCrossingData *crossing,
+                                            double                 x,
+                                            double                 y)
+{
+  GtkEventControllerFocus *focus = GTK_EVENT_CONTROLLER_FOCUS (controller);
+
+  if (crossing->type != GTK_CROSSING_FOCUS)
+    return;
+
+  focus->current_crossing = crossing;
+
+  update_focus (controller, crossing);
+
+  focus->current_crossing = NULL;
+}
+
+static void
+gtk_event_controller_focus_get_property (GObject    *object,
+                                         guint       prop_id,
+                                         GValue     *value,
+                                         GParamSpec *pspec)
+{
+  GtkEventControllerFocus *controller = GTK_EVENT_CONTROLLER_FOCUS (object);
+
+  switch (prop_id)
+    {
+    case PROP_IS_FOCUS:
+      g_value_set_boolean (value, controller->is_focus);
+      break;
+
+    case PROP_CONTAINS_FOCUS:
+      g_value_set_boolean (value, controller->contains_focus);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+gtk_event_controller_focus_class_init (GtkEventControllerFocusClass *klass)
+{
+  GtkEventControllerClass *controller_class = GTK_EVENT_CONTROLLER_CLASS (klass);
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = gtk_event_controller_focus_finalize;
+  object_class->get_property = gtk_event_controller_focus_get_property;
+  controller_class->handle_crossing = gtk_event_controller_focus_handle_crossing;
+
+  /**
+   * GtkEventControllerFocus:is-focus:
+   *
+   * Whether focus is in the controllers widget itself,
+   * opposed to in a descendent widget. See also
+   * #GtkEventControllerFocus:contains-focus.
+   *
+   * When handling focus events, this property is updated
+   * before #GtkEventControllerFocus::focus-in or
+   * #GtkEventControllerFocus::focus-out are emitted.
+   */
+  props[PROP_IS_FOCUS] =
+      g_param_spec_boolean ("is-focus",
+                            P_("Is Focus"),
+                            P_("Whether the focus is in the controllers widget"),
+                            FALSE,
+                            G_PARAM_READABLE);
+
+  /**
+   * GtkEventControllerFocus:contains-focus:
+   *
+   * Whether focus is contain in the controllers widget. See
+   * See #GtkEventControllerFocus:is-focus for whether the focus is in the widget itself
+   * or inside a descendent.
+   *
+   * When handling focus events, this property is updated
+   * before #GtkEventControllerFocus::focus-in or
+   * #GtkEventControllerFocus::focus-out are emitted.
+   */
+  props[PROP_CONTAINS_FOCUS] =
+      g_param_spec_boolean ("contains-focus",
+                            P_("Contains Focus"),
+                            P_("Whether the focus is in a descendant of the controllers widget"),
+                            FALSE,
+                            G_PARAM_READABLE);
+
+  g_object_class_install_properties (object_class, NUM_PROPERTIES, props);
+
+  /**
+   * GtkEventControllerFocus::enter:
+   * @controller: the object which received the signal
+   *
+   * This signal is emitted whenever the focus enters into the
+   * widget or one of its descendents.
+   */
+  signals[ENTER] =
+    g_signal_new (I_("enter"),
+                  GTK_TYPE_EVENT_CONTROLLER_FOCUS,
+                  G_SIGNAL_RUN_LAST,
+                  0, NULL, NULL,
+                  NULL,
+                  G_TYPE_NONE, 0);
+
+  /**
+   * GtkEventControllerFocus::leave:
+   * @controller: the object which received the signal
+   *
+   * This signal is emitted whenever the focus leaves from 
+   * the widget or one of its descendents.
+   */
+  signals[LEAVE] =
+    g_signal_new (I_("leave"),
+                  GTK_TYPE_EVENT_CONTROLLER_FOCUS,
+                  G_SIGNAL_RUN_LAST,
+                  0, NULL, NULL,
+                  NULL,
+                  G_TYPE_NONE, 0);
+}
+
+static void
+gtk_event_controller_focus_init (GtkEventControllerFocus *controller)
+{
+}
+
+/**
+ * gtk_event_controller_focus_new:
+ *
+ * Creates a new event controller that will handle focus events.
+ *
+ * Returns: a new #GtkEventControllerFocus
+ **/
+GtkEventController *
+gtk_event_controller_focus_new (void)
+{
+  return g_object_new (GTK_TYPE_EVENT_CONTROLLER_FOCUS, NULL);
+}
+
+/**
+ * gtk_event_controller_focus_get_focus_origin:
+ * @controller: a #GtkEventControllerFocus
+ *
+ * Returns the widget that was holding focus before.
+ *
+ * This function can only be used in handlers for the
+ * #GtkEventControllerFocus::focus-in and #GtkEventControllerFocus::focus-out signals.
+ *
+ * Returns: (transfer none): the previous focus
+ */
+GtkWidget *
+gtk_event_controller_focus_get_focus_origin (GtkEventControllerFocus *controller)
+{
+  g_return_val_if_fail (GTK_IS_EVENT_CONTROLLER_FOCUS (controller), NULL);
+  g_return_val_if_fail (controller->current_crossing != NULL, NULL);
+
+  return controller->current_crossing->old_target;
+}
+
+/**
+ * gtk_event_controller_focus_get_focus_target:
+ * @controller: a #GtkEventControllerFocus
+ *
+ * Returns the widget that will be holding focus afterwards.
+ *
+ * This function can only be used in handlers for the
+ * #GtkEventControllerFocus::focus-in and #GtkEventControllerFocus::focus-out signals.
+ *
+ * Returns: (transfer none): the next focus
+ */
+GtkWidget *
+gtk_event_controller_focus_get_focus_target (GtkEventControllerFocus *controller)
+{
+  g_return_val_if_fail (GTK_IS_EVENT_CONTROLLER_FOCUS (controller), NULL);
+  g_return_val_if_fail (controller->current_crossing != NULL, NULL);
+
+  return controller->current_crossing->new_target;
+}
+
+GtkWidget *
+gtk_event_controller_focus_get_old_focus_child (GtkEventControllerFocus *controller)
+{
+  g_return_val_if_fail (GTK_IS_EVENT_CONTROLLER_FOCUS (controller), NULL);
+  g_return_val_if_fail (controller->current_crossing != NULL, NULL);
+
+  return controller->current_crossing->old_descendent;
+}
+
+GtkWidget *
+gtk_event_controller_focus_get_new_focus_child (GtkEventControllerFocus *controller)
+{
+  g_return_val_if_fail (GTK_IS_EVENT_CONTROLLER_FOCUS (controller), NULL);
+  g_return_val_if_fail (controller->current_crossing != NULL, NULL);
+
+  return controller->current_crossing->new_descendent;
+}
+
+/**
+ * gtk_event_controller_focus_contains_focus:
+ * @self: a #GtkEventControllerFocus
+ *
+ * Returns the value of the GtkEventControllerFocus:contains-focus property.
+ *
+ * Returns: %TRUE if focus is within @self or one of its children
+ */
+gboolean
+gtk_event_controller_focus_contains_focus (GtkEventControllerFocus *self)
+{
+  g_return_val_if_fail (GTK_IS_EVENT_CONTROLLER_FOCUS (self), FALSE);
+
+  return self->contains_focus;
+}
+
+/**
+ * gtk_event_controller_focus_is_focus:
+ * @self: a #GtkEventControllerFocus
+ *
+ * Returns the value of the GtkEventControllerFocus:is-focus property.
+ *
+ * Returns: %TRUE if focus is within @self but not one of its children
+ */
+gboolean
+gtk_event_controller_focus_is_focus (GtkEventControllerFocus *self)
+{
+  g_return_val_if_fail (GTK_IS_EVENT_CONTROLLER_FOCUS (self), FALSE);
+
+  return self->is_focus;
+}
diff --git a/gtk/gtkeventcontrollerfocus.h b/gtk/gtkeventcontrollerfocus.h
new file mode 100644
index 0000000000..3a4ae1b7fe
--- /dev/null
+++ b/gtk/gtkeventcontrollerfocus.h
@@ -0,0 +1,66 @@
+/* GTK - The GIMP Toolkit
+ * Copyright (C) 2020, Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author(s): Matthias Clasen <mclasen redhat com>
+ */
+
+#ifndef __GTK_EVENT_CONTROLLER_FOCUS_H__
+#define __GTK_EVENT_CONTROLLER_FOCUS_H__
+
+#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
+#error "Only <gtk/gtk.h> can be included directly."
+#endif
+
+#include <gdk/gdk.h>
+#include <gtk/gtkeventcontroller.h>
+#include <gtk/gtkimcontext.h>
+
+G_BEGIN_DECLS
+
+#define GTK_TYPE_EVENT_CONTROLLER_FOCUS         (gtk_event_controller_focus_get_type ())
+#define GTK_EVENT_CONTROLLER_FOCUS(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), 
GTK_TYPE_EVENT_CONTROLLER_FOCUS, GtkEventControllerFocus))
+#define GTK_EVENT_CONTROLLER_FOCUS_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST ((k), 
GTK_TYPE_EVENT_CONTROLLER_FOCUS, GtkEventControllerFocusClass))
+#define GTK_IS_EVENT_CONTROLLER_FOCUS(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), 
GTK_TYPE_EVENT_CONTROLLER_FOCUS))
+#define GTK_IS_EVENT_CONTROLLER_FOCUS_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), 
GTK_TYPE_EVENT_CONTROLLER_FOCUS))
+#define GTK_EVENT_CONTROLLER_FOCUS_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), 
GTK_TYPE_EVENT_CONTROLLER_FOCUS, GtkEventControllerFocusClass))
+
+typedef struct _GtkEventControllerFocus GtkEventControllerFocus;
+typedef struct _GtkEventControllerFocusClass GtkEventControllerFocusClass;
+
+GDK_AVAILABLE_IN_ALL
+GType               gtk_event_controller_focus_get_type  (void) G_GNUC_CONST;
+
+GDK_AVAILABLE_IN_ALL
+GtkEventController *gtk_event_controller_focus_new (void);
+
+GDK_AVAILABLE_IN_ALL
+GtkWidget *         gtk_event_controller_focus_get_focus_origin   (GtkEventControllerFocus  *controller);
+GDK_AVAILABLE_IN_ALL
+GtkWidget *         gtk_event_controller_focus_get_focus_target   (GtkEventControllerFocus  *controller);
+GDK_AVAILABLE_IN_ALL
+GtkWidget *         gtk_event_controller_focus_get_old_focus_child (GtkEventControllerFocus  *controller);
+GDK_AVAILABLE_IN_ALL
+GtkWidget *         gtk_event_controller_focus_get_new_focus_child (GtkEventControllerFocus  *controller);
+
+GDK_AVAILABLE_IN_ALL
+gboolean            gtk_event_controller_focus_contains_focus     (GtkEventControllerFocus  *self);
+GDK_AVAILABLE_IN_ALL
+gboolean            gtk_event_controller_focus_is_focus           (GtkEventControllerFocus  *self);
+
+
+G_END_DECLS
+
+#endif /* __GTK_EVENT_CONTROLLER_FOCUS_H__ */
diff --git a/gtk/gtkeventcontrollerkey.c b/gtk/gtkeventcontrollerkey.c
index 14a79e7b0c..79103f407c 100644
--- a/gtk/gtkeventcontrollerkey.c
+++ b/gtk/gtkeventcontrollerkey.c
@@ -51,7 +51,6 @@ struct _GtkEventControllerKey
   GdkModifierType state;
 
   GdkEvent *current_event;
-  const GtkCrossingData *current_crossing;
 
   guint is_focus       : 1;
   guint contains_focus : 1;
@@ -67,20 +66,11 @@ enum {
   KEY_RELEASED,
   MODIFIERS,
   IM_UPDATE,
-  FOCUS_CHANGE,
   N_SIGNALS
 };
 
 static guint signals[N_SIGNALS] = { 0 };
 
-enum {
-  PROP_IS_FOCUS = 1,
-  PROP_CONTAINS_FOCUS,
-  NUM_PROPERTIES
-};
-
-static GParamSpec *props[NUM_PROPERTIES] = { NULL, };
-
 G_DEFINE_TYPE (GtkEventControllerKey, gtk_event_controller_key,
                GTK_TYPE_EVENT_CONTROLLER)
 
@@ -155,87 +145,6 @@ gtk_event_controller_key_handle_event (GtkEventController *controller,
   return handled;
 }
 
-static void
-update_focus (GtkEventController    *controller,
-              const GtkCrossingData *crossing)
-{
-  GtkEventControllerKey *key = GTK_EVENT_CONTROLLER_KEY (controller);
-  GtkWidget *widget = gtk_event_controller_get_widget (controller);
-  gboolean is_focus = FALSE;
-  gboolean contains_focus = FALSE;
-
-  if (crossing->direction == GTK_CROSSING_IN)
-    {
-      if (crossing->new_target == widget)
-        is_focus = TRUE;
-      if (crossing->new_target != NULL)
-        contains_focus = TRUE;
-    }
-
-  g_object_freeze_notify (G_OBJECT (key));
-  if (key->is_focus != is_focus)
-    {
-      key->is_focus = is_focus;
-      g_object_notify (G_OBJECT (key), "is-focus");
-      if (key->im_context)
-        {
-          if (is_focus)
-            gtk_im_context_focus_in (key->im_context);
-          else
-            gtk_im_context_focus_out (key->im_context);
-        }
-    }
-  if (key->contains_focus != contains_focus)
-    {
-      key->contains_focus = contains_focus;
-      g_object_notify (G_OBJECT (key), "contains-focus");
-    }
-  g_object_thaw_notify (G_OBJECT (key));
-}
-
-static void
-gtk_event_controller_key_handle_crossing (GtkEventController    *controller,
-                                          const GtkCrossingData *crossing,
-                                          double                 x,
-                                          double                 y)
-{
-  GtkEventControllerKey *key = GTK_EVENT_CONTROLLER_KEY (controller);
-
-  if (crossing->type != GTK_CROSSING_FOCUS)
-    return;
-
-  key->current_crossing = crossing;
-
-  update_focus (controller, crossing);
-
-  g_signal_emit (controller, signals[FOCUS_CHANGE], 0, crossing->direction);
-
-  key->current_crossing = NULL;
-}
-
-static void
-gtk_event_controller_key_get_property (GObject    *object,
-                                       guint       prop_id,
-                                       GValue     *value,
-                                       GParamSpec *pspec)
-{
-  GtkEventControllerKey *controller = GTK_EVENT_CONTROLLER_KEY (object);
-
-  switch (prop_id)
-    {
-    case PROP_IS_FOCUS:
-      g_value_set_boolean (value, controller->is_focus);
-      break;
-
-    case PROP_CONTAINS_FOCUS:
-      g_value_set_boolean (value, controller->contains_focus);
-      break;
-
-    default:
-      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
-    }
-}
-
 static void
 gtk_event_controller_key_class_init (GtkEventControllerKeyClass *klass)
 {
@@ -243,47 +152,7 @@ gtk_event_controller_key_class_init (GtkEventControllerKeyClass *klass)
   GObjectClass *object_class = G_OBJECT_CLASS (klass);
 
   object_class->finalize = gtk_event_controller_key_finalize;
-  object_class->get_property = gtk_event_controller_key_get_property;
   controller_class->handle_event = gtk_event_controller_key_handle_event;
-  controller_class->handle_crossing = gtk_event_controller_key_handle_crossing;
-
-  /**
-   * GtkEventControllerKey:is-focus:
-   *
-   * Whether focus is in the controllers widget itself,
-   * opposed to in a descendent widget. See also
-   * #GtkEventControllerKey:contains-focus.
-   *
-   * When handling focus events, this property is updated
-   * before #GtkEventControllerKey::focus-in or
-   * #GtkEventControllerKey::focus-out are emitted.
-   */
-  props[PROP_IS_FOCUS] =
-      g_param_spec_boolean ("is-focus",
-                            P_("Is Focus"),
-                            P_("Whether the focus is in the controllers widget"),
-                            FALSE,
-                            G_PARAM_READABLE);
-
-  /**
-   * GtkEventControllerKey:contains-focus:
-   *
-   * Whether focus is contain in the controllers widget. See
-   * See #GtkEventControllerKey:is-focus for whether the focus is in the widget itself
-   * or inside a descendent.
-   *
-   * When handling focus events, this property is updated
-   * before #GtkEventControllerKey::focus-in or
-   * #GtkEventControllerKey::focus-out are emitted.
-   */
-  props[PROP_CONTAINS_FOCUS] =
-      g_param_spec_boolean ("contains-focus",
-                            P_("Contains Focus"),
-                            P_("Whether the focus is in a descendant of the controllers widget"),
-                            FALSE,
-                            G_PARAM_READABLE);
-
-  g_object_class_install_properties (object_class, NUM_PROPERTIES, props);
 
   /**
    * GtkEventControllerKey::key-pressed:
@@ -365,29 +234,6 @@ gtk_event_controller_key_class_init (GtkEventControllerKeyClass *klass)
                   0, NULL, NULL,
                   NULL,
                   G_TYPE_NONE, 0);
-
-  /**
-   * GtkEventControllerKey::focus-change:
-   * @controller: the object which received the signal
-   * @direction: the direction of this crossing event
-   *
-   * This signal is emitted whenever the focus change from or
-   * to a widget that is a descendant of the widget to which
-   * @controller is attached.
-   *
-   * Handlers for this signal can use
-   * gtk_event_controller_key_get_focus_origin() and
-   * gtk_event_controller_key_get_focus_target() to find
-   * the old and new focus locations.
-   */
-  signals[FOCUS_CHANGE] =
-    g_signal_new (I_("focus-change"),
-                  GTK_TYPE_EVENT_CONTROLLER_KEY,
-                  G_SIGNAL_RUN_LAST,
-                  0, NULL, NULL,
-                  NULL,
-                  G_TYPE_NONE, 1,
-                  GTK_TYPE_CROSSING_DIRECTION);
 }
 
 static void
@@ -507,93 +353,3 @@ gtk_event_controller_key_get_group (GtkEventControllerKey *controller)
 
   return gdk_key_event_get_group (controller->current_event);
 }
-
-/**
- * gtk_event_controller_key_get_focus_origin:
- * @controller: a #GtkEventControllerKey
- *
- * Returns the widget that was holding focus before.
- *
- * This function can only be used in handlers for the
- * #GtkEventControllerKey::focus-changed signal.
- *
- * Returns: (transfer none): the previous focus
- */
-GtkWidget *
-gtk_event_controller_key_get_focus_origin (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_target;
-}
-
-/**
- * gtk_event_controller_key_get_focus_target:
- * @controller: a #GtkEventControllerKey
- *
- * Returns the widget that will be holding focus afterwards.
- *
- * This function can only be used in handlers for the
- * #GtkEventControllerKey::focus-changed signal.
- *
- * Returns: (transfer none): the next focus
- */
-GtkWidget *
-gtk_event_controller_key_get_focus_target (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_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
- *
- * Returns the value of the GtkEventControllerKey:contains-focus property.
- *
- * Returns: %TRUE if focus is within @self or one of its children
- */
-gboolean
-gtk_event_controller_key_contains_focus (GtkEventControllerKey *self)
-{
-  g_return_val_if_fail (GTK_IS_EVENT_CONTROLLER_KEY (self), FALSE);
-
-  return self->contains_focus;
-}
-
-/**
- * gtk_event_controller_key_is_focus:
- * @self: a #GtkEventControllerKey
- *
- * Returns the value of the GtkEventControllerKey:is-focus property.
- *
- * Returns: %TRUE if focus is within @self but not one of its children
- */
-gboolean
-gtk_event_controller_key_is_focus (GtkEventControllerKey *self)
-{
-  g_return_val_if_fail (GTK_IS_EVENT_CONTROLLER_KEY (self), FALSE);
-
-  return self->is_focus;
-}
diff --git a/gtk/gtkeventcontrollerkey.h b/gtk/gtkeventcontrollerkey.h
index 4aa22777d6..b24df105d0 100644
--- a/gtk/gtkeventcontrollerkey.h
+++ b/gtk/gtkeventcontrollerkey.h
@@ -58,20 +58,6 @@ gboolean            gtk_event_controller_key_forward        (GtkEventControllerK
 GDK_AVAILABLE_IN_ALL
 guint               gtk_event_controller_key_get_group      (GtkEventControllerKey *controller);
 
-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);
-GDK_AVAILABLE_IN_ALL
-gboolean            gtk_event_controller_key_is_focus           (GtkEventControllerKey  *self);
-
 
 G_END_DECLS
 
diff --git a/gtk/gtkfilechooserentry.c b/gtk/gtkfilechooserentry.c
index 9123bddb91..09146f4963 100644
--- a/gtk/gtkfilechooserentry.c
+++ b/gtk/gtkfilechooserentry.c
@@ -33,6 +33,7 @@
 #include "gtkintl.h"
 #include "gtkmarshalers.h"
 #include "gtkfilefilterprivate.h"
+#include "gtkeventcontrollerfocus.h"
 
 typedef struct _GtkFileChooserEntryClass GtkFileChooserEntryClass;
 
@@ -259,12 +260,10 @@ match_func (GtkEntryCompletion *compl,
 }
 
 static void
-chooser_entry_focus_change (GtkEventController   *controller,
-                            GtkCrossingDirection  direction,
-                            GtkFileChooserEntry  *chooser_entry)
+chooser_entry_focus_out (GtkEventController   *controller,
+                         GtkFileChooserEntry  *chooser_entry)
 {
-  if (direction == GTK_CROSSING_OUT)
-    set_complete_on_load (chooser_entry, FALSE);
+  set_complete_on_load (chooser_entry, FALSE);
 }
 
 static void
@@ -311,8 +310,10 @@ _gtk_file_chooser_entry_init (GtkFileChooserEntry *chooser_entry)
                     "key-pressed",
                     G_CALLBACK (gtk_file_chooser_entry_tab_handler),
                     chooser_entry);
+  gtk_widget_add_controller (GTK_WIDGET (chooser_entry), controller);
+  controller = gtk_event_controller_focus_new ();
   g_signal_connect (controller,
-                   "focus-change", G_CALLBACK (chooser_entry_focus_change),
+                   "leave", G_CALLBACK (chooser_entry_focus_out),
                    chooser_entry);
   gtk_widget_add_controller (GTK_WIDGET (chooser_entry), controller);
 
diff --git a/gtk/gtkmodelbutton.c b/gtk/gtkmodelbutton.c
index d60b7b16c5..803006f95c 100644
--- a/gtk/gtkmodelbutton.c
+++ b/gtk/gtkmodelbutton.c
@@ -44,6 +44,7 @@
 #include "gtkactionable.h"
 #include "gtkeventcontrollermotion.h"
 #include "gtkeventcontrollerkey.h"
+#include "gtkeventcontrollerfocus.h"
 #include "gtknative.h"
 
 /**
@@ -1349,21 +1350,17 @@ motion_cb (GtkEventController *controller,
 }
 
 static void
-focus_change_cb (GtkEventController   *controller,
-                 GtkCrossingDirection  direction,
-                 gpointer              data)
+focus_in_cb (GtkEventController   *controller,
+             gpointer              data)
 {
   GtkWidget *target;
   GtkWidget *popover;
 
-  if (direction == GTK_CROSSING_IN)
-    {
-      target = gtk_event_controller_get_widget (controller);
-      popover = gtk_widget_get_ancestor (target, GTK_TYPE_POPOVER_MENU);
+  target = gtk_event_controller_get_widget (controller);
+  popover = gtk_widget_get_ancestor (target, GTK_TYPE_POPOVER_MENU);
 
-      if (popover)
-        gtk_popover_menu_set_active_item (GTK_POPOVER_MENU (popover), target);
-    }
+  if (popover)
+    gtk_popover_menu_set_active_item (GTK_POPOVER_MENU (popover), target);
 }
 
 static void
@@ -1390,8 +1387,8 @@ gtk_model_button_init (GtkModelButton *self)
   g_signal_connect (controller, "motion", G_CALLBACK (motion_cb), self);
   gtk_widget_add_controller (GTK_WIDGET (self), controller);
 
-  controller = gtk_event_controller_key_new ();
-  g_signal_connect (controller, "focus-change", G_CALLBACK (focus_change_cb), NULL);
+  controller = gtk_event_controller_focus_new ();
+  g_signal_connect (controller, "enter", G_CALLBACK (focus_in_cb), NULL);
   gtk_widget_add_controller (GTK_WIDGET (self), controller);
 
   gesture = gtk_gesture_click_new ();
diff --git a/gtk/gtkpopovermenu.c b/gtk/gtkpopovermenu.c
index c8f4afe6a4..50eccf17dc 100644
--- a/gtk/gtkpopovermenu.c
+++ b/gtk/gtkpopovermenu.c
@@ -28,7 +28,7 @@
 #include "gtkmenutrackerprivate.h"
 #include "gtkpopoverprivate.h"
 #include "gtkwidgetprivate.h"
-#include "gtkeventcontrollerkey.h"
+#include "gtkeventcontrollerfocus.h"
 #include "gtkeventcontrollermotion.h"
 #include "gtkmain.h"
 #include "gtktypebuiltins.h"
@@ -168,14 +168,12 @@ visible_submenu_changed (GObject        *object,
 }
 
 static void
-focus_change (GtkEventController   *controller,
-              GtkCrossingDirection  direction,
-              GtkPopoverMenu       *menu)
+focus_out (GtkEventController   *controller,
+           GtkPopoverMenu       *menu)
 {
   GtkWidget *new_focus = gtk_root_get_focus (gtk_widget_get_root (GTK_WIDGET (menu)));
 
-  if (direction == GTK_CROSSING_OUT &&
-      !gtk_event_controller_key_contains_focus (GTK_EVENT_CONTROLLER_KEY (controller)) &&
+  if (!gtk_event_controller_focus_contains_focus (GTK_EVENT_CONTROLLER_FOCUS (controller)) &&
       new_focus != NULL)
     {
       if (menu->parent_menu &&
@@ -220,8 +218,8 @@ gtk_popover_menu_init (GtkPopoverMenu *popover)
 
   gtk_widget_add_css_class (GTK_WIDGET (popover), "menu");
 
-  controller = gtk_event_controller_key_new ();
-  g_signal_connect (controller, "focus-change", G_CALLBACK (focus_change), popover);
+  controller = gtk_event_controller_focus_new ();
+  g_signal_connect (controller, "leave", G_CALLBACK (focus_out), popover);
   gtk_widget_add_controller (GTK_WIDGET (popover), controller);
 
   controller = gtk_event_controller_motion_new ();
diff --git a/gtk/gtkspinbutton.c b/gtk/gtkspinbutton.c
index ec1d3a0da0..d0147ab297 100644
--- a/gtk/gtkspinbutton.c
+++ b/gtk/gtkspinbutton.c
@@ -39,6 +39,7 @@
 #include "gtkimage.h"
 #include "gtktext.h"
 #include "gtkeventcontrollerkey.h"
+#include "gtkeventcontrollerfocus.h"
 #include "gtkeventcontrollermotion.h"
 #include "gtkeventcontrollerscroll.h"
 #include "gtkgestureclick.h"
@@ -920,14 +921,12 @@ key_controller_key_released (GtkEventControllerKey *key,
 }
 
 static void
-key_controller_focus_change (GtkEventController   *controller,
-                             GtkCrossingDirection  direction,
-                             GtkSpinButton        *spin_button)
+key_controller_focus_out (GtkEventController   *controller,
+                          GtkSpinButton        *spin_button)
 {
   GtkSpinButtonPrivate *priv = gtk_spin_button_get_instance_private (spin_button);
 
-  if (direction == GTK_CROSSING_OUT &&
-      gtk_editable_get_editable (GTK_EDITABLE (priv->entry)))
+  if (gtk_editable_get_editable (GTK_EDITABLE (priv->entry)))
     gtk_spin_button_update (spin_button);
 }
 
@@ -1015,8 +1014,10 @@ gtk_spin_button_init (GtkSpinButton *spin_button)
   controller = gtk_event_controller_key_new ();
   g_signal_connect (controller, "key-released",
                     G_CALLBACK (key_controller_key_released), spin_button);
-  g_signal_connect (controller, "focus-change",
-                    G_CALLBACK (key_controller_focus_change), spin_button);
+  gtk_widget_add_controller (GTK_WIDGET (spin_button), controller);
+  controller = gtk_event_controller_focus_new ();
+  g_signal_connect (controller, "leave",
+                    G_CALLBACK (key_controller_focus_out), spin_button);
   gtk_widget_add_controller (GTK_WIDGET (spin_button), controller);
 }
 
diff --git a/gtk/gtktext.c b/gtk/gtktext.c
index 00c07d6d3f..17063ee477 100644
--- a/gtk/gtktext.c
+++ b/gtk/gtktext.c
@@ -34,6 +34,7 @@
 #include "gtkemojichooser.h"
 #include "gtkemojicompletion.h"
 #include "gtkentrybuffer.h"
+#include "gtkeventcontrollerfocus.h"
 #include "gtkeventcontrollerkey.h"
 #include "gtkeventcontrollermotion.h"
 #include "gtkgesturedrag.h"
@@ -323,8 +324,8 @@ static void   gtk_text_size_allocate        (GtkWidget        *widget,
                                              int               baseline);
 static void   gtk_text_snapshot             (GtkWidget        *widget,
                                              GtkSnapshot      *snapshot);
-static void   gtk_text_focus_change         (GtkWidget            *widget,
-                                             GtkCrossingDirection  direction);
+static void   gtk_text_focus_in             (GtkWidget            *widget);
+static void   gtk_text_focus_out            (GtkWidget            *widget);
 static gboolean gtk_text_grab_focus         (GtkWidget        *widget);
 static void   gtk_text_css_changed          (GtkWidget        *widget,
                                              GtkCssStyleChange *change);
@@ -1783,11 +1784,15 @@ gtk_text_init (GtkText *self)
                     G_CALLBACK (gtk_text_key_controller_key_pressed), self);
   g_signal_connect_swapped (priv->key_controller, "im-update",
                             G_CALLBACK (gtk_text_schedule_im_reset), self);
-  g_signal_connect_swapped (priv->key_controller, "focus-change",
-                            G_CALLBACK (gtk_text_focus_change), self);
   gtk_event_controller_key_set_im_context (GTK_EVENT_CONTROLLER_KEY (priv->key_controller),
                                            priv->im_context);
   gtk_widget_add_controller (GTK_WIDGET (self), priv->key_controller);
+  controller = gtk_event_controller_focus_new ();
+  g_signal_connect_swapped (controller, "enter",
+                            G_CALLBACK (gtk_text_focus_in), self);
+  g_signal_connect_swapped (controller, "leave",
+                            G_CALLBACK (gtk_text_focus_out), self);
+  gtk_widget_add_controller (GTK_WIDGET (self), controller);
 
   widget_node = gtk_widget_get_css_node (GTK_WIDGET (self));
   for (i = 0; i < 2; i++)
@@ -3052,53 +3057,55 @@ gtk_text_key_controller_key_pressed (GtkEventControllerKey *controller,
 }
 
 static void
-gtk_text_focus_change (GtkWidget            *widget,
-                       GtkCrossingDirection  direction)
+gtk_text_focus_in (GtkWidget *widget)
 {
   GtkText *self = GTK_TEXT (widget);
   GtkTextPrivate *priv = gtk_text_get_instance_private (self);
   GdkKeymap *keymap;
 
-  if (direction == GTK_CROSSING_IN)
-    {
-      gtk_widget_queue_draw (widget);
+  gtk_widget_queue_draw (widget);
 
-      keymap = gdk_display_get_keymap (gtk_widget_get_display (widget));
+  keymap = gdk_display_get_keymap (gtk_widget_get_display (widget));
 
-      if (priv->editable)
-        {
-          gtk_text_schedule_im_reset (self);
-          gtk_im_context_focus_in (priv->im_context);
-        }
+  if (priv->editable)
+    {
+      gtk_text_schedule_im_reset (self);
+      gtk_im_context_focus_in (priv->im_context);
+    }
 
-      g_signal_connect (keymap, "direction-changed",
-                        G_CALLBACK (keymap_direction_changed), self);
+  g_signal_connect (keymap, "direction-changed",
+                    G_CALLBACK (keymap_direction_changed), self);
 
-      gtk_text_reset_blink_time (self);
-      gtk_text_check_cursor_blink (self);
-    }
-  else
-    {
-      gtk_text_selection_bubble_popup_unset (self);
+  gtk_text_reset_blink_time (self);
+  gtk_text_check_cursor_blink (self);
+}
 
-      if (priv->text_handle)
-        _gtk_text_handle_set_mode (priv->text_handle,
-                                   GTK_TEXT_HANDLE_MODE_NONE);
+static void
+gtk_text_focus_out (GtkWidget *widget)
+{
+  GtkText *self = GTK_TEXT (widget);
+  GtkTextPrivate *priv = gtk_text_get_instance_private (self);
+  GdkKeymap *keymap;
 
-      gtk_widget_queue_draw (widget);
+  gtk_text_selection_bubble_popup_unset (self);
 
-      keymap = gdk_display_get_keymap (gtk_widget_get_display (widget));
+  if (priv->text_handle)
+    _gtk_text_handle_set_mode (priv->text_handle,
+                               GTK_TEXT_HANDLE_MODE_NONE);
 
-      if (priv->editable)
-        {
-          gtk_text_schedule_im_reset (self);
-          gtk_im_context_focus_out (priv->im_context);
-        }
+  gtk_widget_queue_draw (widget);
 
-      gtk_text_check_cursor_blink (self);
+  keymap = gdk_display_get_keymap (gtk_widget_get_display (widget));
 
-      g_signal_handlers_disconnect_by_func (keymap, keymap_direction_changed, self);
+  if (priv->editable)
+    {
+      gtk_text_schedule_im_reset (self);
+      gtk_im_context_focus_out (priv->im_context);
     }
+
+  gtk_text_check_cursor_blink (self);
+
+  g_signal_handlers_disconnect_by_func (keymap, keymap_direction_changed, self);
 }
 
 static gboolean
diff --git a/gtk/gtktextview.c b/gtk/gtktextview.c
index c161237831..1051264b00 100644
--- a/gtk/gtktextview.c
+++ b/gtk/gtktextview.c
@@ -404,8 +404,8 @@ static gboolean gtk_text_view_key_controller_key_pressed  (GtkEventControllerKey
 static void     gtk_text_view_key_controller_im_update    (GtkEventControllerKey *controller,
                                                            GtkTextView           *text_view);
 
-static void gtk_text_view_focus_change         (GtkWidget            *widget,
-                                                GtkCrossingDirection  direction);
+static void gtk_text_view_focus_in             (GtkWidget            *widget);
+static void gtk_text_view_focus_out            (GtkWidget            *widget);
 static void gtk_text_view_motion               (GtkEventController *controller,
                                                 double              x,
                                                 double              y,
@@ -1698,12 +1698,15 @@ gtk_text_view_init (GtkTextView *text_view)
   g_signal_connect (priv->key_controller, "im-update",
                     G_CALLBACK (gtk_text_view_key_controller_im_update),
                     widget);
-  g_signal_connect_swapped (priv->key_controller, "focus-change",
-                            G_CALLBACK (gtk_text_view_focus_change),
-                            widget);
   gtk_event_controller_key_set_im_context (GTK_EVENT_CONTROLLER_KEY (priv->key_controller),
                                            priv->im_context);
   gtk_widget_add_controller (widget, priv->key_controller);
+  controller = gtk_event_controller_focus_new ();
+  g_signal_connect_swapped (controller, "enter",
+                            G_CALLBACK (gtk_text_view_focus_in), widget);
+  g_signal_connect_swapped (controller, "leave",
+                            G_CALLBACK (gtk_text_view_focus_out), widget);
+  gtk_widget_add_controller (widget, controller);
 
   priv->selection_node = gtk_css_node_new ();
   gtk_css_node_set_name (priv->selection_node, g_quark_from_static_string ("selection"));
@@ -5310,65 +5313,66 @@ keymap_direction_changed (GdkKeymap   *keymap,
 }
 
 static void
-gtk_text_view_focus_change (GtkWidget             *widget,
-                            GtkCrossingDirection   direction)
+gtk_text_view_focus_in (GtkWidget *widget)
 {
   GtkTextView *text_view = GTK_TEXT_VIEW (widget);
   GtkTextViewPrivate *priv = text_view->priv;
 
-  if (direction == GTK_CROSSING_IN)
-    {
-      gtk_widget_queue_draw (widget);
+  gtk_widget_queue_draw (widget);
 
-      DV(g_print (G_STRLOC": focus_in\n"));
+  DV(g_print (G_STRLOC": focus_in\n"));
 
-      gtk_text_view_reset_blink_time (text_view);
+  gtk_text_view_reset_blink_time (text_view);
 
-      if (cursor_visible (text_view) && priv->layout)
-        {
-          gtk_text_layout_set_cursor_visible (priv->layout, TRUE);
-          gtk_text_view_check_cursor_blink (text_view);
-        }
+  if (cursor_visible (text_view) && priv->layout)
+    {
+      gtk_text_layout_set_cursor_visible (priv->layout, TRUE);
+      gtk_text_view_check_cursor_blink (text_view);
+    }
 
-      g_signal_connect (gdk_display_get_keymap (gtk_widget_get_display (widget)),
-                        "direction-changed",
-                        G_CALLBACK (keymap_direction_changed), text_view);
-      gtk_text_view_check_keymap_direction (text_view);
+  g_signal_connect (gdk_display_get_keymap (gtk_widget_get_display (widget)),
+                    "direction-changed",
+                    G_CALLBACK (keymap_direction_changed), text_view);
+  gtk_text_view_check_keymap_direction (text_view);
 
-      if (priv->editable)
-        {
-          priv->need_im_reset = TRUE;
-          gtk_im_context_focus_in (priv->im_context);
-        }
+  if (priv->editable)
+    {
+      priv->need_im_reset = TRUE;
+      gtk_im_context_focus_in (priv->im_context);
     }
-  else
-    { 
-      gtk_text_view_end_selection_drag (text_view);
+}
 
-      gtk_widget_queue_draw (widget);
+static void
+gtk_text_view_focus_out (GtkWidget *widget)
+{
+  GtkTextView *text_view = GTK_TEXT_VIEW (widget);
+  GtkTextViewPrivate *priv = text_view->priv;
 
-      DV(g_print (G_STRLOC": focus_out\n"));
+  gtk_text_view_end_selection_drag (text_view);
 
-      if (cursor_visible (text_view) && priv->layout)
-        {
-          gtk_text_view_check_cursor_blink (text_view);
-          gtk_text_layout_set_cursor_visible (priv->layout, FALSE);
-        }
+  gtk_widget_queue_draw (widget);
 
-      g_signal_handlers_disconnect_by_func (gdk_display_get_keymap (gtk_widget_get_display (widget)),
-                                            keymap_direction_changed,
-                                            text_view);
-      gtk_text_view_selection_bubble_popup_unset (text_view);
+  DV(g_print (G_STRLOC": focus_out\n"));
 
-      if (priv->text_handle)
-        _gtk_text_handle_set_mode (priv->text_handle,
-                                   GTK_TEXT_HANDLE_MODE_NONE);
+  if (cursor_visible (text_view) && priv->layout)
+    {
+      gtk_text_view_check_cursor_blink (text_view);
+      gtk_text_layout_set_cursor_visible (priv->layout, FALSE);
+    }
 
-      if (priv->editable)
-        {
-          priv->need_im_reset = TRUE;
-          gtk_im_context_focus_out (priv->im_context);
-        }
+  g_signal_handlers_disconnect_by_func (gdk_display_get_keymap (gtk_widget_get_display (widget)),
+                                        keymap_direction_changed,
+                                        text_view);
+  gtk_text_view_selection_bubble_popup_unset (text_view);
+
+  if (priv->text_handle)
+    _gtk_text_handle_set_mode (priv->text_handle,
+                               GTK_TEXT_HANDLE_MODE_NONE);
+
+  if (priv->editable)
+    {
+      priv->need_im_reset = TRUE;
+      gtk_im_context_focus_out (priv->im_context);
     }
 }
 
diff --git a/gtk/gtktreeview.c b/gtk/gtktreeview.c
index 7a0585c645..eccacdc91b 100644
--- a/gtk/gtktreeview.c
+++ b/gtk/gtktreeview.c
@@ -37,6 +37,7 @@
 #include "gtkentryprivate.h"
 #include "gtksearchentryprivate.h"
 #include "gtkeventcontrollerkey.h"
+#include "gtkeventcontrollerfocus.h"
 #include "gtkeventcontrollermotion.h"
 #include "gtkeventcontrollerscroll.h"
 #include "gtkframe.h"
@@ -667,8 +668,7 @@ static void     gtk_tree_view_key_controller_key_released (GtkEventControllerKey
                                                            guint                  keycode,
                                                            GdkModifierType        state,
                                                            GtkTreeView           *tree_view);
-static void     gtk_tree_view_key_controller_focus_change (GtkEventController     *key,
-                                                           GtkCrossingDirection    direction,
+static void     gtk_tree_view_focus_controller_focus_out  (GtkEventController   *focus,
                                                            GtkTreeView            *tree_view);
 
 static gint     gtk_tree_view_focus                (GtkWidget        *widget,
@@ -1832,8 +1832,11 @@ gtk_tree_view_init (GtkTreeView *tree_view)
                     G_CALLBACK (gtk_tree_view_key_controller_key_pressed), tree_view);
   g_signal_connect (controller, "key-released",
                     G_CALLBACK (gtk_tree_view_key_controller_key_released), tree_view);
-  g_signal_connect (controller, "focus-change",
-                    G_CALLBACK (gtk_tree_view_key_controller_focus_change), tree_view);
+  gtk_widget_add_controller (GTK_WIDGET (tree_view), controller);
+
+  controller = gtk_event_controller_focus_new ();
+  g_signal_connect (controller, "leave",
+                    G_CALLBACK (gtk_tree_view_focus_controller_focus_out), tree_view);
   gtk_widget_add_controller (GTK_WIDGET (tree_view), controller);
 }
 
@@ -5537,19 +5540,15 @@ gtk_tree_view_motion_controller_pointer (GtkEventControllerMotion *controller,
 }
 
 static void
-gtk_tree_view_key_controller_focus_change (GtkEventController   *key,
-                                           GtkCrossingDirection  direction,
-                                           GtkTreeView          *tree_view)
+gtk_tree_view_focus_controller_focus_out (GtkEventController   *focus,
+                                          GtkTreeView          *tree_view)
 {
-  if (direction == GTK_CROSSING_OUT)
-    {
-      gtk_widget_queue_draw (GTK_WIDGET (tree_view));
+  gtk_widget_queue_draw (GTK_WIDGET (tree_view));
 
-      if (tree_view->search_popover &&
-          !gtk_event_controller_key_contains_focus (GTK_EVENT_CONTROLLER_KEY (key)))
-        gtk_tree_view_search_popover_hide (tree_view->search_popover, tree_view,
-                                           gtk_get_current_event_device ());
-    }
+  if (tree_view->search_popover &&
+      !gtk_event_controller_focus_contains_focus (GTK_EVENT_CONTROLLER_FOCUS (focus)))
+    gtk_tree_view_search_popover_hide (tree_view->search_popover, tree_view,
+                                       gtk_get_current_event_device ());
 }
 
 /* Incremental Reflow
diff --git a/gtk/gtktreeviewcolumn.c b/gtk/gtktreeviewcolumn.c
index 790e611036..a70c893a49 100644
--- a/gtk/gtktreeviewcolumn.c
+++ b/gtk/gtktreeviewcolumn.c
@@ -35,6 +35,7 @@
 #include "gtktypebuiltins.h"
 #include "gtkwidgetprivate.h"
 #include "gtkgesturedrag.h"
+#include "gtkeventcontrollerfocus.h"
 #include "gtkeventcontrollerkey.h"
 
 #include "a11y/gtktreeviewaccessibleprivate.h"
@@ -831,11 +832,9 @@ gtk_tree_view_column_cell_layout_get_area (GtkCellLayout   *cell_layout)
 
 static void
 focus_in (GtkEventControllerKey *controller,
-          GtkCrossingDirection   direction,
           GtkTreeViewColumn     *column)
 {
-  if (direction == GTK_CROSSING_IN)
-    _gtk_tree_view_set_focus_column (GTK_TREE_VIEW (column->priv->tree_view), column);
+  _gtk_tree_view_set_focus_column (GTK_TREE_VIEW (column->priv->tree_view), column);
 }
 
 /* Button handling code
@@ -865,8 +864,8 @@ gtk_tree_view_column_create_button (GtkTreeViewColumn *tree_column)
   gtk_event_controller_set_propagation_phase (controller, GTK_PHASE_CAPTURE);
   gtk_widget_add_controller (priv->button, controller);
 
-  controller = gtk_event_controller_key_new ();
-  g_signal_connect (controller, "focus-change", G_CALLBACK (focus_in), tree_column);
+  controller = gtk_event_controller_focus_new ();
+  g_signal_connect (controller, "enter", G_CALLBACK (focus_in), tree_column);
   gtk_widget_add_controller (priv->button, controller);
 
   priv->frame = gtk_frame_new (NULL);
diff --git a/gtk/gtkwindow.c b/gtk/gtkwindow.c
index 01a2e9dfc3..6ae7b55ac4 100644
--- a/gtk/gtkwindow.c
+++ b/gtk/gtkwindow.c
@@ -41,6 +41,7 @@
 #include "gtkcssshadowvalueprivate.h"
 #include "gtkcssstylepropertyprivate.h"
 #include "gtkdragdest.h"
+#include "gtkeventcontrollerfocus.h"
 #include "gtkeventcontrollerkey.h"
 #include "gtkeventcontrollermotion.h"
 #include "gtkgesturedrag.h"
@@ -394,8 +395,8 @@ static void gtk_window_size_allocate      (GtkWidget         *widget,
                                            int                height,
                                            int                  baseline);
 static gboolean gtk_window_close_request  (GtkWindow         *window);
-static void gtk_window_focus_change       (GtkWidget            *widget,
-                                           GtkCrossingDirection  direction);
+static void gtk_window_focus_in           (GtkWidget         *widget);
+static void gtk_window_focus_out          (GtkWidget         *widget);
 static gboolean gtk_window_key_pressed    (GtkWidget         *widget,
                                            guint              keyval,
                                            guint              keycode,
@@ -1772,6 +1773,7 @@ gtk_window_init (GtkWindow *window)
   GtkCssNode *widget_node;
   GdkSeat *seat;
   GtkEventController *motion_controller;
+  GtkEventController *controller;
 #ifdef GDK_WINDOWING_X11
   GdkContentFormats *targets;
   GtkDropTarget *dest;
@@ -1846,13 +1848,17 @@ gtk_window_init (GtkWindow *window)
 
   priv->key_controller = gtk_event_controller_key_new ();
   gtk_event_controller_set_propagation_phase (priv->key_controller, GTK_PHASE_CAPTURE);
-  g_signal_connect_swapped (priv->key_controller, "focus-change",
-                            G_CALLBACK (gtk_window_focus_change), window);
   g_signal_connect_swapped (priv->key_controller, "key-pressed",
                             G_CALLBACK (gtk_window_key_pressed), window);
   g_signal_connect_swapped (priv->key_controller, "key-released",
                             G_CALLBACK (gtk_window_key_released), window);
   gtk_widget_add_controller (widget, priv->key_controller);
+  controller = gtk_event_controller_focus_new ();
+  g_signal_connect_swapped (controller, "enter",
+                            G_CALLBACK (gtk_window_focus_in), window);
+  g_signal_connect_swapped (controller, "leave",
+                            G_CALLBACK (gtk_window_focus_out), window);
+  gtk_widget_add_controller (widget, controller);
 
   /* Shared constraint solver */
   priv->constraint_solver = gtk_constraint_solver_new ();
@@ -6123,33 +6129,33 @@ gtk_window_has_mnemonic_modifier_pressed (GtkWindow *window)
 }
 
 static void
-gtk_window_focus_change (GtkWidget            *widget,
-                         GtkCrossingDirection  direction)
+gtk_window_focus_in (GtkWidget *widget)
 {
   GtkWindow *window = GTK_WINDOW (widget);
 
-  if (direction == GTK_CROSSING_IN)
+  /* It appears spurious focus in events can occur when
+   * the window is hidden. So we'll just check to see if
+   * the window is visible before actually handling the
+   * event
+   */
+  if (gtk_widget_get_visible (widget))
     {
-      /* It appears spurious focus in events can occur when
-       * the window is hidden. So we'll just check to see if
-       * the window is visible before actually handling the
-       * event
-       */
-      if (gtk_widget_get_visible (widget))
-        {
-          _gtk_window_set_is_active (window, TRUE);
+      _gtk_window_set_is_active (window, TRUE);
 
-          if (gtk_window_has_mnemonic_modifier_pressed (window))
-            _gtk_window_schedule_mnemonics_visible (window);
-        }
+      if (gtk_window_has_mnemonic_modifier_pressed (window))
+        _gtk_window_schedule_mnemonics_visible (window);
     }
-  else
-    {
-      _gtk_window_set_is_active (window, FALSE);
+}
 
-      /* set the mnemonic-visible property to false */
-      gtk_window_set_mnemonics_visible (window, FALSE);
-    }
+static void
+gtk_window_focus_out (GtkWidget *widget)
+{
+  GtkWindow *window = GTK_WINDOW (widget);
+
+  _gtk_window_set_is_active (window, FALSE);
+
+  /* set the mnemonic-visible property to false */
+  gtk_window_set_mnemonics_visible (window, FALSE);
 }
 
 static void
diff --git a/gtk/meson.build b/gtk/meson.build
index b57a7e33ad..a09b672130 100644
--- a/gtk/meson.build
+++ b/gtk/meson.build
@@ -218,6 +218,7 @@ gtk_public_sources = files([
   'gtkentrybuffer.c',
   'gtkentrycompletion.c',
   'gtkeventcontroller.c',
+  'gtkeventcontrollerfocus.c',
   'gtkeventcontrollerkey.c',
   'gtkeventcontrollerlegacy.c',
   'gtkeventcontrollermotion.c',
diff --git a/testsuite/gtk/focus.c b/testsuite/gtk/focus.c
index ac6d43fd7b..854236d57d 100644
--- a/testsuite/gtk/focus.c
+++ b/testsuite/gtk/focus.c
@@ -14,17 +14,27 @@ widget_name (GtkWidget *widget)
 }
 
 static void
-focus_change (GtkEventControllerKey *key,
-              GtkCrossingDirection   direction,
-              GString               *s)
+focus_in (GtkEventControllerFocus *key,
+          GString               *s)
 {
   GtkWidget *widget = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (key));
 
-  g_string_append_printf (s, "%s: focus-change %s is-focus: %d contains-focus: %d\n",
+  g_string_append_printf (s, "%s: focus-in is-focus: %d contains-focus: %d\n",
                           widget_name (widget),
-                          direction == GTK_CROSSING_IN ? "in" : "out",
-                          gtk_event_controller_key_is_focus (key),
-                          gtk_event_controller_key_contains_focus (key));
+                          gtk_event_controller_focus_is_focus (key),
+                          gtk_event_controller_focus_contains_focus (key));
+}
+
+static void
+focus_out (GtkEventControllerFocus *key,
+          GString               *s)
+{
+  GtkWidget *widget = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (key));
+
+  g_string_append_printf (s, "%s: focus-out is-focus: %d contains-focus: %d\n",
+                          widget_name (widget),
+                          gtk_event_controller_focus_is_focus (key),
+                          gtk_event_controller_focus_contains_focus (key));
 }
 
 static void
@@ -32,8 +42,9 @@ add_controller (GtkWidget *widget, GString *s)
 {
   GtkEventController *controller;
 
-  controller = gtk_event_controller_key_new ();
-  g_signal_connect (controller, "focus-change", G_CALLBACK (focus_change), s);
+  controller = gtk_event_controller_focus_new ();
+  g_signal_connect (controller, "enter", G_CALLBACK (focus_in), s);
+  g_signal_connect (controller, "leave", G_CALLBACK (focus_out), s);
   gtk_widget_add_controller (widget, controller);
 }
 
@@ -107,8 +118,8 @@ test_window_focus (void)
     g_print ("-> box\n%s\n", s->str);
 
   g_assert_cmpstr (s->str, ==,
-"window: focus-change in is-focus: 0 contains-focus: 1\n"
-"box: focus-change in is-focus: 1 contains-focus: 1\n"
+"window: focus-in is-focus: 0 contains-focus: 1\n"
+"box: focus-in is-focus: 1 contains-focus: 1\n"
                   );
   g_string_truncate (s, 0);
 
@@ -118,12 +129,12 @@ test_window_focus (void)
     g_print ("box -> entry1\n%s\n", s->str);
 
   g_assert_cmpstr (s->str, ==,
-"box: focus-change out is-focus: 0 contains-focus: 0\n"
-"window: focus-change out is-focus: 0 contains-focus: 0\n"
-"window: focus-change in is-focus: 0 contains-focus: 1\n"
-"box: focus-change in is-focus: 0 contains-focus: 1\n"
-"box1: focus-change in is-focus: 0 contains-focus: 1\n"
-"entry1: focus-change in is-focus: 1 contains-focus: 1\n"
+"box: focus-out is-focus: 0 contains-focus: 0\n"
+"window: focus-out is-focus: 0 contains-focus: 0\n"
+"window: focus-in is-focus: 0 contains-focus: 1\n"
+"box: focus-in is-focus: 0 contains-focus: 1\n"
+"box1: focus-in is-focus: 0 contains-focus: 1\n"
+"entry1: focus-in is-focus: 1 contains-focus: 1\n"
                   );
 
   g_string_truncate (s, 0);
@@ -136,14 +147,14 @@ test_window_focus (void)
     g_print ("entry1 -> entry2\n%s\n", s->str);
 
   g_assert_cmpstr (s->str, ==,
-"entry1: focus-change out is-focus: 0 contains-focus: 0\n"
-"box1: focus-change out is-focus: 0 contains-focus: 0\n"
-"box: focus-change out is-focus: 0 contains-focus: 0\n"
-"window: focus-change out is-focus: 0 contains-focus: 0\n"
-"window: focus-change in is-focus: 0 contains-focus: 1\n"
-"box: focus-change in is-focus: 0 contains-focus: 1\n"
-"box2: focus-change in is-focus: 0 contains-focus: 1\n"
-"entry2: focus-change in is-focus: 1 contains-focus: 1\n"
+"entry1: focus-out is-focus: 0 contains-focus: 0\n"
+"box1: focus-out is-focus: 0 contains-focus: 0\n"
+"box: focus-out is-focus: 0 contains-focus: 0\n"
+"window: focus-out is-focus: 0 contains-focus: 0\n"
+"window: focus-in is-focus: 0 contains-focus: 1\n"
+"box: focus-in is-focus: 0 contains-focus: 1\n"
+"box2: focus-in is-focus: 0 contains-focus: 1\n"
+"entry2: focus-in is-focus: 1 contains-focus: 1\n"
                   );
 
   g_string_truncate (s, 0);
@@ -156,12 +167,12 @@ test_window_focus (void)
     g_print ("entry2 -> box\n%s", s->str);
 
   g_assert_cmpstr (s->str, ==,
-"entry2: focus-change out is-focus: 0 contains-focus: 0\n"
-"box2: focus-change out is-focus: 0 contains-focus: 0\n"
-"box: focus-change out is-focus: 0 contains-focus: 0\n"
-"window: focus-change out is-focus: 0 contains-focus: 0\n"
-"window: focus-change in is-focus: 0 contains-focus: 1\n"
-"box: focus-change in is-focus: 1 contains-focus: 1\n"
+"entry2: focus-out is-focus: 0 contains-focus: 0\n"
+"box2: focus-out is-focus: 0 contains-focus: 0\n"
+"box: focus-out is-focus: 0 contains-focus: 0\n"
+"window: focus-out is-focus: 0 contains-focus: 0\n"
+"window: focus-in is-focus: 0 contains-focus: 1\n"
+"box: focus-in is-focus: 1 contains-focus: 1\n"
                   );
 
   g_string_truncate (s, 0);


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]