[gtk/readonly-events-1: 111/149] New focus change handling



commit 23c67f8c672ee3c0a3e22afd71ac86f96592dca5
Author: Matthias Clasen <mclasen redhat com>
Date:   Wed Feb 12 20:26:29 2020 -0500

    New focus change handling
    
    Instead of relying on gdk's antiquated crossing events,
    create a new GtkCrossingData struct that contains the
    actual widgets, and a new event controller vfunc that
    expects this struct. This also saves us from making sense
    of X's crossing modes and details, and makes for a
    generally simpler api.
    
    The ::focus-in and ::focus-out signals of GtkEventControllerKey
    have been replaced by a single ::focus-change signal that
    takes GtkCrossingData as an argument. All callers have
    been updated.

 gtk/gtkcalendar.c               |  17 ++--
 gtk/gtkentrycompletion.c        |  14 +--
 gtk/gtkenums.h                  |  10 ++
 gtk/gtkeventcontroller.c        |  67 ++++++++++++
 gtk/gtkeventcontroller.h        |  35 +++++++
 gtk/gtkeventcontrollerkey.c     | 221 +++++++++++++++-------------------------
 gtk/gtkeventcontrollerprivate.h |   5 +
 gtk/gtkfilechooserentry.c       |  12 +--
 gtk/gtkmodelbutton.c            |  20 ++--
 gtk/gtkpopover.c                |  13 ---
 gtk/gtkpopovermenu.c            |  12 +--
 gtk/gtkspinbutton.c             |  14 +--
 gtk/gtktext.c                   |  74 +++++++-------
 gtk/gtktextview.c               | 108 +++++++++-----------
 gtk/gtktreeview.c               |  37 +++----
 gtk/gtktreeviewcolumn.c         |   8 +-
 gtk/gtkwidget.c                 |  21 ++++
 gtk/gtkwidgetprivate.h          |   4 +
 gtk/gtkwindow.c                 | 119 +++++++++++++++-------
 19 files changed, 456 insertions(+), 355 deletions(-)
---
diff --git a/gtk/gtkcalendar.c b/gtk/gtkcalendar.c
index 1c97018c2d..36470a711d 100644
--- a/gtk/gtkcalendar.c
+++ b/gtk/gtkcalendar.c
@@ -280,9 +280,8 @@ static gboolean gtk_calendar_key_controller_key_pressed (GtkEventControllerKey *
                                                          guint                  keycode,
                                                          GdkModifierType        state,
                                                          GtkWidget             *widget);
-static void     gtk_calendar_key_controller_focus       (GtkEventControllerKey *controller,
-                                                         GdkCrossingMode        mode,
-                                                         GdkNotifyType          detail,
+static void     gtk_calendar_key_controller_focus       (GtkEventController    *controller,
+                                                         GtkCrossingDirection   direction,
                                                          GtkWidget             *widget);
 static void     gtk_calendar_state_flags_changed  (GtkWidget     *widget,
                                                    GtkStateFlags  previous_state);
@@ -593,10 +592,7 @@ 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-in",
-                    G_CALLBACK (gtk_calendar_key_controller_focus),
-                    calendar);
-  g_signal_connect (controller, "focus-out",
+  g_signal_connect (controller, "focus-change",
                     G_CALLBACK (gtk_calendar_key_controller_focus),
                     calendar);
   gtk_widget_add_controller (GTK_WIDGET (calendar), controller);
@@ -1393,10 +1389,9 @@ gtk_calendar_key_controller_key_pressed (GtkEventControllerKey *controller,
 }
 
 static void
-gtk_calendar_key_controller_focus (GtkEventControllerKey *key,
-                                   GdkCrossingMode        mode,
-                                   GdkNotifyType          detail,
-                                   GtkWidget             *widget)
+gtk_calendar_key_controller_focus (GtkEventController     *controller,
+                                   GtkCrossingDirection    direction,
+                                   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 c5a95c9748..4167b7e104 100644
--- a/gtk/gtkentrycompletion.c
+++ b/gtk/gtkentrycompletion.c
@@ -2307,13 +2307,13 @@ accept_completion_callback (GtkEntryCompletion *completion)
   return FALSE;
 }
 
-static gboolean
-text_focus_out (GtkEntryCompletion *completion)
+static void
+text_focus_change (GtkEntryCompletion   *completion,
+                   GtkCrossingDirection  direction) 
 {
-  if (gtk_widget_get_mapped (completion->priv->popup_window))
-    return FALSE;
-
-  return accept_completion_callback (completion);
+  if (direction == GTK_CROSSING_OUT &&
+      !gtk_widget_get_mapped (completion->priv->popup_window))
+    accept_completion_callback (completion);
 }
 
 static void
@@ -2349,7 +2349,7 @@ 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-out", G_CALLBACK (text_focus_out), completion);
+  g_signal_connect_swapped (controller, "focus-change", G_CALLBACK (text_focus_change), completion);
   gtk_widget_add_controller (GTK_WIDGET (text), controller);
 
   completion->priv->changed_id =
diff --git a/gtk/gtkenums.h b/gtk/gtkenums.h
index d4c3b4de2b..59eb3e17c6 100644
--- a/gtk/gtkenums.h
+++ b/gtk/gtkenums.h
@@ -954,6 +954,16 @@ typedef enum
   GTK_EVENT_SEQUENCE_DENIED
 } GtkEventSequenceState;
 
