[gtk+/touchscreens: 33/49] gtk: Add event storing/replaying to GtkWidget::captured-event



commit ab0b1438a523ec1b12e68a29b4921793e887a279
Author: Carlos Garnacho <carlosg gnome org>
Date:   Sun Nov 13 17:44:30 2011 +0100

    gtk: Add event storing/replaying to GtkWidget::captured-event
    
    It now returns a GtkCapturedEventFlags which tells whether the
    widget captured the event, and additionally whether the event
    should be stored for later replay.
    
    gtk_widget_release_captured_events() has been added too so
    all stored events are released onto the widget that was initially
    to receive the events.

 gtk/gtkenums.h          |    6 ++
 gtk/gtkmarshalers.list  |    1 +
 gtk/gtkprivate.c        |   17 ++++++
 gtk/gtkprivate.h        |    4 ++
 gtk/gtkscrolledwindow.c |  120 ++++++++++++++---------------------------
 gtk/gtkwidget.c         |  136 +++++++++++++++++++++++++++++++++++++++++++----
 gtk/gtkwidget.h         |    7 ++-
 7 files changed, 200 insertions(+), 91 deletions(-)
---
diff --git a/gtk/gtkenums.h b/gtk/gtkenums.h
index 7045f92..9188208 100644
--- a/gtk/gtkenums.h
+++ b/gtk/gtkenums.h
@@ -920,6 +920,12 @@ typedef enum {
   GTK_BORDER_STYLE_OUTSET
 } GtkBorderStyle;
 
+typedef enum {
+  GTK_CAPTURED_EVENT_NONE    = 0,
+  GTK_CAPTURED_EVENT_HANDLED = 1 << 0,
+  GTK_CAPTURED_EVENT_STORE   = 1 << 1
+} GtkCapturedEventFlags;
+
 G_END_DECLS
 
 
diff --git a/gtk/gtkmarshalers.list b/gtk/gtkmarshalers.list
index a21caff..b75651f 100644
--- a/gtk/gtkmarshalers.list
+++ b/gtk/gtkmarshalers.list
@@ -48,6 +48,7 @@ BOOLEAN:BOOLEAN,BOOLEAN,BOOLEAN
 BOOLEAN:STRING
 ENUM:ENUM
 ENUM:VOID
+FLAGS:BOXED
 INT:POINTER
 OBJECT:VOID
 STRING:DOUBLE
diff --git a/gtk/gtkprivate.c b/gtk/gtkprivate.c
index ed5f1f7..0300d33 100644
--- a/gtk/gtkprivate.c
+++ b/gtk/gtkprivate.c
@@ -31,6 +31,7 @@
 #include "gdk/gdk.h"
 
 #include "gtkprivate.h"
+#include "gtkenums.h"
 
 
 #if !defined G_OS_WIN32 && !(defined GDK_WINDOWING_QUARTZ && defined QUARTZ_RELOCATION)
@@ -158,6 +159,22 @@ _gtk_single_string_accumulator (GSignalInvocationHint *ihint,
   return continue_emission;
 }
 
+gboolean
+_gtk_captured_enum_accumulator (GSignalInvocationHint *ihint,
+                                GValue                *return_accu,
+                                const GValue          *handler_return,
+                                gpointer               dummy)
+{
+  gboolean continue_emission;
+  GtkCapturedEventFlags flags;
+
+  flags = g_value_get_flags (handler_return);
+  g_value_set_flags (return_accu, flags);
+  continue_emission = (flags & GTK_CAPTURED_EVENT_HANDLED) == 0;
+
+  return continue_emission;
+}
+
 GdkModifierType
 _gtk_replace_virtual_modifiers (GdkKeymap       *keymap,
                                 GdkModifierType  modifiers)
diff --git a/gtk/gtkprivate.h b/gtk/gtkprivate.h
index 1b4d0ae..957176f 100644
--- a/gtk/gtkprivate.h
+++ b/gtk/gtkprivate.h
@@ -59,6 +59,10 @@ gboolean _gtk_single_string_accumulator   (GSignalInvocationHint *ihint,
                                            GValue                *return_accu,
                                            const GValue          *handler_return,
                                            gpointer               dummy);
