[gtk+/gestures: 17/92] widget: Implement hierarchy-level mechanism to claim/deny sequences



commit 1ca2f88da994c5889fe4156521463ff1e9005dcc
Author: Carlos Garnacho <carlosg gnome org>
Date:   Mon Mar 3 14:08:45 2014 +0100

    widget: Implement hierarchy-level mechanism to claim/deny sequences
    
    The policy of sequence states has been made tighter on GtkGesture,
    so gestures can never return to a "none" state, nor get out of a
    "denied" state, a "claimed" sequence can go "denied" though.
    
    The helper API at the widget level will first emit
    GtkWidget::sequence-state-changed on the called widget, and then
    notify through the same signal to every other widget in the captured
    event chain. So the effect of that signal is twofold, on one hand
    it lets the original widget set the state on its attached controllers,
    and on the other hand it lets the other widgets freely adapt to the
    sequence state changing elsewhere in the event widget chain.
    
    By default, that signal updates every controller on the first usecase,
    and propagates the default gesture policy to every other widget in the
    chain on the second. This means that, by default:
    
    1) Sequences start out on the "none" state, and get propagated through
       all the event widget chain.
    2) If a widget in the chain denies the sequence, all other widgets are
       unaffected.
    3) If a widget in the chain claims the sequence, then:
      3.1) Every widget below the claiming widget (ie. towards the event widget)
           will get the sequence cancelled.
      3.2) Every widget above the claiming widget that had the sequence as "none"
           will remain as such, if it was claimed it will go denied, but that should
           rarely happen.
    
    This behavior can be tweaked through the GtkWidget::sequence-state-changed and
    GtkGesture::event-handled vmethods, although this should be very rarely done.

 gtk/gtkgesture.c |   63 ++++++++++++++++++++++++++-
 gtk/gtkgesture.h |    7 +++-
 gtk/gtkwidget.c  |  124 +++++++++++++++++++++++++++++++++++++++++++++++-------
 3 files changed, 176 insertions(+), 18 deletions(-)
---
diff --git a/gtk/gtkgesture.c b/gtk/gtkgesture.c
index 27cc1f5..e86e6d4 100644
--- a/gtk/gtkgesture.c
+++ b/gtk/gtkgesture.c
@@ -37,6 +37,7 @@ enum {
   BEGIN,
   END,
   UPDATE,
+  CANCEL,
   SEQUENCE_STATE_CHANGED,
   N_SIGNALS
 };
@@ -403,6 +404,13 @@ gtk_gesture_class_init (GtkGestureClass *klass)
                   G_STRUCT_OFFSET (GtkGestureClass, update),
                   NULL, NULL, NULL,
                   G_TYPE_NONE, 1, G_TYPE_POINTER);