+typedef enum {
+  GTK_CROSSING_FOCUS,
+  GTK_CROSSING_POINTER
+} GtkCrossingType;
+
+typedef enum {
+  GTK_CROSSING_IN,
+  GTK_CROSSING_OUT
+} GtkCrossingDirection;
+
 /**
  * GtkPanDirection:
  * @GTK_PAN_DIRECTION_LEFT: panned towards the left
diff --git a/gtk/gtkeventcontroller.c b/gtk/gtkeventcontroller.c
index f67669d18d..9c77d49444 100644
--- a/gtk/gtkeventcontroller.c
+++ b/gtk/gtkeventcontroller.c
@@ -127,6 +127,14 @@ gtk_event_controller_handle_event_default (GtkEventController *self,
   return FALSE;
 }
 
+static void
+gtk_event_controller_handle_crossing_default (GtkEventController    *self,
+                                              const GtkCrossingData *crossing,
+                                              double                 x,
+                                              double                 y)
+{
+}
+
 static void
 gtk_event_controller_set_property (GObject      *object,
                                    guint         prop_id,
@@ -204,6 +212,7 @@ gtk_event_controller_class_init (GtkEventControllerClass *klass)
   klass->unset_widget = gtk_event_controller_unset_widget;
   klass->filter_event = gtk_event_controller_filter_event_default;
   klass->handle_event = gtk_event_controller_handle_event_default;
+  klass->handle_crossing = gtk_event_controller_handle_crossing_default;
 
   object_class->finalize = gtk_event_controller_finalize;
   object_class->set_property = gtk_event_controller_set_property;
@@ -307,6 +316,34 @@ gtk_event_controller_handle_event (GtkEventController *controller,
   return retval;
 }
 
+/**
+ * gtk_event_controller_handle_crossing:
+ * @controller: a #GtkEventController
+ * @crossing: a #GtkCrossingData
+ * @x: event position in widget coordinates
+ * @y: event position in widget coordinates
+ *
+ * Feeds a crossing event into @controller, so it can be interpreted
+ * and the controller actions triggered.
+ **/
+void
+gtk_event_controller_handle_crossing (GtkEventController    *controller,
+                                      const GtkCrossingData *crossing,
+                                      double                 x,
+                                      double                 y)
+{
+  GtkEventControllerClass *controller_class;
+
+  g_return_if_fail (GTK_IS_EVENT_CONTROLLER (controller));
+  g_return_if_fail (crossing != NULL);
+
+  controller_class = GTK_EVENT_CONTROLLER_GET_CLASS (controller);
+
+  g_object_ref (controller);
+  controller_class->handle_crossing (controller, crossing, x, y);
+  g_object_unref (controller);
+}
+
 /**
  * gtk_event_controller_get_widget:
  * @controller: a #GtkEventController
@@ -451,3 +488,33 @@ gtk_event_controller_set_name (GtkEventController *controller,
   g_free (priv->name);
   priv->name = g_strdup (name);
 }
+
+static GtkCrossingData *
+gtk_crossing_data_copy (GtkCrossingData *crossing)
+{
+  GtkCrossingData *copy;
+
+  copy = g_new (GtkCrossingData, 1);
+
+  copy->type = crossing->type;
+  copy->direction = crossing->direction;
+
+  if (crossing->old_target)
+    copy->old_target = g_object_ref (crossing->old_target);
+  if (crossing->new_target)
+    copy->new_target = g_object_ref (crossing->new_target);
+
+  return copy;
+}
+
+static void
+gtk_crossing_data_free (GtkCrossingData *crossing)
+{
+  g_clear_object (&crossing->old_target);
+  g_clear_object (&crossing->new_target);
+
+  g_free (crossing);
+}
+
+G_DEFINE_BOXED_TYPE (GtkCrossingData, gtk_crossing_data,
+                     gtk_crossing_data_copy, gtk_crossing_data_free)
diff --git a/gtk/gtkeventcontroller.h b/gtk/gtkeventcontroller.h
index 1f058fb11b..b4694a5f7b 100644
--- a/gtk/gtkeventcontroller.h
+++ b/gtk/gtkeventcontroller.h
@@ -40,6 +40,36 @@ G_BEGIN_DECLS
 #define GTK_EVENT_CONTROLLER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GTK_TYPE_EVENT_CONTROLLER, 
GtkEventControllerClass))
 
 
+typedef struct _GtkCrossingData GtkCrossingData;
+
+/**
+ * GtkCrossingData:
+ * @type: the type of crossing event
+ * @direction: whether this is a focus-in or focus-out event
+ * @mode: the crossing mode
+ * @old_target: the old target
+ * @new_target: the new target
+ *
+ * The struct that is passed to gtk_event_controller_handle_crossing()
+ * and is also passed to #GtkEventControllerKey::focus-change.
+ *
+ * The @old_target and @new_target fields are set to the old or new
+ * focus or hover location.
+ */
+struct _GtkCrossingData {
+  GtkCrossingType type;
+  GtkCrossingDirection direction;
+  GdkCrossingMode mode;
+  GtkWidget *old_target;
+  GtkWidget *new_target;
+};
+
+#define GTK_TYPE_CROSSING_DATA (gtk_crossing_data_get_type ())
+
+GDK_AVAILABLE_IN_ALL
+GType               gtk_crossing_data_get_type (void) G_GNUC_CONST;
+
+
 GDK_AVAILABLE_IN_ALL
 GType        gtk_event_controller_get_type       (void) G_GNUC_CONST;
 
@@ -52,6 +82,11 @@ gboolean     gtk_event_controller_handle_event   (GtkEventController *controller
                                                   double              x,
                                                   double              y);
 GDK_AVAILABLE_IN_ALL