+gboolean _gtk_captured_enum_accumulator   (GSignalInvocationHint *ihint,
+                                           GValue                *return_accu,
+                                           const GValue          *handler_return,
+                                           gpointer               dummy);
 
 GdkModifierType _gtk_replace_virtual_modifiers (GdkKeymap       *keymap,
                                                 GdkModifierType  modifiers);
diff --git a/gtk/gtkscrolledwindow.c b/gtk/gtkscrolledwindow.c
index b932e03..2f10709 100644
--- a/gtk/gtkscrolledwindow.c
+++ b/gtk/gtkscrolledwindow.c
@@ -168,7 +168,7 @@ struct _GtkScrolledWindowPrivate
   guint    hide_scrollbars_id;
 
   /* Kinetic scrolling */
-  GdkEvent              *button_press_event;
+  GdkDevice             *drag_device;
   guint                  kinetic_scrolling_enabled : 1;
   guint                  in_drag                   : 1;
   guint                  hmoving                   : 1;
@@ -1252,12 +1252,6 @@ gtk_scrolled_window_destroy (GtkWidget *widget)
       priv->release_timeout_id = 0;
     }
 
-  if (priv->button_press_event)
-    {
-      gdk_event_free (priv->button_press_event);
-      priv->button_press_event = NULL;
-    }
-
   motion_event_list_clear (&priv->motion_events);
   gtk_scrolled_window_auto_hide_scrollbars_stop (scrolled_window);
 
@@ -2717,17 +2711,13 @@ static gboolean
 gtk_scrolled_window_release_captured_events (GtkScrolledWindow *scrolled_window)
 {
   GtkScrolledWindowPrivate *priv = scrolled_window->priv;
-  GdkDevice *device;
 
   /* Cancel the scrolling and send the button press
    * event to the child widget
    */
-  if (!priv->button_press_event)
-    return FALSE;
-
-  device = gdk_event_get_device (priv->button_press_event);
-  gdk_device_ungrab (device, GDK_CURRENT_TIME);
-  gtk_device_grab_remove (GTK_WIDGET (scrolled_window), device);
+  gdk_device_ungrab (priv->drag_device, GDK_CURRENT_TIME);
+  gtk_device_grab_remove (GTK_WIDGET (scrolled_window), priv->drag_device);
+  priv->drag_device = NULL;
 
   if (priv->motion_notify_id > 0)
     {
@@ -2740,18 +2730,7 @@ gtk_scrolled_window_release_captured_events (GtkScrolledWindow *scrolled_window)
       priv->button_release_id = 0;
     }
 
-  /* We are going to synthesize the button press event so that
-   * it can be handled by child widget, but we don't want to
-   * handle it, so block both button-press and and press-and-hold
-   * during this button press
-   */
-  g_signal_handler_block (scrolled_window, priv->button_press_id);
-
-  gtk_main_do_event (priv->button_press_event);
-
-  g_signal_handler_unblock (scrolled_window, priv->button_press_id);
-  gdk_event_free (priv->button_press_event);
-  priv->button_press_event = NULL;
+  gtk_widget_release_captured_events (GTK_WIDGET (scrolled_window), TRUE);
 
   return FALSE;
 }