+  signals[CANCEL] =
+    g_signal_new ("cancel",
+                  G_TYPE_FROM_CLASS (klass),
+                  G_SIGNAL_RUN_LAST,
+                  G_STRUCT_OFFSET (GtkGestureClass, cancel),
+                  NULL, NULL, NULL,
+                  G_TYPE_NONE, 1, G_TYPE_POINTER);
   signals[SEQUENCE_STATE_CHANGED] =
     g_signal_new ("sequence-state-changed",
                   G_TYPE_FROM_CLASS (klass),
@@ -505,9 +513,20 @@ gtk_gesture_get_sequence_state (GtkGesture       *gesture,
  * @sequence: a #GdkEventSequence
  * @state: the sequence state
  *
- * Sets the state of @sequence in @gesture.
+ * Sets the state of @sequence in @gesture. Sequences start
+ * in state #GTK_EVENT_SEQUENCE_NONE, and whenever they change
+ * state, they can never go back to that state. Likewise,
+ * sequences in state #GTK_EVENT_SEQUENCE_DENIED cannot turn
+ * back to a not denied state. With these rules, the lifetime
+ * of an event sequence is constrained to the next four:
  *
- * Returns: #TRUE if @sequence is handled by @gesture.
+ * * None
+ * * None → Denied
+ * * None → Claimed
+ * * None → Claimed → Denied
+ *
+ * Returns: #TRUE if @sequence is handled by @gesture,
+ *          and the state is changed successfully.
  *
  * Since: 3.14
  **/
@@ -530,6 +549,15 @@ gtk_gesture_set_sequence_state (GtkGesture            *gesture,
   if (!data || data->state == state)
     return FALSE;
 
+  /* denied sequences remain denied */
+  if (data->state == GTK_EVENT_SEQUENCE_DENIED)
+    return FALSE;
+
+  /* Sequences can't go from claimed/denied to none */
+  if (state == GTK_EVENT_SEQUENCE_NONE &&
+      data->state != GTK_EVENT_SEQUENCE_NONE)
+    return FALSE;
+
   old_state = data->state;
   data->state = state;
   g_signal_emit (gesture, signals[SEQUENCE_STATE_CHANGED], 0,
@@ -962,3 +990,34 @@ gtk_gesture_handles_sequence (GtkGesture       *gesture,
 
   return g_hash_table_contains (priv->points, sequence);
 }
+
+/**
+ * gtk_gesture_cancel_sequence:
+ * @gesture: a #GtkGesture
+ * @sequence: a #GdkEventSequence
+ *
+ * Cancels @sequence on @gesture, this emits #GtkGesture::cancel
+ * and forgets the sequence altogether.
+ *
+ * Returns: #TRUE if the sequence was being handled by gesture
+ **/
+gboolean
+gtk_gesture_cancel_sequence (GtkGesture       *gesture,
+                             GdkEventSequence *sequence)
+{
+  GtkGesturePrivate *priv;
+  PointData *data;
+
+  g_return_val_if_fail (GTK_IS_GESTURE (gesture), FALSE);
+
+  priv = gtk_gesture_get_instance_private (gesture);
+  data = g_hash_table_lookup (priv->points, sequence);
+
+  if (!data)
+    return FALSE;
+
+  g_signal_emit (gesture, signals[CANCEL], 0, sequence);
+  _gtk_gesture_check_recognized (gesture, sequence);
+  _gtk_gesture_remove_point (gesture, data->event);
+  return TRUE;
+}
diff --git a/gtk/gtkgesture.h b/gtk/gtkgesture.h
index a9296c7..1d28000 100644
--- a/gtk/gtkgesture.h
+++ b/gtk/gtkgesture.h
@@ -57,6 +57,9 @@ struct _GtkGestureClass
   void     (* end)    (GtkGesture       *gesture,
                        GdkEventSequence *sequence);
 
+  void     (* cancel) (GtkGesture       *gesture,
+                       GdkEventSequence *sequence);
+
   void     (* sequence_state_changed) (GtkGesture            *gesture,
                                        GdkEventSequence      *sequence,
                                        GtkEventSequenceState  state);
@@ -89,7 +92,9 @@ GdkEventSequence * gtk_gesture_get_last_updated_sequence
 GDK_AVAILABLE_IN_3_14
 gboolean    gtk_gesture_handles_sequence     (GtkGesture       *gesture,
                                               GdkEventSequence *sequence);
-
+GDK_AVAILABLE_IN_3_14
+gboolean    gtk_gesture_cancel_sequence      (GtkGesture       *gesture,
+                                              GdkEventSequence *sequence);
 GDK_AVAILABLE_IN_3_14
 const GdkEvent *
             gtk_gesture_get_last_event       (GtkGesture       *gesture,
diff --git a/gtk/gtkwidget.c b/gtk/gtkwidget.c
index 205aa12..f4da96d 100644
--- a/gtk/gtkwidget.c
+++ b/gtk/gtkwidget.c
@@ -592,6 +592,7 @@ enum {
   DRAG_FAILED,
   STYLE_UPDATED,
   TOUCH_EVENT,
+  SEQUENCE_STATE_CHANGED,
   LAST_SIGNAL
 };
 
@@ -3427,6 +3428,14 @@ G_GNUC_END_IGNORE_DEPRECATIONS
                  _gtk_marshal_BOOLEAN__UINT,
                   G_TYPE_BOOLEAN, 1, G_TYPE_UINT);
 
+  widget_signals[SEQUENCE_STATE_CHANGED] =
+    g_signal_new (I_("sequence-state-changed"),
+                  G_TYPE_FROM_CLASS (klass),
+                  G_SIGNAL_RUN_LAST, 0,
+                  g_signal_accumulator_first_wins, NULL, NULL,
+                  G_TYPE_BOOLEAN, 3, GTK_TYPE_WIDGET,
+                  G_TYPE_POINTER, GTK_TYPE_EVENT_SEQUENCE_STATE);
+
   binding_set = gtk_binding_set_by_class (klass);
   gtk_binding_entry_add_signal (binding_set, GDK_KEY_F10, GDK_SHIFT_MASK,
                                 "popup-menu", 0);
@@ -4000,6 +4009,78 @@ gtk_widget_get_property (GObject         *object,
     }
 }
 
+static gboolean
+_gtk_widget_set_sequence_state_internal (GtkWidget             *widget,
+                                         GdkEventSequence      *sequence,
+                                         GtkEventSequenceState  state)
+{
+  GtkWidgetPrivate *priv = widget->priv;
+  EventControllerData *data;
+  gboolean handled = FALSE;
+  GList *l;
+
+  for (l = priv->event_controllers; l; l = l->next)
+    {
+      data = l->data;
+
+      if (!GTK_IS_GESTURE (data->controller))
+        continue;
+
+      handled |= gtk_gesture_set_sequence_state (GTK_GESTURE (data->controller),
+                                                 sequence, state);
+    }
+
+  return handled;
+}
+
+static gboolean
+_gtk_widget_cancel_sequence (GtkWidget *widget,
+                             GdkEventSequence *sequence)
+{
+  GtkWidgetPrivate *priv = widget->priv;
+  EventControllerData *data;
+  gboolean handled = FALSE;
+  GList *l;
+
+  for (l = priv->event_controllers; l; l = l->next)
+    {
+      data = l->data;
+
+      if (!GTK_IS_GESTURE (data->controller))
+        continue;
+
+      handled |= gtk_gesture_cancel_sequence (GTK_GESTURE (data->controller),
+                                              sequence);
+    }
+
+  return handled;
+}
+
+static gboolean
+gtk_widget_real_sequence_state_changed (GtkWidget             *widget,
+                                        GtkWidget             *changed_widget,
+                                        GdkEventSequence      *sequence,
+                                        GtkEventSequenceState  state)
+{
+  GtkEventSequenceState changed_state;
+
+  if (widget == changed_widget)
+    return _gtk_widget_set_sequence_state_internal (widget, sequence, state);
+  else if (gtk_widget_is_ancestor (widget, changed_widget))
+    return _gtk_widget_cancel_sequence (widget, sequence);
+  else
+    {
+      changed_state = gtk_widget_get_sequence_state (changed_widget, sequence);
+
+      if (state == GTK_EVENT_SEQUENCE_CLAIMED &&
+          changed_state == GTK_EVENT_SEQUENCE_CLAIMED)
+        return _gtk_widget_set_sequence_state_internal (widget, sequence,
+                                                        GTK_EVENT_SEQUENCE_DENIED);
+    }
+
+  return FALSE;
+}
+
 static void
 gtk_widget_init (GtkWidget *widget)
 {
@@ -4057,6 +4138,10 @@ gtk_widget_init (GtkWidget *widget)
   priv->style = gtk_widget_get_default_style ();
   G_GNUC_END_IGNORE_DEPRECATIONS;
   g_object_ref (priv->style);
+
+  g_signal_connect (widget, "sequence-state-changed",
+                    G_CALLBACK (gtk_widget_real_sequence_state_changed),
+                    NULL);
 }
 
 
@@ -16438,27 +16523,36 @@ gtk_widget_set_sequence_state (GtkWidget             *widget,
 {
   EventControllerData *data;
   gboolean handled = FALSE;
+  GtkWidget *event_widget;
   GtkWidgetPrivate *priv;
-  GList *l;
-
-  g_return_val_if_fail (GTK_IS_WIDGET (widget),
-                        GTK_EVENT_SEQUENCE_NONE);
+  const GdkEvent *event;
 
-  priv = widget->priv;
+  g_return_if_fail (GTK_IS_WIDGET (widget));
+  g_return_if_fail (state >= GTK_EVENT_SEQUENCE_NONE &&
+                    state <= GTK_EVENT_SEQUENCE_DENIED);
 
-  for (l = priv->event_controllers; l; l = l->next)
-    {
-      data = l->data;
+  g_signal_emit (widget, widget_signals[SEQUENCE_STATE_CHANGED],
+                 0, widget, sequence, state, &handled);
 
-      if (!GTK_IS_GESTURE (data->controller))
-        continue;
+  if (!handled)
+    return;
 
-      handled |= gtk_gesture_set_sequence_state (GTK_GESTURE (data->controller),
-                                                 sequence, state);
-    }
+  priv = widget->priv;
+  data = priv->event_controllers->data;
+  event = gtk_gesture_get_last_event (GTK_GESTURE (data->controller), sequence);
 
-  if (!handled)
+  if (!event)
     return;
 
-  /* FIXME: Propagate upwards/downwards */
+  event_widget = gtk_get_event_widget ((GdkEvent *) event);
+  g_assert (widget == event_widget ||
+            gtk_widget_is_ancestor (event_widget, widget));
+
+  while (event_widget)
+    {
+      if (event_widget != widget)
+        g_signal_emit (event_widget, widget_signals[SEQUENCE_STATE_CHANGED],
+                       0, widget, sequence, state, &handled);
+      event_widget = gtk_widget_get_parent (event_widget);
+    }
 }


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