+void         gtk_event_controller_handle_crossing (GtkEventController    *controller,
+                                                   const GtkCrossingData *crossing,
+                                                   double                 x,
+                                                   double                 y);
+GDK_AVAILABLE_IN_ALL
 void         gtk_event_controller_reset          (GtkEventController *controller);
 
 GDK_AVAILABLE_IN_ALL
diff --git a/gtk/gtkeventcontrollerkey.c b/gtk/gtkeventcontrollerkey.c
index 0085e636f9..47890a4aee 100644
--- a/gtk/gtkeventcontrollerkey.c
+++ b/gtk/gtkeventcontrollerkey.c
@@ -38,6 +38,7 @@
 #include "gtkbindings.h"
 #include "gtkenums.h"
 #include "gtkmain.h"
+#include "gtktypebuiltins.h"
 
 #include <gdk/gdk.h>
 
@@ -50,6 +51,7 @@ struct _GtkEventControllerKey
   GdkModifierType state;
 
   const GdkEvent *current_event;
+  const GtkCrossingData *current_crossing;
 
   guint is_focus       : 1;
   guint contains_focus : 1;
@@ -65,8 +67,7 @@ enum {
   KEY_RELEASED,
   MODIFIERS,
   IM_UPDATE,
-  FOCUS_IN,
-  FOCUS_OUT,
+  FOCUS_CHANGE,
   N_SIGNALS
 };
 
@@ -94,57 +95,6 @@ gtk_event_controller_key_finalize (GObject *object)
   G_OBJECT_CLASS (gtk_event_controller_key_parent_class)->finalize (object);
 }
 