@@ -2765,23 +2744,25 @@ gtk_scrolled_window_button_release_event (GtkWidget *widget,
   GtkWidget *child;
   gdouble distance;
   GdkEventButton *event;
-  GdkDevice *device;
 
   if (_event->type != GDK_BUTTON_RELEASE)
-    return FALSE;
+    return GTK_CAPTURED_EVENT_NONE;
 
   event = (GdkEventButton *)_event;
 
   if (event->button != 1)
-    return FALSE;
+    return GTK_CAPTURED_EVENT_NONE;
 
   child = gtk_bin_get_child (GTK_BIN (widget));
   if (!child)
-    return FALSE;
+    return GTK_CAPTURED_EVENT_NONE;
+
+  if (priv->drag_device != gdk_event_get_device (_event))
+    return GTK_CAPTURED_EVENT_NONE;
 
-  device = gdk_event_get_device (_event);
-  gdk_device_ungrab (device, event->time);
-  gtk_device_grab_remove (widget, device);
+  gdk_device_ungrab (priv->drag_device, event->time);
+  gtk_device_grab_remove (widget, priv->drag_device);
+  priv->drag_device = NULL;
 
   if (priv->motion_notify_id > 0)
     {
@@ -2806,28 +2787,12 @@ gtk_scrolled_window_button_release_event (GtkWidget *widget,
       /* There hasn't been scrolling at all, so just let the
        * child widget handle the events normally
        */
-      if (priv->button_press_event)
-        {
-          g_signal_handler_block (widget, priv->button_press_id);
-          gtk_main_do_event (priv->button_press_event);
-          g_signal_handler_unblock (widget, priv->button_press_id);
-          gdk_event_free (priv->button_press_event);
-          priv->button_press_event = NULL;
-          gtk_main_do_event (_event);
-
-          return TRUE;
-      }
+      gtk_widget_release_captured_events (widget, TRUE);
 
-      return FALSE;
+      return GTK_CAPTURED_EVENT_NONE;
     }
   priv->in_drag = FALSE;
 
-  if (priv->button_press_event)
-    {
-      gdk_event_free (priv->button_press_event);
-      priv->button_press_event = NULL;
-    }
-
   distance =
     gtk_scrolled_window_get_deceleration_distance (scrolled_window,
                                                    event->x_root, event->y_root,
@@ -2843,7 +2808,7 @@ gtk_scrolled_window_button_release_event (GtkWidget *widget,
   /* Reset motion event buffer */
   motion_event_list_reset (&priv->motion_events);
 
-  return TRUE;
+  return GTK_CAPTURED_EVENT_HANDLED;
 }
 
 static gboolean
@@ -2860,16 +2825,19 @@ gtk_scrolled_window_motion_notify_event (GtkWidget *widget,
   GdkEventMotion *event;
 
   if (_event->type != GDK_MOTION_NOTIFY)
-    return FALSE;
+    return GTK_CAPTURED_EVENT_NONE;
 
   event = (GdkEventMotion *)_event;
 
   if (!(event->state & GDK_BUTTON1_MASK))
-    return FALSE;
+    return GTK_CAPTURED_EVENT_NONE;
 
   child = gtk_bin_get_child (GTK_BIN (widget));
   if (!child)
-    return FALSE;
+    return GTK_CAPTURED_EVENT_NONE;
+
+  if (priv->drag_device != gdk_event_get_device (_event))
+    return GTK_CAPTURED_EVENT_NONE;
 
   /* Check if we've passed the drag threshold */
   if (!priv->in_drag)
@@ -2885,18 +2853,13 @@ gtk_scrolled_window_motion_notify_event (GtkWidget *widget,
           priv->in_drag = TRUE;
         }
       else
-        return FALSE;
+        return GTK_CAPTURED_EVENT_NONE;
     }
 
   priv->last_button_event_x_root = -TOUCH_BYPASS_CAPTURED_THRESHOLD;
   priv->last_button_event_y_root = -TOUCH_BYPASS_CAPTURED_THRESHOLD;
 
-  if (priv->button_press_event)
-    {
-      gdk_event_free (priv->button_press_event);
-      priv->button_press_event = NULL;
-    }
-
+  gtk_widget_release_captured_events (widget, FALSE);
   motion = motion_event_list_last (&priv->motion_events);
 
   if (motion)
@@ -2921,7 +2884,7 @@ gtk_scrolled_window_motion_notify_event (GtkWidget *widget,
   motion->y = event->y_root;
   motion->time = event->time;
 
-  return FALSE;
+  return GTK_CAPTURED_EVENT_HANDLED;
 }
 
 static gboolean
@@ -2935,24 +2898,24 @@ gtk_scrolled_window_button_press_event (GtkWidget *widget,
   gint threshold;
   GtkWidget *event_widget;
   GdkEventButton *event;
-  GdkDevice *device, *source_device;
+  GdkDevice *source_device;
   GdkInputSource source;
   guint timeout;
 
   if (_event->type != GDK_BUTTON_PRESS)
-    return FALSE;
+    return GTK_CAPTURED_EVENT_NONE;
 
   source_device = gdk_event_get_source_device (_event);
   source = gdk_device_get_source (source_device);
 
   if (source != GDK_SOURCE_TOUCH)
-    return FALSE;
+    return GTK_CAPTURED_EVENT_NONE;
 
   event = (GdkEventButton *)_event;
 
   if (!priv->vscrollbar_visible &&
       !priv->hscrollbar_visible)
-    return FALSE;
+    return GTK_CAPTURED_EVENT_NONE;
 
   /* Check whether the button press is close to the previous one,
    * take that as a shortcut to get the child widget handle events
@@ -2962,32 +2925,32 @@ gtk_scrolled_window_button_press_event (GtkWidget *widget,
     {
       priv->last_button_event_x_root = -TOUCH_BYPASS_CAPTURED_THRESHOLD;
       priv->last_button_event_y_root = -TOUCH_BYPASS_CAPTURED_THRESHOLD;
-      return FALSE;
+      return GTK_CAPTURED_EVENT_NONE;
     }
 
   priv->last_button_event_x_root = event->x_root;
   priv->last_button_event_y_root = event->y_root;
 
   if (event->button != 1)
-    return FALSE;
+    return GTK_CAPTURED_EVENT_NONE;
 
   child = gtk_bin_get_child (GTK_BIN (widget));
   if (!child)
-    return FALSE;
+    return GTK_CAPTURED_EVENT_NONE;
 
   event_widget = gtk_get_event_widget (_event);
   if (priv->hscrollbar == event_widget || priv->vscrollbar == event_widget)
-    return FALSE;
+    return GTK_CAPTURED_EVENT_NONE;
 
-  device = gdk_event_get_device (_event);
-  gdk_device_grab (device,
+  priv->drag_device = gdk_event_get_device (_event);
+  gdk_device_grab (priv->drag_device,
                    gtk_widget_get_window (widget),
                    GDK_OWNERSHIP_WINDOW,
                    TRUE,
                    GDK_BUTTON_RELEASE_MASK | GDK_BUTTON1_MOTION_MASK,
                    NULL,
                    event->time);
-  gtk_device_grab_add (widget, device, TRUE);
+  gtk_device_grab_add (widget, priv->drag_device, TRUE);
 
   /* Reset motion buffer */
   motion_event_list_reset (&priv->motion_events);
@@ -3034,14 +2997,15 @@ gtk_scrolled_window_button_press_event (GtkWidget *widget,
       priv->in_drag = TRUE;
 
       /* Swallow the press event */
-      return TRUE;
+      return GTK_CAPTURED_EVENT_HANDLED;
     }
   else
     priv->in_drag = FALSE;
 
-  priv->button_press_event = gdk_event_copy (_event);
-
-  return TRUE;
+  /* Store the button press event in
+   * case we need to propagate it later
+   */
+  return GTK_CAPTURED_EVENT_HANDLED | GTK_CAPTURED_EVENT_STORE;
 }
 
 static gboolean
diff --git a/gtk/gtkwidget.c b/gtk/gtkwidget.c
index 3880b3d..12b233d 100644
--- a/gtk/gtkwidget.c
+++ b/gtk/gtkwidget.c
@@ -398,6 +398,8 @@ struct _GtkWidgetPrivate
   /* The widget's parent */
   GtkWidget *parent;
 
+  GSList *captured_events;
+
 #ifdef G_ENABLE_DEBUG
   /* Number of gtk_widget_push_verify_invariants () */
   guint verifying_invariants_count;
@@ -1866,21 +1868,41 @@ gtk_widget_class_init (GtkWidgetClass *klass)
    * The event is propagated starting from the top-level container to
    * the widget that received the event going down the hierarchy.
    *
-   * Returns: %TRUE to stop other handlers from being invoked for the event
-   * and to cancel the emission of the ::event signal.
-   *   %FALSE to propagate the event further and to allow the emission of
-   *   the ::event signal.
+   * This signal returns a #GtkCapturedEventFlags with the handling
+   * status of the event, if %GTK_CAPTURED_EVENT_HANDLED is enabled,
+   * the event will be considered to be handled, and thus not propagated
+   * further.
    *
-   * Since: 3.2
+   * If, additionally, %GTK_CAPTURED_EVENT_STORE is enabled, the event will
+   * be stored so it can possibly be re-sent at a later stage. See
+   * gtk_widget_release_captured_events().
+   *
+   * If no flags are enabled, the captured event will be propagated even
+   * further, eventually triggering the emission of the #GtkWidget::event
+   * signal on the widget that received the event if no parent widgets
+   * captured it.
+   *
+   * <note><para>Enabling %GTK_CAPTURED_EVENT_STORE without
+   * %GTK_CAPTURED_EVENT_HANDLED is not allowed to avoid doubly
+   * event emission.</para></note>
+   *
+   * <warning><para>%GTK_CAPTURED_EVENT_STORE will not keep any track of
+   * event parity (eg. ensuring that button/key presses and releases
+   * are paired, or focus/crossing events) nor consistency, so use with
+   * discretion.</para></warning>
+   *
+   * Returns: a #GtkCapturedEventFlags specifying what to do with the event.
+   *
+   * Since: 3.4
    */
   widget_signals[CAPTURED_EVENT] =
     g_signal_new (I_("captured-event"),
 		  G_TYPE_FROM_CLASS (klass),
 		  G_SIGNAL_RUN_LAST,
 		  G_STRUCT_OFFSET (GtkWidgetClass, captured_event),
-		  _gtk_boolean_handled_accumulator, NULL,
-		  _gtk_marshal_BOOLEAN__BOXED,
-		  G_TYPE_BOOLEAN, 1,
+		  _gtk_captured_enum_accumulator, NULL,
+		  _gtk_marshal_FLAGS__BOXED,
+		  GTK_TYPE_CAPTURED_EVENT_FLAGS, 1,
 		  GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);
 
   /**
@@ -5971,11 +5993,15 @@ gboolean
 _gtk_widget_captured_event (GtkWidget *widget,
                             GdkEvent  *event)
 {
-  gboolean return_val = FALSE;
+  GtkCapturedEventFlags flags;
+  GtkWidgetPrivate *priv;
+  gboolean return_val;
 
   g_return_val_if_fail (GTK_IS_WIDGET (widget), TRUE);
   g_return_val_if_fail (WIDGET_REALIZED_FOR_EVENT (widget, event), TRUE);
 
+  priv = widget->priv;
+
   if (event->type == GDK_EXPOSE)
     {
       g_warning ("Events of type GDK_EXPOSE cannot be synthesized. To get "
@@ -5989,8 +6015,25 @@ _gtk_widget_captured_event (GtkWidget *widget,
 
   g_object_ref (widget);
 
-  g_signal_emit (widget, widget_signals[CAPTURED_EVENT], 0, event, &return_val);
-  return_val |= !WIDGET_REALIZED_FOR_EVENT (widget, event);
+  g_signal_emit (widget, widget_signals[CAPTURED_EVENT], 0, event, &flags);
+
+  /* Only store events that have been handled, so we don't end up
+   * sending twice the same event.
+   */
+  if (flags & GTK_CAPTURED_EVENT_STORE)
+    {
+      if ((flags & GTK_CAPTURED_EVENT_HANDLED) != 0)
+        priv->captured_events = g_slist_prepend (priv->captured_events,
+                                                 gdk_event_copy (event));
+      else
+        g_warning ("Captured events can only be stored if they are claimed "
+                   "to be handled by the capturing widget. The use of "
+                   "GTK_CAPTURED_EVENT_HANDLED is mandatory if using "
+                   "GTK_CAPTURED_EVENT_STORE.");
+    }
+
+  return_val = (flags & GTK_CAPTURED_EVENT_HANDLED) != 0 ||
+    !WIDGET_REALIZED_FOR_EVENT (widget, event);
 
   g_object_unref (widget);
 
@@ -10621,6 +10664,12 @@ gtk_widget_finalize (GObject *object)
 
   _gtk_widget_free_cached_sizes (widget);
 
+  if (priv->captured_events)
+    {
+      g_slist_foreach (priv->captured_events, (GFunc) gdk_event_free, NULL);
+      g_slist_free (priv->captured_events);
+    }
+
   if (g_object_is_floating (object))
     g_warning ("A floating object was finalized. This means that someone\n"
                "called g_object_unref() on an object that had only a floating\n"
@@ -14368,3 +14417,68 @@ _gtk_widget_set_style (GtkWidget *widget,
 {
   widget->priv->style = style;
 }
+
+/**
+ * gtk_widget_release_captured_events:
+ * @widget: the #GtkWidget holding the events
+ * @emit: #TRUE if the events must be emitted on the targed widget
+ *
+ * Releases the events that a widget has captured and stored
+ * in the #GtkWidget::captured-event signal. if @emit is #TRUE,
+ * the events will be emitted on the target widget (the widget
+ * that would receive the event if no signal capturing happened)
+ *
+ * Since: 3.4
+ **/
+void
+gtk_widget_release_captured_events (GtkWidget *widget,
+                                    gboolean   emit)
+{
+  GtkWidgetPrivate *priv;
+  GSList *l;
+
+  g_return_if_fail (GTK_IS_WIDGET (widget));
+
+  priv = widget->priv;
+
+  if (emit)
+    {
+      priv->captured_events = g_slist_reverse (priv->captured_events);
+
+      for (l = priv->captured_events; l; l = l->next)
+        {
+          GtkWidget *event_widget;
+          GdkEvent *event = l->data;
+
+          event_widget = gtk_get_event_widget (event);
+
+          switch (event->type)
+            {
+            case GDK_PROPERTY_NOTIFY:
+            case GDK_FOCUS_CHANGE:
+            case GDK_CONFIGURE:
+            case GDK_MAP:
+            case GDK_UNMAP:
+            case GDK_SELECTION_CLEAR:
+            case GDK_SELECTION_REQUEST:
+            case GDK_SELECTION_NOTIFY:
+            case GDK_CLIENT_EVENT:
+            case GDK_VISIBILITY_NOTIFY:
+            case GDK_WINDOW_STATE:
+            case GDK_GRAB_BROKEN:
+            case GDK_DAMAGE:
+              /* These events are capturable, but don't bubble up */
+              gtk_widget_event (event_widget, event);
+              break;
+            default:
+              /* All other capturable events do bubble up */
+              gtk_propagate_event (event_widget, event);
+              break;
+            }
+        }
+    }
+
+  g_slist_foreach (priv->captured_events, (GFunc) gdk_event_free, NULL);
+  g_slist_free (priv->captured_events);
+  priv->captured_events = NULL;
+}
diff --git a/gtk/gtkwidget.h b/gtk/gtkwidget.h
index 32fc5bb..653c188 100644
--- a/gtk/gtkwidget.h
+++ b/gtk/gtkwidget.h
@@ -432,8 +432,9 @@ struct _GtkWidgetClass
 
   void         (* style_updated)          (GtkWidget *widget);
 
-  void         (* captured_event)         (GtkWidget *widget,
-                                           GdkEvent  *event);
+  GtkCapturedEventFlags (* captured_event) (GtkWidget *widget,
+                                            GdkEvent  *event);
+
   gboolean     (* press_and_hold)         (GtkWidget             *widget,
                                            GdkDevice             *device,
                                            GtkPressAndHoldAction  action,
@@ -904,6 +905,8 @@ GtkWidgetPath *   gtk_widget_get_path (GtkWidget *widget);
 GdkModifierType   gtk_widget_get_modifier_mask (GtkWidget         *widget,
                                                 GdkModifierIntent  intent);
 
+void              gtk_widget_release_captured_events (GtkWidget *widget,
+                                                      gboolean   emit);
 
 G_END_DECLS
 



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