-static void
-update_focus (GtkEventControllerKey *key,
-              gboolean               focus_in,
-              GdkNotifyType          detail)
-{
-  gboolean is_focus;
-  gboolean contains_focus;
-
-  switch (detail)
-    {
-    case GDK_NOTIFY_VIRTUAL:
-    case GDK_NOTIFY_NONLINEAR_VIRTUAL:
-      is_focus = FALSE;
-      contains_focus = focus_in;
-      break;
-    case GDK_NOTIFY_ANCESTOR:
-    case GDK_NOTIFY_NONLINEAR:
-      is_focus = focus_in;
-      contains_focus = is_focus;
-      break;
-    case GDK_NOTIFY_INFERIOR:
-      is_focus = focus_in;
-      contains_focus = TRUE;
-      break;
-    case GDK_NOTIFY_UNKNOWN:
-    default:
-      g_warning ("Unknown focus change detail");
-      return;
-    }
-
-  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 (focus_in)
-            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 gboolean
 gtk_event_controller_key_handle_event (GtkEventController *controller,
                                        const GdkEvent     *event,
@@ -158,30 +108,6 @@ gtk_event_controller_key_handle_event (GtkEventController *controller,
   guint keyval;
   gboolean handled = FALSE;
 
-  if (event_type == GDK_FOCUS_CHANGE)
-    {
-      gboolean focus_in;
-      GdkCrossingMode mode;
-      GdkNotifyType detail;
-
-      gdk_event_get_focus_in (event, &focus_in);
-      gdk_event_get_crossing_mode (event, &mode);
-      gdk_event_get_crossing_detail (event, &detail);
-
-      update_focus (key, focus_in, detail);
-
-      key->current_event = event;
-
-      if (focus_in)
-        g_signal_emit (controller, signals[FOCUS_IN], 0, mode, detail);
-      else
-        g_signal_emit (controller, signals[FOCUS_OUT], 0, mode, detail);
-
-      key->current_event = NULL;
-
-      return FALSE;
-    }
-
   if (event_type != GDK_KEY_PRESS && event_type != GDK_KEY_RELEASE)
     return FALSE;
 
@@ -229,6 +155,64 @@ 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,
@@ -261,6 +245,7 @@ gtk_event_controller_key_class_init (GtkEventControllerKeyClass *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:
@@ -366,7 +351,7 @@ gtk_event_controller_key_class_init (GtkEventControllerKeyClass *klass)
 
   /**
    * GtkEventControllerKey::im-update:
-   * @controller: the object which received the signal.
+   * @controller: the object which received the signal
    *
    * This signal is emitted whenever the input method context filters away a
    * keypress and prevents the @controller receiving it. See
@@ -382,46 +367,27 @@ gtk_event_controller_key_class_init (GtkEventControllerKeyClass *klass)
                   G_TYPE_NONE, 0);
 
   /**
-   * GtkEventControllerKey::focus-in:
-   * @controller: the object which received the signal.
-   * @mode: crossing mode indicating what caused this change
-   * @detail: detail indication where the focus is coming from
+   * GtkEventControllerKey::focus-change:
+   * @controller: the object which received the signal
+   * @direction: the direction of this crossing event
    *
-   * This signal is emitted whenever the widget controlled
-   * by the @controller or one of its descendants) is given
-   * the keyboard focus.
-   */
-  signals[FOCUS_IN] =
-    g_signal_new (I_("focus-in"),
-                  GTK_TYPE_EVENT_CONTROLLER_KEY,
-                  G_SIGNAL_RUN_LAST,
-                  0, NULL, NULL,
-                  NULL,
-                  G_TYPE_NONE,
-                  2,
-                  GDK_TYPE_CROSSING_MODE,
-                  GDK_TYPE_NOTIFY_TYPE);
-
-  /**
-   * GtkEventControllerKey::focus-out:
-   * @controller: the object which received the signal.
-   * @mode: crossing mode indicating what caused this change
-   * @detail: detail indication where the focus is going
+   * 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.
    *
-   * This signal is emitted whenever the widget controlled
-   * by the @controller (or one of its descendants) loses
-   * the keyboard focus.
+   * 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_OUT] =
-    g_signal_new (I_("focus-out"),
+  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,
-                  2,
-                  GDK_TYPE_CROSSING_MODE,
-                  GDK_TYPE_NOTIFY_TYPE);
+                  G_TYPE_NONE, 1,
+                  GTK_TYPE_CROSSING_DIRECTION);
 }
 
 static void
@@ -553,29 +519,17 @@ gtk_event_controller_key_get_group (GtkEventControllerKey *controller)
  * Returns the widget that was holding focus before.
  *
  * This function can only be used in handlers for the
- * #GtkEventControllerKey::focus-in and
- * #GtkEventControllerKey::focus-out signals.
+ * #GtkEventControllerKey::focus-changed signal.
  *
  * Returns: (transfer none): the previous focus
  */
 GtkWidget *
 gtk_event_controller_key_get_focus_origin (GtkEventControllerKey *controller)
 {
-  gboolean focus_in;
-  GtkWidget *origin;
-
   g_return_val_if_fail (GTK_IS_EVENT_CONTROLLER_KEY (controller), NULL);
-  g_return_val_if_fail (controller->current_event != NULL, NULL);
-  g_return_val_if_fail (gdk_event_get_event_type (controller->current_event) == GDK_FOCUS_CHANGE, NULL);
-
-  gdk_event_get_focus_in (controller->current_event, &focus_in);
+  g_return_val_if_fail (controller->current_crossing != NULL, NULL);
 
-  if (focus_in)
-    origin = (GtkWidget *)gdk_event_get_related_target (controller->current_event);
-  else
-    origin = (GtkWidget *)gdk_event_get_target (controller->current_event);
-
-  return origin;
+  return controller->current_crossing->old_target;
 }
 
 /**
@@ -585,26 +539,17 @@ gtk_event_controller_key_get_focus_origin (GtkEventControllerKey *controller)
  * Returns the widget that will be holding focus afterwards.
  *
  * This function can only be used in handlers for the
- * #GtkEventControllerKey::focus-in and
- * #GtkEventControllerKey::focus-out signals.
+ * #GtkEventControllerKey::focus-changed signal.
  *
  * Returns: (transfer none): the next focus
  */
 GtkWidget *
 gtk_event_controller_key_get_focus_target (GtkEventControllerKey *controller)
 {
-  gboolean focus_in;
-
   g_return_val_if_fail (GTK_IS_EVENT_CONTROLLER_KEY (controller), NULL);
-  g_return_val_if_fail (controller->current_event != NULL, NULL);
-  g_return_val_if_fail (gdk_event_get_event_type (controller->current_event) == GDK_FOCUS_CHANGE, NULL);
+  g_return_val_if_fail (controller->current_crossing != NULL, NULL);
 
-  gdk_event_get_focus_in (controller->current_event, &focus_in);
-
-  if (focus_in)
-    return (GtkWidget *)gdk_event_get_target (controller->current_event);
-  else
-    return (GtkWidget *)gdk_event_get_related_target (controller->current_event);
+  return controller->current_crossing->new_target;
 }
 
 /**
diff --git a/gtk/gtkeventcontrollerprivate.h b/gtk/gtkeventcontrollerprivate.h
index 354956f841..aed93f311a 100644
--- a/gtk/gtkeventcontrollerprivate.h
+++ b/gtk/gtkeventcontrollerprivate.h
@@ -40,6 +40,11 @@ struct _GtkEventControllerClass
                              double              y);
   void     (* reset)        (GtkEventController *controller);
 
+  void     (* handle_crossing) (GtkEventController    *controller,
+                                const GtkCrossingData *crossing,
+                                double                 x,
+                                double                 y);
+
   /*<private>*/
 
   /* Tells whether the event is filtered out, %TRUE makes
diff --git a/gtk/gtkfilechooserentry.c b/gtk/gtkfilechooserentry.c
index 2c8a73f0c8..9123bddb91 100644
--- a/gtk/gtkfilechooserentry.c
+++ b/gtk/gtkfilechooserentry.c
@@ -259,12 +259,12 @@ match_func (GtkEntryCompletion *compl,
 }
 
 static void
-chooser_entry_focus_out (GtkEventControllerKey *key_controller,
-                         GdkCrossingMode        mode,
-                         GdkNotifyType          detail,
-                         GtkFileChooserEntry   *chooser_entry)
+chooser_entry_focus_change (GtkEventController   *controller,
+                            GtkCrossingDirection  direction,
+                            GtkFileChooserEntry  *chooser_entry)
 {
-  set_complete_on_load (chooser_entry, FALSE);
+  if (direction == GTK_CROSSING_OUT)
+    set_complete_on_load (chooser_entry, FALSE);
 }
 
 static void
@@ -312,7 +312,7 @@ _gtk_file_chooser_entry_init (GtkFileChooserEntry *chooser_entry)
                     G_CALLBACK (gtk_file_chooser_entry_tab_handler),
                     chooser_entry);
   g_signal_connect (controller,
-                   "focus-out", G_CALLBACK (chooser_entry_focus_out),
+                   "focus-change", G_CALLBACK (chooser_entry_focus_change),
                    chooser_entry);
   gtk_widget_add_controller (GTK_WIDGET (chooser_entry), controller);
 
diff --git a/gtk/gtkmodelbutton.c b/gtk/gtkmodelbutton.c
index 5545ed95df..7744c6cfdd 100644
--- a/gtk/gtkmodelbutton.c
+++ b/gtk/gtkmodelbutton.c
@@ -1359,19 +1359,21 @@ leave_cb (GtkEventController *controller,
 }
 
 static void
-focus_in_cb (GtkEventController *controller,
-             GdkCrossingMode     mode,
-             GdkNotifyType       type,
-             gpointer            data)
+focus_change_cb (GtkEventController   *controller,
+                 GtkCrossingDirection  direction,
+                 gpointer              data)
 {
   GtkWidget *target;
   GtkWidget *popover;
 
-  target = gtk_event_controller_get_widget (controller);
-  popover = gtk_widget_get_ancestor (target, GTK_TYPE_POPOVER_MENU);
+  if (direction == GTK_CROSSING_IN)
+    {
+      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
@@ -1400,7 +1402,7 @@ gtk_model_button_init (GtkModelButton *self)
   gtk_widget_add_controller (GTK_WIDGET (self), controller);
 
   controller = gtk_event_controller_key_new ();
-  g_signal_connect (controller, "focus-in", G_CALLBACK (focus_in_cb), NULL);
+  g_signal_connect (controller, "focus-change", G_CALLBACK (focus_change_cb), NULL);
   gtk_widget_add_controller (GTK_WIDGET (self), controller);
 
   gesture = gtk_gesture_click_new ();
diff --git a/gtk/gtkpopover.c b/gtk/gtkpopover.c
index a5285a2766..c2e4d8ee23 100644
--- a/gtk/gtkpopover.c
+++ b/gtk/gtkpopover.c
@@ -563,17 +563,6 @@ gtk_popover_native_check_resize (GtkNative *native)
     present_popup (popover);
 }
 
-
-static void
-gtk_popover_focus_in (GtkWidget *widget)
-{
-}
-
-static void
-gtk_popover_focus_out (GtkWidget *widget)
-{
-}
-
 static void
 close_menu (GtkPopover *popover)
 {
@@ -727,8 +716,6 @@ gtk_popover_init (GtkPopover *popover)
   priv->has_arrow = TRUE;
 
   controller = gtk_event_controller_key_new ();
-  g_signal_connect_swapped (controller, "focus-in", G_CALLBACK (gtk_popover_focus_in), popover);
-  g_signal_connect_swapped (controller, "focus-out", G_CALLBACK (gtk_popover_focus_out), popover);
   g_signal_connect_swapped (controller, "key-pressed", G_CALLBACK (gtk_popover_key_pressed), popover);
   gtk_widget_add_controller (GTK_WIDGET (popover), controller);
 
diff --git a/gtk/gtkpopovermenu.c b/gtk/gtkpopovermenu.c
index bf56b4c5be..7e99902e6b 100644
--- a/gtk/gtkpopovermenu.c
+++ b/gtk/gtkpopovermenu.c
@@ -168,14 +168,14 @@ visible_submenu_changed (GObject        *object,
 }
 
 static void
-focus_out (GtkEventControllerKey *controller,
-           GdkCrossingMode        mode,
-           GdkNotifyType          detail,
-           GtkPopoverMenu        *menu)
+focus_change (GtkEventController   *controller,
+              GtkCrossingDirection  direction,
+              GtkPopoverMenu       *menu)
 {
   GtkWidget *new_focus = gtk_root_get_focus (gtk_widget_get_root (GTK_WIDGET (menu)));
 
-  if (!gtk_event_controller_key_contains_focus (controller) &&
+  if (direction == GTK_CROSSING_OUT &&
+      !gtk_event_controller_key_contains_focus (GTK_EVENT_CONTROLLER_KEY (controller)) &&
       new_focus != NULL)
     {
       if (menu->parent_menu &&
@@ -216,7 +216,7 @@ 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-out", G_CALLBACK (focus_out), popover);
+  g_signal_connect (controller, "focus-change", G_CALLBACK (focus_change), 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 bef3c0b49f..284041d168 100644
--- a/gtk/gtkspinbutton.c
+++ b/gtk/gtkspinbutton.c
@@ -917,14 +917,14 @@ key_controller_key_released (GtkEventControllerKey *key,
 }
 
 static void
-key_controller_focus_out (GtkEventControllerKey *key,
-                          GdkCrossingMode        mode,
-                          GdkNotifyType          detail,
-                          GtkSpinButton         *spin_button)
+key_controller_focus_change (GtkEventController   *controller,
+                             GtkCrossingDirection  direction,
+                             GtkSpinButton        *spin_button)
 {
   GtkSpinButtonPrivate *priv = gtk_spin_button_get_instance_private (spin_button);
 
-  if (gtk_editable_get_editable (GTK_EDITABLE (priv->entry)))
+  if (direction == GTK_CROSSING_OUT &&
+      gtk_editable_get_editable (GTK_EDITABLE (priv->entry)))
     gtk_spin_button_update (spin_button);
 }
 
@@ -1012,8 +1012,8 @@ 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-out",
-                    G_CALLBACK (key_controller_focus_out), 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);
 }
 
diff --git a/gtk/gtktext.c b/gtk/gtktext.c
index d2396f6f17..51e3233a2a 100644
--- a/gtk/gtktext.c
+++ b/gtk/gtktext.c
@@ -323,8 +323,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_in             (GtkWidget        *widget);
-static void   gtk_text_focus_out            (GtkWidget        *widget);
+static void   gtk_text_focus_change         (GtkWidget            *widget,
+                                             GtkCrossingDirection  direction);
 static gboolean gtk_text_grab_focus         (GtkWidget        *widget);
 static void   gtk_text_css_changed          (GtkWidget        *widget,
                                              GtkCssStyleChange *change);
@@ -1781,10 +1781,8 @@ 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-in",
-                            G_CALLBACK (gtk_text_focus_in), self);
-  g_signal_connect_swapped (priv->key_controller, "focus-out",
-                            G_CALLBACK (gtk_text_focus_out), 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);
@@ -3048,55 +3046,53 @@ gtk_text_key_controller_key_pressed (GtkEventControllerKey *controller,
 }
 
 static void
-gtk_text_focus_in (GtkWidget *widget)
+gtk_text_focus_change (GtkWidget            *widget,
+                       GtkCrossingDirection  direction)
 {
   GtkText *self = GTK_TEXT (widget);
   GtkTextPrivate *priv = gtk_text_get_instance_private (self);
   GdkKeymap *keymap;
 
-  gtk_widget_queue_draw (widget);
-
-  keymap = gdk_display_get_keymap (gtk_widget_get_display (widget));
-
-  if (priv->editable)
+  if (direction == GTK_CROSSING_IN)
     {
-      gtk_text_schedule_im_reset (self);
-      gtk_im_context_focus_in (priv->im_context);
-    }
+      gtk_widget_queue_draw (widget);
 
-  g_signal_connect (keymap, "direction-changed",
-                    G_CALLBACK (keymap_direction_changed), self);
+      keymap = gdk_display_get_keymap (gtk_widget_get_display (widget));
 
-  gtk_text_reset_blink_time (self);
-  gtk_text_check_cursor_blink (self);
-}
+      if (priv->editable)
+        {
+          gtk_text_schedule_im_reset (self);
+          gtk_im_context_focus_in (priv->im_context);
+        }
 
-static void
-gtk_text_focus_out (GtkWidget *widget)
-{
-  GtkText *self = GTK_TEXT (widget);
-  GtkTextPrivate *priv = gtk_text_get_instance_private (self);
-  GdkKeymap *keymap;
+      g_signal_connect (keymap, "direction-changed",
+                        G_CALLBACK (keymap_direction_changed), self);
 
-  gtk_text_selection_bubble_popup_unset (self);
+      gtk_text_reset_blink_time (self);
+      gtk_text_check_cursor_blink (self);
+    }
+  else
+    {
+      gtk_text_selection_bubble_popup_unset (self);
 
-  if (priv->text_handle)
-    _gtk_text_handle_set_mode (priv->text_handle,
-                               GTK_TEXT_HANDLE_MODE_NONE);
+      if (priv->text_handle)
+        _gtk_text_handle_set_mode (priv->text_handle,
+                                   GTK_TEXT_HANDLE_MODE_NONE);
 
-  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_out (priv->im_context);
-    }
+      if (priv->editable)
+        {
+          gtk_text_schedule_im_reset (self);
+          gtk_im_context_focus_out (priv->im_context);
+        }
 
-  gtk_text_check_cursor_blink (self);
+      gtk_text_check_cursor_blink (self);
 
-  g_signal_handlers_disconnect_by_func (keymap, keymap_direction_changed, 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 04aba8b4c8..f55a784741 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_in             (GtkWidget        *widget);
-static void gtk_text_view_focus_out            (GtkWidget        *widget);
+static void gtk_text_view_focus_change         (GtkWidget            *widget,
+                                                GtkCrossingDirection  direction);
 static void gtk_text_view_motion               (GtkEventController *controller,
                                                 double              x,
                                                 double              y,
@@ -1695,11 +1695,8 @@ 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-in",
-                            G_CALLBACK (gtk_text_view_focus_in),
-                            widget);
-  g_signal_connect_swapped (priv->key_controller, "focus-out",
-                            G_CALLBACK (gtk_text_view_focus_out),
+  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);
@@ -5318,72 +5315,65 @@ keymap_direction_changed (GdkKeymap   *keymap,
 }
 
 static void
-gtk_text_view_focus_in (GtkWidget *widget)
+gtk_text_view_focus_change (GtkWidget             *widget,
+                            GtkCrossingDirection   direction)
 {
-  GtkTextView *text_view;
-  GtkTextViewPrivate *priv;
-
-  text_view = GTK_TEXT_VIEW (widget);
-  priv = text_view->priv;
-
-  gtk_widget_queue_draw (widget);
-
-  DV(g_print (G_STRLOC": focus_in\n"));
-
-  gtk_text_view_reset_blink_time (text_view);
+  GtkTextView *text_view = GTK_TEXT_VIEW (widget);
+  GtkTextViewPrivate *priv = text_view->priv;
 
-  if (cursor_visible (text_view) && priv->layout)
+  if (direction == GTK_CROSSING_IN)
     {
-      gtk_text_layout_set_cursor_visible (priv->layout, TRUE);
-      gtk_text_view_check_cursor_blink (text_view);
-    }
+      gtk_widget_queue_draw (widget);
 
-  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);
+      DV(g_print (G_STRLOC": focus_in\n"));
 
-  if (priv->editable)
-    {
-      priv->need_im_reset = TRUE;
-      gtk_im_context_focus_in (priv->im_context);
-    }
-}
+      gtk_text_view_reset_blink_time (text_view);
 
-static void
-gtk_text_view_focus_out (GtkWidget *widget)
-{
-  GtkTextView *text_view;
-  GtkTextViewPrivate *priv;
+      if (cursor_visible (text_view) && priv->layout)
+        {
+          gtk_text_layout_set_cursor_visible (priv->layout, TRUE);
+          gtk_text_view_check_cursor_blink (text_view);
+        }
 
-  text_view = GTK_TEXT_VIEW (widget);
-  priv = text_view->priv;
+      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);
 
-  gtk_text_view_end_selection_drag (text_view);
+      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);
+      gtk_widget_queue_draw (widget);
 
-  DV(g_print (G_STRLOC": focus_out\n"));
+      DV(g_print (G_STRLOC": focus_out\n"));
 
-  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 (cursor_visible (text_view) && priv->layout)
+        {
+          gtk_text_view_check_cursor_blink (text_view);
+          gtk_text_layout_set_cursor_visible (priv->layout, FALSE);
+        }
 
-  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);
+      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->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);
+      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 cb3f3eb685..6e0574c4d7 100644
--- a/gtk/gtktreeview.c
+++ b/gtk/gtktreeview.c
@@ -667,10 +667,9 @@ 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_out    (GtkEventControllerKey *key,
-                                                           GdkCrossingMode        mode,
-                                                           GdkNotifyType          detail,
-                                                           GtkTreeView           *tree_view);
+static void     gtk_tree_view_key_controller_focus_change (GtkEventController     *key,
+                                                           GtkCrossingDirection    direction,
+                                                           GtkTreeView            *tree_view);
 
 static gint     gtk_tree_view_focus                (GtkWidget        *widget,
                                                    GtkDirectionType  direction);
@@ -1839,8 +1838,8 @@ 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-out",
-                    G_CALLBACK (gtk_tree_view_key_controller_focus_out), 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);
 }
 
@@ -5546,23 +5545,19 @@ gtk_tree_view_motion_controller_leave (GtkEventControllerMotion *controller,
 }
 
 static void
-gtk_tree_view_key_controller_focus_out (GtkEventControllerKey *key,
-                                        GdkCrossingMode        mode,
-                                        GdkNotifyType          detail,
-                                        GtkTreeView           *tree_view)
+gtk_tree_view_key_controller_focus_change (GtkEventController   *key,
+                                           GtkCrossingDirection  direction,
+                                           GtkTreeView          *tree_view)
 {
-  gboolean is_focus, contains_focus;
-
-  gtk_widget_queue_draw (GTK_WIDGET (tree_view));
-
-  g_object_get (key,
-                "is-focus", &is_focus,
-                "contains-focus", &contains_focus,
-                NULL);
+  if (direction == GTK_CROSSING_OUT)
+    {
+      gtk_widget_queue_draw (GTK_WIDGET (tree_view));
 
-  if (tree_view->search_popover && !gtk_event_controller_key_contains_focus (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_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 ());
+    }
 }
 
 /* Incremental Reflow
diff --git a/gtk/gtktreeviewcolumn.c b/gtk/gtktreeviewcolumn.c
index aa39d9b9ba..790e611036 100644
--- a/gtk/gtktreeviewcolumn.c
+++ b/gtk/gtktreeviewcolumn.c
@@ -831,11 +831,11 @@ gtk_tree_view_column_cell_layout_get_area (GtkCellLayout   *cell_layout)
 
 static void
 focus_in (GtkEventControllerKey *controller,
-          GdkCrossingMode        mode,
-          GdkNotifyType          detail,
+          GtkCrossingDirection   direction,
           GtkTreeViewColumn     *column)
 {
-  _gtk_tree_view_set_focus_column (GTK_TREE_VIEW (column->priv->tree_view), column);
+  if (direction == GTK_CROSSING_IN)
+    _gtk_tree_view_set_focus_column (GTK_TREE_VIEW (column->priv->tree_view), column);
 }
 
 /* Button handling code
@@ -866,7 +866,7 @@ gtk_tree_view_column_create_button (GtkTreeViewColumn *tree_column)
   gtk_widget_add_controller (priv->button, controller);
 
   controller = gtk_event_controller_key_new ();
-  g_signal_connect (controller, "focus-in", G_CALLBACK (focus_in), tree_column);
+  g_signal_connect (controller, "focus-change", G_CALLBACK (focus_in), tree_column);
   gtk_widget_add_controller (priv->button, controller);
 
   priv->frame = gtk_frame_new (NULL);
diff --git a/gtk/gtkwidget.c b/gtk/gtkwidget.c
index 0e937bd10e..57d536d510 100644
--- a/gtk/gtkwidget.c
+++ b/gtk/gtkwidget.c
@@ -4852,6 +4852,27 @@ gtk_widget_run_controllers (GtkWidget           *widget,
   return handled;
 }
 
+void
+gtk_widget_handle_crossing (GtkWidget             *widget,
+                            const GtkCrossingData *crossing,
+                            double                 x,
+                            double                 y)
+{
+  GtkWidgetPrivate *priv = gtk_widget_get_instance_private (widget);
+  GList *l;
+
+  g_object_ref (widget);
+
+  for (l = priv->event_controllers; l; l = l->next)
+    {
+      GtkEventController *controller = l->data;
+
+      gtk_event_controller_handle_crossing (controller, crossing, x, y);
+    }
+
+  g_object_unref (widget);
+}
+
 static gboolean
 translate_event_coordinates (GdkEvent  *event,
                              double    *x,
diff --git a/gtk/gtkwidgetprivate.h b/gtk/gtkwidgetprivate.h
index 87d0eba9c0..bc24a9c728 100644
--- a/gtk/gtkwidgetprivate.h
+++ b/gtk/gtkwidgetprivate.h
@@ -347,6 +347,10 @@ gboolean          gtk_widget_run_controllers               (GtkWidget
                                                             double               x,
                                                             double               y,
                                                             GtkPropagationPhase  phase);
+void              gtk_widget_handle_crossing               (GtkWidget             *widget,
+                                                            const GtkCrossingData *crossing,
+                                                            double                 x,
+                                                            double                 y);
 
 
 guint             gtk_widget_add_surface_transform_changed_callback (GtkWidget                          
*widget,
diff --git a/gtk/gtkwindow.c b/gtk/gtkwindow.c
index a8de122391..4302654304 100644
--- a/gtk/gtkwindow.c
+++ b/gtk/gtkwindow.c
@@ -394,8 +394,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_in           (GtkWidget         *widget);
-static void gtk_window_focus_out          (GtkWidget         *widget);
+static void gtk_window_focus_change       (GtkWidget            *widget,
+                                           GtkCrossingDirection  direction);
 static gboolean gtk_window_key_pressed    (GtkWidget         *widget,
                                            guint              keyval,
                                            guint              keycode,
@@ -1844,10 +1844,8 @@ 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-in",
-                            G_CALLBACK (gtk_window_focus_in), window);
-  g_signal_connect_swapped (priv->key_controller, "focus-out",
-                            G_CALLBACK (gtk_window_focus_out), window);
+  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",
@@ -6121,33 +6119,33 @@ gtk_window_has_mnemonic_modifier_pressed (GtkWindow *window)
 }
 
 static void
-gtk_window_focus_in (GtkWidget *widget)
+gtk_window_focus_change (GtkWidget            *widget,
+                         GtkCrossingDirection  direction)
 {
   GtkWindow *window = GTK_WINDOW (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))
+  if (direction == GTK_CROSSING_IN)
     {
-      _gtk_window_set_is_active (window, TRUE);
+      /* 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);
 
-      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);
+        }
     }
-}
-
-static void
-gtk_window_focus_out (GtkWidget *widget)
-{
-  GtkWindow *window = GTK_WINDOW (widget);
-
-  _gtk_window_set_is_active (window, FALSE);
+  else
+    {
+      _gtk_window_set_is_active (window, FALSE);
 
-  /* set the mnemonic-visible property to false */
-  gtk_window_set_mnemonics_visible (window, FALSE);
+      /* set the mnemonic-visible property to false */
+      gtk_window_set_mnemonics_visible (window, FALSE);
+    }
 }
 
 static void
@@ -6347,6 +6345,61 @@ gtk_window_move_focus (GtkWidget        *widget,
     gtk_window_set_focus (GTK_WINDOW (widget), NULL);
 }
 
+static void
+synthesize_focus_change_events (GtkWindow *window,
+                                GtkWidget *old_focus,
+                                GtkWidget *new_focus)
+{
+  GtkCrossingData crossing;
+  GtkWidget *widget, *focus_child;
+  GList *list, *l;
+  GtkStateFlags flags;
+
+  flags = GTK_STATE_FLAG_FOCUSED;
+  if (gtk_window_get_focus_visible (GTK_WINDOW (window)))
+    flags |= GTK_STATE_FLAG_FOCUS_VISIBLE;
+
+  crossing.type = GTK_CROSSING_FOCUS;
+  crossing.mode = GDK_CROSSING_NORMAL;
+  crossing.old_target = old_focus;
+  crossing.new_target = new_focus;
+
+  crossing.direction = GTK_CROSSING_OUT;
+
+  widget = old_focus;
+  while (widget)
+    {
+      gtk_widget_handle_crossing (widget, &crossing, 0, 0);
+      gtk_widget_unset_state_flags (widget, flags);
+      gtk_widget_set_focus_child (widget, NULL);
+      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);
+    }
+
+  crossing.direction = GTK_CROSSING_IN;
+
+  for (l = list; l; l = l->next)
+    {
+      widget = l->data;
+      if (l->next)
+        focus_child = l->next->data;
+      else
+        focus_child = NULL;
+      gtk_widget_handle_crossing (widget, &crossing, 0, 0);
+      gtk_widget_set_state_flags (widget, flags, FALSE);
+      gtk_widget_set_focus_child (widget, focus_child);
+    }
+
+  g_list_free (list);
+}
+
 /**
  * gtk_window_set_focus:
  * @window: a #GtkWindow
@@ -6365,9 +6418,6 @@ gtk_window_set_focus (GtkWindow *window,
 {
   GtkWindowPrivate *priv = gtk_window_get_instance_private (window);
   GtkWidget *old_focus = NULL;
-  GdkSeat *seat;
-  GdkDevice *device;
-  GdkEvent *event;
 
   g_return_if_fail (GTK_IS_WINDOW (window));
 
@@ -6381,14 +6431,13 @@ gtk_window_set_focus (GtkWindow *window,
     old_focus = g_object_ref (priv->focus_widget);
   g_set_object (&priv->focus_widget, NULL);
 
-  seat = gdk_display_get_default_seat (gtk_widget_get_display (GTK_WIDGET (window)));
-  device = gdk_seat_get_keyboard (seat);
+  if (old_focus)
+    gtk_widget_set_has_focus (old_focus, FALSE);
 
-  event = gdk_event_focus_new (priv->surface, device, device, TRUE);
+  synthesize_focus_change_events (window, old_focus, focus);
 
-  gtk_synthesize_crossing_events (GTK_ROOT (window), old_focus, focus, event, GDK_CROSSING_NORMAL);
-
-  gdk_event_unref (event);
+  if (focus)
+    gtk_widget_set_has_focus (focus, TRUE);
 
   g_set_object (&priv->focus_widget, focus);
 


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