1843 lines
		
	
	
		
			53 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			1843 lines
		
	
	
		
			53 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/* GTK - The GIMP Toolkit
 | 
						|
 * Copyright (C) 2012, One Laptop Per Child.
 | 
						|
 * Copyright (C) 2014, 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): Carlos Garnacho <carlosg@gnome.org>
 | 
						|
 */
 | 
						|
 | 
						|
/**
 | 
						|
 * SECTION:gtkgesture
 | 
						|
 * @Short_description: Base class for gestures
 | 
						|
 * @Title: GtkGesture
 | 
						|
 * @See_also: #GtkEventController, #GtkGestureSingle
 | 
						|
 *
 | 
						|
 * #GtkGesture is the base object for gesture recognition, although this
 | 
						|
 * object is quite generalized to serve as a base for multi-touch gestures,
 | 
						|
 * it is suitable to implement single-touch and pointer-based gestures (using
 | 
						|
 * the special %NULL #GdkEventSequence value for these).
 | 
						|
 *
 | 
						|
 * The number of touches that a #GtkGesture need to be recognized is controlled
 | 
						|
 * by the #GtkGesture:n-points property, if a gesture is keeping track of less
 | 
						|
 * or more than that number of sequences, it won't check wether the gesture
 | 
						|
 * is recognized.
 | 
						|
 *
 | 
						|
 * As soon as the gesture has the expected number of touches, the gesture will
 | 
						|
 * run the #GtkGesture::check signal regularly on input events until the gesture
 | 
						|
 * is recognized, the criteria to consider a gesture as "recognized" is left to
 | 
						|
 * #GtkGesture subclasses.
 | 
						|
 *
 | 
						|
 * A recognized gesture will then emit the following signals:
 | 
						|
 * - #GtkGesture::begin when the gesture is recognized.
 | 
						|
 * - A number of #GtkGesture::update, whenever an input event is processed.
 | 
						|
 * - #GtkGesture::end when the gesture is no longer recognized.
 | 
						|
 *
 | 
						|
 * ## Event propagation
 | 
						|
 *
 | 
						|
 * In order to receive events, a gesture needs to either set a propagation phase
 | 
						|
 * through gtk_event_controller_set_propagation_phase(), or feed those manually
 | 
						|
 * through gtk_event_controller_handle_event().
 | 
						|
 *
 | 
						|
 * In the capture phase, events are propagated from the toplevel down to the
 | 
						|
 * target widget, and gestures that are attached to containers above the widget
 | 
						|
 * get a chance to interact with the event before it reaches the target.
 | 
						|
 *
 | 
						|
 * After the capture phase, GTK+ emits the traditional #GtkWidget::button-press-event,
 | 
						|
 * #GtkWidget::button-release-event, #GtkWidget::touch-event, etc signals. Gestures
 | 
						|
 * with the %GTK_PHASE_TARGET phase are fed events from the default #GtkWidget::event
 | 
						|
 * handlers.
 | 
						|
 *
 | 
						|
 * In the bubble phase, events are propagated up from the target widget to the
 | 
						|
 * toplevel, and gestures that are attached to containers above the widget get
 | 
						|
 * a chance to interact with events that have not been handled yet.
 | 
						|
 *
 | 
						|
 * ## States of a sequence # {#touch-sequence-states}
 | 
						|
 *
 | 
						|
 * Whenever input interaction happens, a single event may trigger a cascade of
 | 
						|
 * #GtkGestures, both across the parents of the widget receiving the event and
 | 
						|
 * in parallel within an individual widget. It is a responsibility of the
 | 
						|
 * widgets using those gestures to set the state of touch sequences accordingly
 | 
						|
 * in order to enable cooperation of gestures around the #GdkEventSequences
 | 
						|
 * triggering those.
 | 
						|
 *
 | 
						|
 * Within a widget, gestures can be grouped through gtk_gesture_group(),
 | 
						|
 * grouped gestures synchronize the state of sequences, so calling
 | 
						|
 * gtk_gesture_set_sequence_state() on one will effectively propagate
 | 
						|
 * the state throughout the group.
 | 
						|
 *
 | 
						|
 * By default, all sequences start out in the #GTK_EVENT_SEQUENCE_NONE state,
 | 
						|
 * sequences in this state trigger the gesture event handler, but event
 | 
						|
 * propagation will continue unstopped by gestures.
 | 
						|
 *
 | 
						|
 * If a sequence enters into the #GTK_EVENT_SEQUENCE_DENIED state, the gesture
 | 
						|
 * group will effectively ignore the sequence, letting events go unstopped
 | 
						|
 * through the gesture, but the "slot" will still remain occupied while
 | 
						|
 * the touch is active.
 | 
						|
 *
 | 
						|
 * If a sequence enters in the #GTK_EVENT_SEQUENCE_CLAIMED state, the gesture
 | 
						|
 * group will grab all interaction on the sequence, by:
 | 
						|
 * - Setting the same sequence to #GTK_EVENT_SEQUENCE_DENIED on every other gesture
 | 
						|
 *   group within the widget, and every gesture on parent widgets in the propagation
 | 
						|
 *   chain.
 | 
						|
 * - calling #GtkGesture::cancel on every gesture in widgets underneath in the
 | 
						|
 *   propagation chain.
 | 
						|
 * - Stopping event propagation after the gesture group handles the event.
 | 
						|
 *
 | 
						|
 * Note: if a sequence is set early to #GTK_EVENT_SEQUENCE_CLAIMED on
 | 
						|
 * #GDK_TOUCH_BEGIN/#GDK_BUTTON_PRESS (so those events are captured before
 | 
						|
 * reaching the event widget, this implies #GTK_PHASE_CAPTURE), one similar
 | 
						|
 * event will emulated if the sequence changes to #GTK_EVENT_SEQUENCE_DENIED.
 | 
						|
 * This way event coherence is preserved before event propagation is unstopped
 | 
						|
 * again.
 | 
						|
 *
 | 
						|
 * Sequence states can't be changed freely, see gtk_gesture_set_sequence_state()
 | 
						|
 * to know about the possible lifetimes of a #GdkEventSequence.
 | 
						|
 *
 | 
						|
 * ## Touchpad gestures
 | 
						|
 *
 | 
						|
 * On the platforms that support it, #GtkGesture will handle transparently
 | 
						|
 * touchpad gesture events. The only precautions users of #GtkGesture should do
 | 
						|
 * to enable this support are:
 | 
						|
 * - Enabling %GDK_TOUCHPAD_GESTURE_MASK on their #GdkWindows
 | 
						|
 * - If the gesture has %GTK_PHASE_NONE, ensuring events of type
 | 
						|
 *   %GDK_TOUCHPAD_SWIPE and %GDK_TOUCHPAD_PINCH are handled by the #GtkGesture
 | 
						|
 */
 | 
						|
 | 
						|
#include "config.h"
 | 
						|
#include "gtkgesture.h"
 | 
						|
#include "gtkwidgetprivate.h"
 | 
						|
#include "gtkeventcontrollerprivate.h"
 | 
						|
#include "gtkgestureprivate.h"
 | 
						|
#include "gtktypebuiltins.h"
 | 
						|
#include "gtkprivate.h"
 | 
						|
#include "gtkmain.h"
 | 
						|
#include "gtkintl.h"
 | 
						|
#include "gtkmarshalers.h"
 | 
						|
 | 
						|
typedef struct _GtkGesturePrivate GtkGesturePrivate;
 | 
						|
typedef struct _PointData PointData;
 | 
						|
 | 
						|
enum {
 | 
						|
  PROP_N_POINTS = 1,
 | 
						|
  PROP_WINDOW
 | 
						|
};
 | 
						|
 | 
						|
enum {
 | 
						|
  BEGIN,
 | 
						|
  END,
 | 
						|
  UPDATE,
 | 
						|
  CANCEL,
 | 
						|
  SEQUENCE_STATE_CHANGED,
 | 
						|
  N_SIGNALS
 | 
						|
};
 | 
						|
 | 
						|
struct _PointData
 | 
						|
{
 | 
						|
  GdkEvent *event;
 | 
						|
  gdouble widget_x;
 | 
						|
  gdouble widget_y;
 | 
						|
 | 
						|
  /* Acummulators for touchpad events */
 | 
						|
  gdouble accum_dx;
 | 
						|
  gdouble accum_dy;
 | 
						|
 | 
						|
  guint press_handled : 1;
 | 
						|
  guint state : 2;
 | 
						|
};
 | 
						|
 | 
						|
struct _GtkGesturePrivate
 | 
						|
{
 | 
						|
  GHashTable *points;
 | 
						|
  GdkEventSequence *last_sequence;
 | 
						|
  GdkWindow *user_window;
 | 
						|
  GdkWindow *window;
 | 
						|
  GdkDevice *device;
 | 
						|
  GList *group_link;
 | 
						|
  guint n_points;
 | 
						|
  guint recognized : 1;
 | 
						|
  guint touchpad : 1;
 | 
						|
};
 | 
						|
 | 
						|
static guint signals[N_SIGNALS] = { 0 };
 | 
						|
 | 
						|
#define BUTTONS_MASK (GDK_BUTTON1_MASK | GDK_BUTTON2_MASK | GDK_BUTTON3_MASK)
 | 
						|
 | 
						|
#define EVENT_IS_TOUCHPAD_GESTURE(e) ((e)->type == GDK_TOUCHPAD_SWIPE || \
 | 
						|
                                      (e)->type == GDK_TOUCHPAD_PINCH)
 | 
						|
 | 
						|
GList * _gtk_gesture_get_group_link (GtkGesture *gesture);
 | 
						|
 | 
						|
G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (GtkGesture, gtk_gesture, GTK_TYPE_EVENT_CONTROLLER)
 | 
						|
 | 
						|
static void
 | 
						|
gtk_gesture_get_property (GObject    *object,
 | 
						|
                          guint       prop_id,
 | 
						|
                          GValue     *value,
 | 
						|
                          GParamSpec *pspec)
 | 
						|
{
 | 
						|
  GtkGesturePrivate *priv = gtk_gesture_get_instance_private (GTK_GESTURE (object));
 | 
						|
 | 
						|
  switch (prop_id)
 | 
						|
    {
 | 
						|
    case PROP_N_POINTS:
 | 
						|
      g_value_set_uint (value, priv->n_points);
 | 
						|
      break;
 | 
						|
    case PROP_WINDOW:
 | 
						|
      g_value_set_object (value, priv->user_window);
 | 
						|
      break;
 | 
						|
    default:
 | 
						|
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
gtk_gesture_set_property (GObject      *object,
 | 
						|
                          guint         prop_id,
 | 
						|
                          const GValue *value,
 | 
						|
                          GParamSpec   *pspec)
 | 
						|
{
 | 
						|
  GtkGesturePrivate *priv = gtk_gesture_get_instance_private (GTK_GESTURE (object));
 | 
						|
 | 
						|
  switch (prop_id)
 | 
						|
    {
 | 
						|
    case PROP_N_POINTS:
 | 
						|
      priv->n_points = g_value_get_uint (value);
 | 
						|
      break;
 | 
						|
    case PROP_WINDOW:
 | 
						|
      gtk_gesture_set_window (GTK_GESTURE (object),
 | 
						|
                              g_value_get_object (value));
 | 
						|
      break;
 | 
						|
    default:
 | 
						|
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
gtk_gesture_finalize (GObject *object)
 | 
						|
{
 | 
						|
  GtkGesture *gesture = GTK_GESTURE (object);
 | 
						|
  GtkGesturePrivate *priv = gtk_gesture_get_instance_private (gesture);
 | 
						|
 | 
						|
  gtk_gesture_ungroup (gesture);
 | 
						|
  g_list_free (priv->group_link);
 | 
						|
 | 
						|
  g_hash_table_destroy (priv->points);
 | 
						|
 | 
						|
  G_OBJECT_CLASS (gtk_gesture_parent_class)->finalize (object);
 | 
						|
}
 | 
						|
 | 
						|
static guint
 | 
						|
_gtk_gesture_get_n_touchpad_points (GtkGesture *gesture,
 | 
						|
                                    gboolean    only_active)
 | 
						|
{
 | 
						|
  GtkGesturePrivate *priv;
 | 
						|
  PointData *data;
 | 
						|
 | 
						|
  priv = gtk_gesture_get_instance_private (gesture);
 | 
						|
 | 
						|
  if (!priv->touchpad)
 | 
						|
    return 0;
 | 
						|
 | 
						|
  data = g_hash_table_lookup (priv->points, NULL);
 | 
						|
 | 
						|
  if (!data)
 | 
						|
    return 0;
 | 
						|
 | 
						|
  if (only_active &&
 | 
						|
      (data->state == GTK_EVENT_SEQUENCE_DENIED ||
 | 
						|
       (data->event->type == GDK_TOUCHPAD_SWIPE &&
 | 
						|
        data->event->touchpad_swipe.phase == GDK_TOUCHPAD_GESTURE_PHASE_END) ||
 | 
						|
       (data->event->type == GDK_TOUCHPAD_PINCH &&
 | 
						|
        data->event->touchpad_pinch.phase == GDK_TOUCHPAD_GESTURE_PHASE_END)))
 | 
						|
    return 0;
 | 
						|
 | 
						|
  switch (data->event->type)
 | 
						|
    {
 | 
						|
    case GDK_TOUCHPAD_SWIPE:
 | 
						|
      return data->event->touchpad_swipe.n_fingers;
 | 
						|
    case GDK_TOUCHPAD_PINCH:
 | 
						|
      return data->event->touchpad_pinch.n_fingers;
 | 
						|
    default:
 | 
						|
      return 0;
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
static guint
 | 
						|
_gtk_gesture_get_n_touch_points (GtkGesture *gesture,
 | 
						|
                                 gboolean    only_active)
 | 
						|
{
 | 
						|
  GtkGesturePrivate *priv;
 | 
						|
  GHashTableIter iter;
 | 
						|
  guint n_points = 0;
 | 
						|
  PointData *data;
 | 
						|
 | 
						|
  priv = gtk_gesture_get_instance_private (gesture);
 | 
						|
  g_hash_table_iter_init (&iter, priv->points);
 | 
						|
 | 
						|
  while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &data))
 | 
						|
    {
 | 
						|
      if (only_active &&
 | 
						|
          (data->state == GTK_EVENT_SEQUENCE_DENIED ||
 | 
						|
           data->event->type == GDK_TOUCH_END ||
 | 
						|
           data->event->type == GDK_BUTTON_RELEASE))
 | 
						|
        continue;
 | 
						|
 | 
						|
      n_points++;
 | 
						|
    }
 | 
						|
 | 
						|
  return n_points;
 | 
						|
}
 | 
						|
 | 
						|
static guint
 | 
						|
_gtk_gesture_get_n_physical_points (GtkGesture *gesture,
 | 
						|
                                    gboolean    only_active)
 | 
						|
{
 | 
						|
  GtkGesturePrivate *priv;
 | 
						|
 | 
						|
  priv = gtk_gesture_get_instance_private (gesture);
 | 
						|
 | 
						|
  if (priv->touchpad)
 | 
						|
    return _gtk_gesture_get_n_touchpad_points (gesture, only_active);
 | 
						|
  else
 | 
						|
    return _gtk_gesture_get_n_touch_points (gesture, only_active);
 | 
						|
}
 | 
						|
 | 
						|
static gboolean
 | 
						|
gtk_gesture_check_impl (GtkGesture *gesture)
 | 
						|
{
 | 
						|
  GtkGesturePrivate *priv;
 | 
						|
  guint n_points;
 | 
						|
 | 
						|
  priv = gtk_gesture_get_instance_private (gesture);
 | 
						|
  n_points = _gtk_gesture_get_n_physical_points (gesture, TRUE);
 | 
						|
 | 
						|
  return n_points == priv->n_points;
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
_gtk_gesture_set_recognized (GtkGesture       *gesture,
 | 
						|
                             gboolean          recognized,
 | 
						|
                             GdkEventSequence *sequence)
 | 
						|
{
 | 
						|
  GtkGesturePrivate *priv;
 | 
						|
 | 
						|
  priv = gtk_gesture_get_instance_private (gesture);
 | 
						|
 | 
						|
  if (priv->recognized == recognized)
 | 
						|
    return;
 | 
						|
 | 
						|
  priv->recognized = recognized;
 | 
						|
 | 
						|
  if (recognized)
 | 
						|
    g_signal_emit (gesture, signals[BEGIN], 0, sequence);
 | 
						|
  else
 | 
						|
    g_signal_emit (gesture, signals[END], 0, sequence);
 | 
						|
}
 | 
						|
 | 
						|
static gboolean
 | 
						|
_gtk_gesture_do_check (GtkGesture *gesture)
 | 
						|
{
 | 
						|
  GtkGestureClass *gesture_class;
 | 
						|
  gboolean retval = FALSE;
 | 
						|
 | 
						|
  gesture_class = GTK_GESTURE_GET_CLASS (gesture);
 | 
						|
 | 
						|
  if (!gesture_class->check)
 | 
						|
    return retval;
 | 
						|
 | 
						|
  retval = gesture_class->check (gesture);
 | 
						|
  return retval;
 | 
						|
}
 | 
						|
 | 
						|
static gboolean
 | 
						|
_gtk_gesture_has_matching_touchpoints (GtkGesture *gesture)
 | 
						|
{
 | 
						|
  GtkGesturePrivate *priv = gtk_gesture_get_instance_private (gesture);
 | 
						|
  guint active_n_points, current_n_points;
 | 
						|
 | 
						|
  current_n_points = _gtk_gesture_get_n_physical_points (gesture, FALSE);
 | 
						|
  active_n_points = _gtk_gesture_get_n_physical_points (gesture, TRUE);
 | 
						|
 | 
						|
  return (active_n_points == priv->n_points &&
 | 
						|
          current_n_points == priv->n_points);
 | 
						|
}
 | 
						|
 | 
						|
static gboolean
 | 
						|
_gtk_gesture_check_recognized (GtkGesture       *gesture,
 | 
						|
                               GdkEventSequence *sequence)
 | 
						|
{
 | 
						|
  GtkGesturePrivate *priv = gtk_gesture_get_instance_private (gesture);
 | 
						|
  gboolean has_matching_touchpoints;
 | 
						|
 | 
						|
  has_matching_touchpoints = _gtk_gesture_has_matching_touchpoints (gesture);
 | 
						|
 | 
						|
  if (priv->recognized && !has_matching_touchpoints)
 | 
						|
    _gtk_gesture_set_recognized (gesture, FALSE, sequence);
 | 
						|
  else if (!priv->recognized && has_matching_touchpoints &&
 | 
						|
           _gtk_gesture_do_check (gesture))
 | 
						|
    _gtk_gesture_set_recognized (gesture, TRUE, sequence);
 | 
						|
 | 
						|
  return priv->recognized;
 | 
						|
}
 | 
						|
 | 
						|
/* Finds the first window pertaining to the controller's widget */
 | 
						|
static GdkWindow *
 | 
						|
_find_widget_window (GtkGesture *gesture,
 | 
						|
                     GdkWindow  *window)
 | 
						|
{
 | 
						|
  GtkWidget *widget, *window_widget;
 | 
						|
 | 
						|
  widget = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (gesture));
 | 
						|
 | 
						|
  while (window && !gdk_window_is_destroyed (window))
 | 
						|
    {
 | 
						|
      gdk_window_get_user_data (window, (gpointer*) &window_widget);
 | 
						|
 | 
						|
      if (window_widget == widget ||
 | 
						|
          gtk_widget_get_window (widget) == window)
 | 
						|
        return window;
 | 
						|
 | 
						|
      window = gdk_window_get_effective_parent (window);
 | 
						|
    }
 | 
						|
 | 
						|
  return NULL;
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
_update_touchpad_deltas (PointData *data)
 | 
						|
{
 | 
						|
  GdkEvent *event = data->event;
 | 
						|
 | 
						|
  if (!event)
 | 
						|
    return;
 | 
						|
 | 
						|
  if (event->type == GDK_TOUCHPAD_SWIPE)
 | 
						|
    {
 | 
						|
      if (event->touchpad_swipe.phase == GDK_TOUCHPAD_GESTURE_PHASE_BEGIN)
 | 
						|
        data->accum_dx = data->accum_dy = 0;
 | 
						|
      else if (event->touchpad_swipe.phase == GDK_TOUCHPAD_GESTURE_PHASE_UPDATE)
 | 
						|
        {
 | 
						|
          data->accum_dx += event->touchpad_swipe.dx;
 | 
						|
          data->accum_dy += event->touchpad_swipe.dy;
 | 
						|
        }
 | 
						|
    }
 | 
						|
  else if (event->type == GDK_TOUCHPAD_PINCH)
 | 
						|
    {
 | 
						|
      if (event->touchpad_pinch.phase == GDK_TOUCHPAD_GESTURE_PHASE_BEGIN)
 | 
						|
        data->accum_dx = data->accum_dy = 0;
 | 
						|
      else if (event->touchpad_pinch.phase == GDK_TOUCHPAD_GESTURE_PHASE_UPDATE)
 | 
						|
        {
 | 
						|
          data->accum_dx += event->touchpad_pinch.dx;
 | 
						|
          data->accum_dy += event->touchpad_pinch.dy;
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
_get_event_coordinates (PointData *data,
 | 
						|
                        gdouble   *x,
 | 
						|
                        gdouble   *y)
 | 
						|
{
 | 
						|
  gdouble event_x, event_y;
 | 
						|
 | 
						|
  g_assert (data->event != NULL);
 | 
						|
 | 
						|
  gdk_event_get_coords (data->event, &event_x, &event_y);
 | 
						|
  event_x += data->accum_dx;
 | 
						|
  event_y += data->accum_dy;
 | 
						|
 | 
						|
  if (x)
 | 
						|
    *x = event_x;
 | 
						|
  if (y)
 | 
						|
    *y = event_y;
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
_update_widget_coordinates (GtkGesture *gesture,
 | 
						|
                            PointData  *data)
 | 
						|
{
 | 
						|
  GdkWindow *window, *event_widget_window;
 | 
						|
  GtkWidget *event_widget, *widget;
 | 
						|
  GtkAllocation allocation;
 | 
						|
  gdouble event_x, event_y;
 | 
						|
  gint wx, wy, x, y;
 | 
						|
 | 
						|
  event_widget = gtk_get_event_widget (data->event);
 | 
						|
 | 
						|
  if (!event_widget)
 | 
						|
    return;
 | 
						|
 | 
						|
  widget = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (gesture));
 | 
						|
  event_widget_window = gtk_widget_get_window (event_widget);
 | 
						|
  _get_event_coordinates (data, &event_x, &event_y);
 | 
						|
  window = data->event->any.window;
 | 
						|
 | 
						|
  while (window && window != event_widget_window)
 | 
						|
    {
 | 
						|
      gdk_window_get_position (window, &wx, &wy);
 | 
						|
      event_x += wx;
 | 
						|
      event_y += wy;
 | 
						|
      window = gdk_window_get_effective_parent (window);
 | 
						|
    }
 | 
						|
 | 
						|
  if (!window)
 | 
						|
    return;
 | 
						|
 | 
						|
  if (!gtk_widget_get_has_window (event_widget))
 | 
						|
    {
 | 
						|
      gtk_widget_get_allocation (event_widget, &allocation);
 | 
						|
      event_x -= allocation.x;
 | 
						|
      event_y -= allocation.y;
 | 
						|
    }
 | 
						|
 | 
						|
  gtk_widget_translate_coordinates (event_widget, widget,
 | 
						|
                                    event_x, event_y, &x, &y);
 | 
						|
  data->widget_x = x;
 | 
						|
  data->widget_y = y;
 | 
						|
}
 | 
						|
 | 
						|
static GtkEventSequenceState
 | 
						|
gtk_gesture_get_group_state (GtkGesture       *gesture,
 | 
						|
                             GdkEventSequence *sequence)
 | 
						|
{
 | 
						|
  GtkEventSequenceState state = GTK_EVENT_SEQUENCE_NONE;
 | 
						|
  GList *group_elem;
 | 
						|
 | 
						|
  group_elem = g_list_first (_gtk_gesture_get_group_link (gesture));
 | 
						|
 | 
						|
  for (; group_elem; group_elem = group_elem->next)
 | 
						|
    {
 | 
						|
      if (group_elem->data == gesture)
 | 
						|
        continue;
 | 
						|
      if (!gtk_gesture_handles_sequence (group_elem->data, sequence))
 | 
						|
        continue;
 | 
						|
 | 
						|
      state = gtk_gesture_get_sequence_state (group_elem->data, sequence);
 | 
						|
      break;
 | 
						|
    }
 | 
						|
 | 
						|
  return state;
 | 
						|
}
 | 
						|
 | 
						|
static gboolean
 | 
						|
_gtk_gesture_update_point (GtkGesture     *gesture,
 | 
						|
                           const GdkEvent *event,
 | 
						|
                           gboolean        add)
 | 
						|
{
 | 
						|
  GdkEventSequence *sequence;
 | 
						|
  GdkWindow *widget_window;
 | 
						|
  GtkGesturePrivate *priv;
 | 
						|
  GdkDevice *device;
 | 
						|
  gboolean existed, touchpad;
 | 
						|
  PointData *data;
 | 
						|
 | 
						|
  if (!gdk_event_get_coords (event, NULL, NULL))
 | 
						|
    return FALSE;
 | 
						|
 | 
						|
  device = gdk_event_get_device (event);
 | 
						|
 | 
						|
  if (!device)
 | 
						|
    return FALSE;
 | 
						|
 | 
						|
  priv = gtk_gesture_get_instance_private (gesture);
 | 
						|
  widget_window = _find_widget_window (gesture, event->any.window);
 | 
						|
 | 
						|
  if (!widget_window)
 | 
						|
    widget_window = event->any.window;
 | 
						|
 | 
						|
  touchpad = EVENT_IS_TOUCHPAD_GESTURE (event);
 | 
						|
 | 
						|
  if (add)
 | 
						|
    {
 | 
						|
      /* If the event happens with the wrong device, or
 | 
						|
       * on the wrong window, ignore.
 | 
						|
       */
 | 
						|
      if (priv->device && priv->device != device)
 | 
						|
        return FALSE;
 | 
						|
      if (priv->window && priv->window != widget_window)
 | 
						|
        return FALSE;
 | 
						|
      if (priv->user_window && priv->user_window != widget_window)
 | 
						|
        return FALSE;
 | 
						|
 | 
						|
      /* Make touchpad and touchscreen gestures mutually exclusive */
 | 
						|
      if (touchpad && g_hash_table_size (priv->points) > 0)
 | 
						|
        return FALSE;
 | 
						|
      else if (!touchpad && priv->touchpad)
 | 
						|
        return FALSE;
 | 
						|
    }
 | 
						|
  else if (!priv->device || !priv->window)
 | 
						|
    return FALSE;
 | 
						|
 | 
						|
  sequence = gdk_event_get_event_sequence (event);
 | 
						|
  existed = g_hash_table_lookup_extended (priv->points, sequence,
 | 
						|
                                          NULL, (gpointer *) &data);
 | 
						|
  if (!existed)
 | 
						|
    {
 | 
						|
      GtkEventSequenceState group_state;
 | 
						|
 | 
						|
      if (!add)
 | 
						|
        return FALSE;
 | 
						|
 | 
						|
      if (g_hash_table_size (priv->points) == 0)
 | 
						|
        {
 | 
						|
          priv->window = widget_window;
 | 
						|
          priv->device = device;
 | 
						|
          priv->touchpad = touchpad;
 | 
						|
        }
 | 
						|
 | 
						|
      data = g_new0 (PointData, 1);
 | 
						|
      g_hash_table_insert (priv->points, sequence, data);
 | 
						|
 | 
						|
      group_state = gtk_gesture_get_group_state (gesture, sequence);
 | 
						|
      gtk_gesture_set_sequence_state (gesture, sequence, group_state);
 | 
						|
    }
 | 
						|
 | 
						|
  if (data->event)
 | 
						|
    gdk_event_free (data->event);
 | 
						|
 | 
						|
  data->event = gdk_event_copy (event);
 | 
						|
  _update_touchpad_deltas (data);
 | 
						|
  _update_widget_coordinates (gesture, data);
 | 
						|
 | 
						|
  /* Deny the sequence right away if the expected
 | 
						|
   * number of points is exceeded, so this sequence
 | 
						|
   * can be tracked with gtk_gesture_handles_sequence().
 | 
						|
   */
 | 
						|
  if (!existed && _gtk_gesture_get_n_physical_points (gesture, FALSE) > priv->n_points)
 | 
						|
    gtk_gesture_set_sequence_state (gesture, sequence,
 | 
						|
                                    GTK_EVENT_SEQUENCE_DENIED);
 | 
						|
 | 
						|
  return TRUE;
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
_gtk_gesture_check_empty (GtkGesture *gesture)
 | 
						|
{
 | 
						|
  GtkGesturePrivate *priv;
 | 
						|
 | 
						|
  priv = gtk_gesture_get_instance_private (gesture);
 | 
						|
 | 
						|
  if (g_hash_table_size (priv->points) == 0)
 | 
						|
    {
 | 
						|
      priv->window = NULL;
 | 
						|
      priv->device = NULL;
 | 
						|
      priv->touchpad = FALSE;
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
_gtk_gesture_remove_point (GtkGesture     *gesture,
 | 
						|
                           const GdkEvent *event)
 | 
						|
{
 | 
						|
  GdkEventSequence *sequence;
 | 
						|
  GtkGesturePrivate *priv;
 | 
						|
  GdkDevice *device;
 | 
						|
 | 
						|
  sequence = gdk_event_get_event_sequence (event);
 | 
						|
  device = gdk_event_get_device (event);
 | 
						|
  priv = gtk_gesture_get_instance_private (gesture);
 | 
						|
 | 
						|
  if (priv->device != device)
 | 
						|
    return;
 | 
						|
 | 
						|
  g_hash_table_remove (priv->points, sequence);
 | 
						|
  _gtk_gesture_check_empty (gesture);
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
_gtk_gesture_cancel_all (GtkGesture *gesture)
 | 
						|
{
 | 
						|
  GdkEventSequence *sequence;
 | 
						|
  GtkGesturePrivate *priv;
 | 
						|
  GHashTableIter iter;
 | 
						|
 | 
						|
  priv = gtk_gesture_get_instance_private (gesture);
 | 
						|
  g_hash_table_iter_init (&iter, priv->points);
 | 
						|
 | 
						|
  while (g_hash_table_iter_next (&iter, (gpointer*) &sequence, NULL))
 | 
						|
    {
 | 
						|
      g_signal_emit (gesture, signals[CANCEL], 0, sequence);
 | 
						|
      g_hash_table_iter_remove (&iter);
 | 
						|
      _gtk_gesture_check_recognized (gesture, sequence);
 | 
						|
    }
 | 
						|
 | 
						|
  _gtk_gesture_check_empty (gesture);
 | 
						|
}
 | 
						|
 | 
						|
static gboolean
 | 
						|
gesture_within_window (GtkGesture *gesture,
 | 
						|
                       GdkWindow  *parent)
 | 
						|
{
 | 
						|
  GdkWindow *window;
 | 
						|
  GtkWidget *widget;
 | 
						|
 | 
						|
  widget = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (gesture));
 | 
						|
  window = gtk_widget_get_window (widget);
 | 
						|
 | 
						|
  while (window)
 | 
						|
    {
 | 
						|
      if (window == parent)
 | 
						|
        return TRUE;
 | 
						|
 | 
						|
      window = gdk_window_get_effective_parent (window);
 | 
						|
    }
 | 
						|
 | 
						|
  return FALSE;
 | 
						|
}
 | 
						|
 | 
						|
static gboolean
 | 
						|
gtk_gesture_filter_event (GtkEventController *controller,
 | 
						|
                          const GdkEvent     *event)
 | 
						|
{
 | 
						|
  /* Even though GtkGesture handles these events, we want
 | 
						|
   * touchpad gestures disabled by default, it will be
 | 
						|
   * subclasses which punch the holes in for the events
 | 
						|
   * they can possibly handle.
 | 
						|
   */
 | 
						|
  return EVENT_IS_TOUCHPAD_GESTURE (event);
 | 
						|
}
 | 
						|
 | 
						|
static gboolean
 | 
						|
gtk_gesture_handle_event (GtkEventController *controller,
 | 
						|
                          const GdkEvent     *event)
 | 
						|
{
 | 
						|
  GtkGesture *gesture = GTK_GESTURE (controller);
 | 
						|
  GdkEventSequence *sequence;
 | 
						|
  GtkGesturePrivate *priv;
 | 
						|
  GdkDevice *source_device;
 | 
						|
  gboolean was_recognized;
 | 
						|
 | 
						|
  source_device = gdk_event_get_source_device (event);
 | 
						|
 | 
						|
  if (!source_device)
 | 
						|
    return FALSE;
 | 
						|
 | 
						|
  priv = gtk_gesture_get_instance_private (gesture);
 | 
						|
  sequence = gdk_event_get_event_sequence (event);
 | 
						|
  was_recognized = gtk_gesture_is_recognized (gesture);
 | 
						|
 | 
						|
  if (gtk_gesture_get_sequence_state (gesture, sequence) != GTK_EVENT_SEQUENCE_DENIED)
 | 
						|
    priv->last_sequence = sequence;
 | 
						|
 | 
						|
  if (event->type == GDK_BUTTON_PRESS ||
 | 
						|
      event->type == GDK_TOUCH_BEGIN ||
 | 
						|
      (event->type == GDK_TOUCHPAD_SWIPE &&
 | 
						|
       event->touchpad_swipe.phase == GDK_TOUCHPAD_GESTURE_PHASE_BEGIN) ||
 | 
						|
      (event->type == GDK_TOUCHPAD_PINCH &&
 | 
						|
       event->touchpad_pinch.phase == GDK_TOUCHPAD_GESTURE_PHASE_BEGIN))
 | 
						|
    {
 | 
						|
      if (_gtk_gesture_update_point (gesture, event, TRUE))
 | 
						|
        {
 | 
						|
          gboolean triggered_recognition;
 | 
						|
 | 
						|
          triggered_recognition =
 | 
						|
            !was_recognized && _gtk_gesture_has_matching_touchpoints (gesture);
 | 
						|
 | 
						|
          if (_gtk_gesture_check_recognized (gesture, sequence))
 | 
						|
            {
 | 
						|
              PointData *data;
 | 
						|
 | 
						|
              data = g_hash_table_lookup (priv->points, sequence);
 | 
						|
 | 
						|
              /* If the sequence was claimed early, the press event will be consumed */
 | 
						|
              if (gtk_gesture_get_sequence_state (gesture, sequence) == GTK_EVENT_SEQUENCE_CLAIMED)
 | 
						|
                data->press_handled = TRUE;
 | 
						|
            }
 | 
						|
          else if (triggered_recognition && g_hash_table_size (priv->points) == 0)
 | 
						|
            {
 | 
						|
              /* Recognition was triggered, but the gesture reset during
 | 
						|
               * ::begin emission. Still, recognition was strictly triggered,
 | 
						|
               * so the event should be consumed.
 | 
						|
               */
 | 
						|
              return TRUE;
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
  else if (event->type == GDK_BUTTON_RELEASE ||
 | 
						|
           event->type == GDK_TOUCH_END ||
 | 
						|
           (event->type == GDK_TOUCHPAD_SWIPE &&
 | 
						|
            event->touchpad_swipe.phase == GDK_TOUCHPAD_GESTURE_PHASE_END) ||
 | 
						|
           (event->type == GDK_TOUCHPAD_PINCH &&
 | 
						|
            event->touchpad_pinch.phase == GDK_TOUCHPAD_GESTURE_PHASE_END))
 | 
						|
    {
 | 
						|
      if (_gtk_gesture_update_point (gesture, event, FALSE))
 | 
						|
        {
 | 
						|
          if (was_recognized &&
 | 
						|
              _gtk_gesture_check_recognized (gesture, sequence))
 | 
						|
            g_signal_emit (gesture, signals[UPDATE], 0, sequence);
 | 
						|
 | 
						|
          _gtk_gesture_remove_point (gesture, event);
 | 
						|
        }
 | 
						|
    }
 | 
						|
  else if (event->type == GDK_MOTION_NOTIFY ||
 | 
						|
           event->type == GDK_TOUCH_UPDATE ||
 | 
						|
           (event->type == GDK_TOUCHPAD_SWIPE &&
 | 
						|
            event->touchpad_swipe.phase == GDK_TOUCHPAD_GESTURE_PHASE_UPDATE) ||
 | 
						|
           (event->type == GDK_TOUCHPAD_PINCH &&
 | 
						|
            event->touchpad_pinch.phase == GDK_TOUCHPAD_GESTURE_PHASE_UPDATE))
 | 
						|
    {
 | 
						|
      if (event->type == GDK_MOTION_NOTIFY)
 | 
						|
        {
 | 
						|
          if ((event->motion.state & BUTTONS_MASK) == 0)
 | 
						|
            return FALSE;
 | 
						|
 | 
						|
          if (event->motion.is_hint)
 | 
						|
            gdk_event_request_motions (&event->motion);
 | 
						|
        }
 | 
						|
 | 
						|
      if (_gtk_gesture_update_point (gesture, event, FALSE) &&
 | 
						|
          _gtk_gesture_check_recognized (gesture, sequence))
 | 
						|
        g_signal_emit (gesture, signals[UPDATE], 0, sequence);
 | 
						|
    }
 | 
						|
  else if (event->type == GDK_TOUCH_CANCEL)
 | 
						|
    {
 | 
						|
      if (!priv->touchpad)
 | 
						|
        _gtk_gesture_cancel_sequence (gesture, sequence);
 | 
						|
    }
 | 
						|
  else if ((event->type == GDK_TOUCHPAD_SWIPE &&
 | 
						|
            event->touchpad_swipe.phase == GDK_TOUCHPAD_GESTURE_PHASE_CANCEL) ||
 | 
						|
           (event->type == GDK_TOUCHPAD_PINCH &&
 | 
						|
            event->touchpad_pinch.phase == GDK_TOUCHPAD_GESTURE_PHASE_CANCEL))
 | 
						|
    {
 | 
						|
      if (priv->touchpad)
 | 
						|
        _gtk_gesture_cancel_sequence (gesture, sequence);
 | 
						|
    }
 | 
						|
  else if (event->type == GDK_GRAB_BROKEN)
 | 
						|
    {
 | 
						|
      if (!event->grab_broken.grab_window ||
 | 
						|
          !gesture_within_window (gesture, event->grab_broken.grab_window))
 | 
						|
        _gtk_gesture_cancel_all (gesture);
 | 
						|
 | 
						|
      return FALSE;
 | 
						|
    }
 | 
						|
  else
 | 
						|
    {
 | 
						|
      /* Unhandled event */
 | 
						|
      return FALSE;
 | 
						|
    }
 | 
						|
 | 
						|
  if (gtk_gesture_get_sequence_state (gesture, sequence) != GTK_EVENT_SEQUENCE_CLAIMED)
 | 
						|
    return FALSE;
 | 
						|
 | 
						|
  return priv->recognized;
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
gtk_gesture_reset (GtkEventController *controller)
 | 
						|
{
 | 
						|
  _gtk_gesture_cancel_all (GTK_GESTURE (controller));
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
gtk_gesture_class_init (GtkGestureClass *klass)
 | 
						|
{
 | 
						|
  GObjectClass *object_class = G_OBJECT_CLASS (klass);
 | 
						|
  GtkEventControllerClass *controller_class = GTK_EVENT_CONTROLLER_CLASS (klass);
 | 
						|
 | 
						|
  object_class->get_property = gtk_gesture_get_property;
 | 
						|
  object_class->set_property = gtk_gesture_set_property;
 | 
						|
  object_class->finalize = gtk_gesture_finalize;
 | 
						|
 | 
						|
  controller_class->filter_event = gtk_gesture_filter_event;
 | 
						|
  controller_class->handle_event = gtk_gesture_handle_event;
 | 
						|
  controller_class->reset = gtk_gesture_reset;
 | 
						|
 | 
						|
  klass->check = gtk_gesture_check_impl;
 | 
						|
 | 
						|
  /**
 | 
						|
   * GtkGesture:n-points:
 | 
						|
   *
 | 
						|
   * The number of touch points that trigger recognition on this gesture,
 | 
						|
   * 
 | 
						|
   *
 | 
						|
   * Since: 3.14
 | 
						|
   */
 | 
						|
  g_object_class_install_property (object_class,
 | 
						|
                                   PROP_N_POINTS,
 | 
						|
                                   g_param_spec_uint ("n-points",
 | 
						|
                                                      P_("Number of points"),
 | 
						|
                                                      P_("Number of points needed "
 | 
						|
                                                         "to trigger the gesture"),
 | 
						|
                                                      1, G_MAXUINT, 1,
 | 
						|
                                                      GTK_PARAM_READWRITE |
 | 
						|
						      G_PARAM_CONSTRUCT_ONLY));
 | 
						|
  /**
 | 
						|
   * GtkGesture:window:
 | 
						|
   *
 | 
						|
   * If non-%NULL, the gesture will only listen for events that happen on
 | 
						|
   * this #GdkWindow, or a child of it.
 | 
						|
   *
 | 
						|
   * Since: 3.14
 | 
						|
   */
 | 
						|
  g_object_class_install_property (object_class,
 | 
						|
                                   PROP_WINDOW,
 | 
						|
                                   g_param_spec_object ("window",
 | 
						|
                                                        P_("GdkWindow to receive events about"),
 | 
						|
                                                        P_("GdkWindow to receive events about"),
 | 
						|
                                                        GDK_TYPE_WINDOW,
 | 
						|
                                                        GTK_PARAM_READWRITE));
 | 
						|
  /**
 | 
						|
   * GtkGesture::begin:
 | 
						|
   * @gesture: the object which received the signal
 | 
						|
   * @sequence: (nullable): the #GdkEventSequence that made the gesture to be recognized
 | 
						|
   *
 | 
						|
   * This signal is emitted when the gesture is recognized. This means the
 | 
						|
   * number of touch sequences matches #GtkGesture:n-points, and the #GtkGesture::check
 | 
						|
   * handler(s) returned #TRUE.
 | 
						|
   *
 | 
						|
   * Note: These conditions may also happen when an extra touch (eg. a third touch
 | 
						|
   * on a 2-touches gesture) is lifted, in that situation @sequence won't pertain
 | 
						|
   * to the current set of active touches, so don't rely on this being true.
 | 
						|
   *
 | 
						|
   * Since: 3.14
 | 
						|
   */
 | 
						|
  signals[BEGIN] =
 | 
						|
    g_signal_new (I_("begin"),
 | 
						|
                  G_TYPE_FROM_CLASS (klass),
 | 
						|
                  G_SIGNAL_RUN_LAST,
 | 
						|
                  G_STRUCT_OFFSET (GtkGestureClass, begin),
 | 
						|
                  NULL, NULL, NULL,
 | 
						|
                  G_TYPE_NONE, 1, GDK_TYPE_EVENT_SEQUENCE);
 | 
						|
  /**
 | 
						|
   * GtkGesture::end:
 | 
						|
   * @gesture: the object which received the signal
 | 
						|
   * @sequence: (nullable): the #GdkEventSequence that made gesture recognition to finish
 | 
						|
   *
 | 
						|
   * This signal is emitted when @gesture either stopped recognizing the event
 | 
						|
   * sequences as something to be handled (the #GtkGesture::check handler returned
 | 
						|
   * %FALSE), or the number of touch sequences became higher or lower than
 | 
						|
   * #GtkGesture:n-points.
 | 
						|
   *
 | 
						|
   * Note: @sequence might not pertain to the group of sequences that were
 | 
						|
   * previously triggering recognition on @gesture (ie. a just pressed touch
 | 
						|
   * sequence that exceeds #GtkGesture:n-points). This situation may be detected
 | 
						|
   * by checking through gtk_gesture_handles_sequence().
 | 
						|
   *
 | 
						|
   * Since: 3.14
 | 
						|
   */
 | 
						|
  signals[END] =
 | 
						|
    g_signal_new (I_("end"),
 | 
						|
                  G_TYPE_FROM_CLASS (klass),
 | 
						|
                  G_SIGNAL_RUN_LAST,
 | 
						|
                  G_STRUCT_OFFSET (GtkGestureClass, end),
 | 
						|
                  NULL, NULL, NULL,
 | 
						|
                  G_TYPE_NONE, 1, GDK_TYPE_EVENT_SEQUENCE);
 | 
						|
  /**
 | 
						|
   * GtkGesture::update:
 | 
						|
   * @gesture: the object which received the signal
 | 
						|
   * @sequence: (nullable): the #GdkEventSequence that was updated
 | 
						|
   *
 | 
						|
   * This signal is emitted whenever an event is handled while the gesture is
 | 
						|
   * recognized. @sequence is guaranteed to pertain to the set of active touches.
 | 
						|
   *
 | 
						|
   * Since: 3.14
 | 
						|
   */
 | 
						|
  signals[UPDATE] =
 | 
						|
    g_signal_new (I_("update"),
 | 
						|
                  G_TYPE_FROM_CLASS (klass),
 | 
						|
                  G_SIGNAL_RUN_LAST,
 | 
						|
                  G_STRUCT_OFFSET (GtkGestureClass, update),
 | 
						|
                  NULL, NULL, NULL,
 | 
						|
                  G_TYPE_NONE, 1, GDK_TYPE_EVENT_SEQUENCE);
 | 
						|
  /**
 | 
						|
   * GtkGesture::cancel:
 | 
						|
   * @gesture: the object which received the signal
 | 
						|
   * @sequence: (nullable): the #GdkEventSequence that was cancelled
 | 
						|
   *
 | 
						|
   * This signal is emitted whenever a sequence is cancelled. This usually
 | 
						|
   * happens on active touches when gtk_event_controller_reset() is called
 | 
						|
   * on @gesture (manually, due to grabs...), or the individual @sequence
 | 
						|
   * was claimed by parent widgets' controllers (see gtk_gesture_set_sequence_state()).
 | 
						|
   *
 | 
						|
   * @gesture must forget everything about @sequence as a reaction to this signal.
 | 
						|
   *
 | 
						|
   * Since: 3.14
 | 
						|
   */
 | 
						|
  signals[CANCEL] =
 | 
						|
    g_signal_new (I_("cancel"),
 | 
						|
                  G_TYPE_FROM_CLASS (klass),
 | 
						|
                  G_SIGNAL_RUN_LAST,
 | 
						|
                  G_STRUCT_OFFSET (GtkGestureClass, cancel),
 | 
						|
                  NULL, NULL, NULL,
 | 
						|
                  G_TYPE_NONE, 1, GDK_TYPE_EVENT_SEQUENCE);
 | 
						|
  /**
 | 
						|
   * GtkGesture::sequence-state-changed:
 | 
						|
   * @gesture: the object which received the signal
 | 
						|
   * @sequence: (nullable): the #GdkEventSequence that was cancelled
 | 
						|
   * @state: the new sequence state
 | 
						|
   *
 | 
						|
   * This signal is emitted whenever a sequence state changes. See
 | 
						|
   * gtk_gesture_set_sequence_state() to know more about the expectable
 | 
						|
   * sequence lifetimes.
 | 
						|
   *
 | 
						|
   * Since: 3.14
 | 
						|
   */
 | 
						|
  signals[SEQUENCE_STATE_CHANGED] =
 | 
						|
    g_signal_new (I_("sequence-state-changed"),
 | 
						|
                  G_TYPE_FROM_CLASS (klass),
 | 
						|
                  G_SIGNAL_RUN_LAST,
 | 
						|
                  G_STRUCT_OFFSET (GtkGestureClass, sequence_state_changed),
 | 
						|
                  NULL, NULL,
 | 
						|
                  _gtk_marshal_VOID__BOXED_ENUM,
 | 
						|
                  G_TYPE_NONE, 2, GDK_TYPE_EVENT_SEQUENCE,
 | 
						|
                  GTK_TYPE_EVENT_SEQUENCE_STATE);
 | 
						|
  g_signal_set_va_marshaller (signals[SEQUENCE_STATE_CHANGED],
 | 
						|
                              G_TYPE_FROM_CLASS (klass),
 | 
						|
                              _gtk_marshal_VOID__BOXED_ENUMv);
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
free_point_data (gpointer data)
 | 
						|
{
 | 
						|
  PointData *point = data;
 | 
						|
 | 
						|
  if (point->event)
 | 
						|
    gdk_event_free (point->event);
 | 
						|
 | 
						|
  g_free (point);
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
gtk_gesture_init (GtkGesture *gesture)
 | 
						|
{
 | 
						|
  GtkGesturePrivate *priv;
 | 
						|
 | 
						|
  priv = gtk_gesture_get_instance_private (gesture);
 | 
						|
  priv->points = g_hash_table_new_full (NULL, NULL, NULL,
 | 
						|
                                        (GDestroyNotify) free_point_data);
 | 
						|
  gtk_event_controller_set_event_mask (GTK_EVENT_CONTROLLER (gesture),
 | 
						|
                                       GDK_TOUCH_MASK |
 | 
						|
                                       GDK_TOUCHPAD_GESTURE_MASK);
 | 
						|
 | 
						|
  priv->group_link = g_list_prepend (NULL, gesture);
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * gtk_gesture_get_device:
 | 
						|
 * @gesture: a #GtkGesture
 | 
						|
 *
 | 
						|
 * Returns the master #GdkDevice that is currently operating
 | 
						|
 * on @gesture, or %NULL if the gesture is not being interacted.
 | 
						|
 *
 | 
						|
 * Returns: (nullable) (transfer none): a #GdkDevice, or %NULL
 | 
						|
 *
 | 
						|
 * Since: 3.14
 | 
						|
 **/
 | 
						|
GdkDevice *
 | 
						|
gtk_gesture_get_device (GtkGesture *gesture)
 | 
						|
{
 | 
						|
  GtkGesturePrivate *priv;
 | 
						|
 | 
						|
  g_return_val_if_fail (GTK_IS_GESTURE (gesture), NULL);
 | 
						|
 | 
						|
  priv = gtk_gesture_get_instance_private (gesture);
 | 
						|
 | 
						|
  return priv->device;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * gtk_gesture_get_sequence_state:
 | 
						|
 * @gesture: a #GtkGesture
 | 
						|
 * @sequence: a #GdkEventSequence
 | 
						|
 *
 | 
						|
 * Returns the @sequence state, as seen by @gesture.
 | 
						|
 *
 | 
						|
 * Returns: The sequence state in @gesture
 | 
						|
 *
 | 
						|
 * Since: 3.14
 | 
						|
 **/
 | 
						|
GtkEventSequenceState
 | 
						|
gtk_gesture_get_sequence_state (GtkGesture       *gesture,
 | 
						|
                                GdkEventSequence *sequence)
 | 
						|
{
 | 
						|
  GtkGesturePrivate *priv;
 | 
						|
  PointData *data;
 | 
						|
 | 
						|
  g_return_val_if_fail (GTK_IS_GESTURE (gesture),
 | 
						|
                        GTK_EVENT_SEQUENCE_NONE);
 | 
						|
 | 
						|
  priv = gtk_gesture_get_instance_private (gesture);
 | 
						|
  data = g_hash_table_lookup (priv->points, sequence);
 | 
						|
 | 
						|
  if (!data)
 | 
						|
    return GTK_EVENT_SEQUENCE_NONE;
 | 
						|
 | 
						|
  return data->state;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * gtk_gesture_set_sequence_state:
 | 
						|
 * @gesture: a #GtkGesture
 | 
						|
 * @sequence: a #GdkEventSequence
 | 
						|
 * @state: the sequence state
 | 
						|
 *
 | 
						|
 * 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:
 | 
						|
 *
 | 
						|
 * * None
 | 
						|
 * * None → Denied
 | 
						|
 * * None → Claimed
 | 
						|
 * * None → Claimed → Denied
 | 
						|
 *
 | 
						|
 * Note: Due to event handling ordering, it may be unsafe to
 | 
						|
 * set the state on another gesture within a #GtkGesture::begin
 | 
						|
 * signal handler, as the callback might be executed before
 | 
						|
 * the other gesture knows about the sequence. A safe way to
 | 
						|
 * perform this could be:
 | 
						|
 *
 | 
						|
 * |[
 | 
						|
 * static void
 | 
						|
 * first_gesture_begin_cb (GtkGesture       *first_gesture,
 | 
						|
 *                         GdkEventSequence *sequence,
 | 
						|
 *                         gpointer          user_data)
 | 
						|
 * {
 | 
						|
 *   gtk_gesture_set_sequence_state (first_gesture, sequence, GTK_EVENT_SEQUENCE_CLAIMED);
 | 
						|
 *   gtk_gesture_set_sequence_state (second_gesture, sequence, GTK_EVENT_SEQUENCE_DENIED);
 | 
						|
 * }
 | 
						|
 *
 | 
						|
 * static void
 | 
						|
 * second_gesture_begin_cb (GtkGesture       *second_gesture,
 | 
						|
 *                          GdkEventSequence *sequence,
 | 
						|
 *                          gpointer          user_data)
 | 
						|
 * {
 | 
						|
 *   if (gtk_gesture_get_sequence_state (first_gesture, sequence) == GTK_EVENT_SEQUENCE_CLAIMED)
 | 
						|
 *     gtk_gesture_set_sequence_state (second_gesture, sequence, GTK_EVENT_SEQUENCE_DENIED);
 | 
						|
 * }
 | 
						|
 * ]|
 | 
						|
 *
 | 
						|
 * If both gestures are in the same group, just set the state on
 | 
						|
 * the gesture emitting the event, the sequence will be already
 | 
						|
 * be initialized to the group's global state when the second
 | 
						|
 * gesture processes the event.
 | 
						|
 *
 | 
						|
 * Returns: %TRUE if @sequence is handled by @gesture,
 | 
						|
 *          and the state is changed successfully
 | 
						|
 *
 | 
						|
 * Since: 3.14
 | 
						|
 **/
 | 
						|
gboolean
 | 
						|
gtk_gesture_set_sequence_state (GtkGesture            *gesture,
 | 
						|
                                GdkEventSequence      *sequence,
 | 
						|
                                GtkEventSequenceState  state)
 | 
						|
{
 | 
						|
  GtkGesturePrivate *priv;
 | 
						|
  PointData *data;
 | 
						|
 | 
						|
  g_return_val_if_fail (GTK_IS_GESTURE (gesture), FALSE);
 | 
						|
  g_return_val_if_fail (state >= GTK_EVENT_SEQUENCE_NONE &&
 | 
						|
                        state <= GTK_EVENT_SEQUENCE_DENIED, FALSE);
 | 
						|
 | 
						|
  priv = gtk_gesture_get_instance_private (gesture);
 | 
						|
  data = g_hash_table_lookup (priv->points, sequence);
 | 
						|
 | 
						|
  if (!data)
 | 
						|
    return FALSE;
 | 
						|
 | 
						|
  if (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;
 | 
						|
 | 
						|
  data->state = state;
 | 
						|
  g_signal_emit (gesture, signals[SEQUENCE_STATE_CHANGED], 0,
 | 
						|
                 sequence, state);
 | 
						|
 | 
						|
  if (state == GTK_EVENT_SEQUENCE_DENIED)
 | 
						|
    _gtk_gesture_check_recognized (gesture, sequence);
 | 
						|
 | 
						|
  return TRUE;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * gtk_gesture_set_state:
 | 
						|
 * @gesture: a #GtkGesture
 | 
						|
 * @state: the sequence state
 | 
						|
 *
 | 
						|
 * Sets the state of all sequences that @gesture is currently
 | 
						|
 * interacting with. See gtk_gesture_set_sequence_state()
 | 
						|
 * for more details on sequence states.
 | 
						|
 *
 | 
						|
 * Returns: %TRUE if the state of at least one sequence
 | 
						|
 *     was changed successfully
 | 
						|
 *
 | 
						|
 * Since: 3.14
 | 
						|
 */
 | 
						|
gboolean
 | 
						|
gtk_gesture_set_state (GtkGesture            *gesture,
 | 
						|
                       GtkEventSequenceState  state)
 | 
						|
{
 | 
						|
  gboolean handled = FALSE;
 | 
						|
  GtkGesturePrivate *priv;
 | 
						|
  GList *sequences, *l;
 | 
						|
 | 
						|
  g_return_val_if_fail (GTK_IS_GESTURE (gesture), FALSE);
 | 
						|
  g_return_val_if_fail (state >= GTK_EVENT_SEQUENCE_NONE &&
 | 
						|
                        state <= GTK_EVENT_SEQUENCE_DENIED, FALSE);
 | 
						|
 | 
						|
  priv = gtk_gesture_get_instance_private (gesture);
 | 
						|
  sequences = g_hash_table_get_keys (priv->points);
 | 
						|
 | 
						|
  for (l = sequences; l; l = l->next)
 | 
						|
    handled |= gtk_gesture_set_sequence_state (gesture, l->data, state);
 | 
						|
 | 
						|
  g_list_free (sequences);
 | 
						|
 | 
						|
  return handled;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * gtk_gesture_get_sequences:
 | 
						|
 * @gesture: a #GtkGesture
 | 
						|
 *
 | 
						|
 * Returns the list of #GdkEventSequences currently being interpreted
 | 
						|
 * by @gesture.
 | 
						|
 *
 | 
						|
 * Returns: (transfer container) (element-type GdkEventSequence): A list
 | 
						|
 *          of #GdkEventSequences, the list elements are owned by GTK+
 | 
						|
 *          and must not be freed or modified, the list itself must be deleted
 | 
						|
 *          through g_list_free()
 | 
						|
 *
 | 
						|
 * Since: 3.14
 | 
						|
 **/
 | 
						|
GList *
 | 
						|
gtk_gesture_get_sequences (GtkGesture *gesture)
 | 
						|
{
 | 
						|
  GdkEventSequence *sequence;
 | 
						|
  GtkGesturePrivate *priv;
 | 
						|
  GList *sequences = NULL;
 | 
						|
  GHashTableIter iter;
 | 
						|
  PointData *data;
 | 
						|
 | 
						|
  g_return_val_if_fail (GTK_IS_GESTURE (gesture), NULL);
 | 
						|
 | 
						|
  priv = gtk_gesture_get_instance_private (gesture);
 | 
						|
  g_hash_table_iter_init (&iter, priv->points);
 | 
						|
 | 
						|
  while (g_hash_table_iter_next (&iter, (gpointer *) &sequence, (gpointer *) &data))
 | 
						|
    {
 | 
						|
      if (data->state == GTK_EVENT_SEQUENCE_DENIED)
 | 
						|
        continue;
 | 
						|
      if (data->event->type == GDK_TOUCH_END ||
 | 
						|
          data->event->type == GDK_BUTTON_RELEASE)
 | 
						|
        continue;
 | 
						|
 | 
						|
      sequences = g_list_prepend (sequences, sequence);
 | 
						|
    }
 | 
						|
 | 
						|
  return sequences;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * gtk_gesture_get_last_updated_sequence:
 | 
						|
 * @gesture: a #GtkGesture
 | 
						|
 *
 | 
						|
 * Returns the #GdkEventSequence that was last updated on @gesture.
 | 
						|
 *
 | 
						|
 * Returns: (transfer none) (nullable): The last updated sequence
 | 
						|
 *
 | 
						|
 * Since: 3.14
 | 
						|
 **/
 | 
						|
GdkEventSequence *
 | 
						|
gtk_gesture_get_last_updated_sequence (GtkGesture *gesture)
 | 
						|
{
 | 
						|
  GtkGesturePrivate *priv;
 | 
						|
 | 
						|
  g_return_val_if_fail (GTK_IS_GESTURE (gesture), NULL);
 | 
						|
 | 
						|
  priv = gtk_gesture_get_instance_private (gesture);
 | 
						|
 | 
						|
  return priv->last_sequence;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * gtk_gesture_get_last_event:
 | 
						|
 * @gesture: a #GtkGesture
 | 
						|
 * @sequence: (nullable): a #GdkEventSequence
 | 
						|
 *
 | 
						|
 * Returns the last event that was processed for @sequence.
 | 
						|
 *
 | 
						|
 * Note that the returned pointer is only valid as long as the @sequence
 | 
						|
 * is still interpreted by the @gesture. If in doubt, you should make
 | 
						|
 * a copy of the event.
 | 
						|
 *
 | 
						|
 * Returns: (transfer none) (nullable): The last event from @sequence
 | 
						|
 **/
 | 
						|
const GdkEvent *
 | 
						|
gtk_gesture_get_last_event (GtkGesture       *gesture,
 | 
						|
                            GdkEventSequence *sequence)
 | 
						|
{
 | 
						|
  GtkGesturePrivate *priv;
 | 
						|
  PointData *data;
 | 
						|
 | 
						|
  g_return_val_if_fail (GTK_IS_GESTURE (gesture), NULL);
 | 
						|
 | 
						|
  priv = gtk_gesture_get_instance_private (gesture);
 | 
						|
  data = g_hash_table_lookup (priv->points, sequence);
 | 
						|
 | 
						|
  if (!data)
 | 
						|
    return NULL;
 | 
						|
 | 
						|
  return data->event;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * gtk_gesture_get_point:
 | 
						|
 * @gesture: a #GtkGesture
 | 
						|
 * @sequence: (allow-none): a #GdkEventSequence, or %NULL for pointer events
 | 
						|
 * @x: (out) (allow-none): return location for X axis of the sequence coordinates
 | 
						|
 * @y: (out) (allow-none): return location for Y axis of the sequence coordinates
 | 
						|
 *
 | 
						|
 * If @sequence is currently being interpreted by @gesture, this
 | 
						|
 * function returns %TRUE and fills in @x and @y with the last coordinates
 | 
						|
 * stored for that event sequence. The coordinates are always relative to the
 | 
						|
 * widget allocation.
 | 
						|
 *
 | 
						|
 * Returns: %TRUE if @sequence is currently interpreted
 | 
						|
 *
 | 
						|
 * Since: 3.14
 | 
						|
 **/
 | 
						|
gboolean
 | 
						|
gtk_gesture_get_point (GtkGesture       *gesture,
 | 
						|
                       GdkEventSequence *sequence,
 | 
						|
                       gdouble          *x,
 | 
						|
                       gdouble          *y)
 | 
						|
{
 | 
						|
  GtkGesturePrivate *priv;
 | 
						|
  PointData *data;
 | 
						|
 | 
						|
  g_return_val_if_fail (GTK_IS_GESTURE (gesture), FALSE);
 | 
						|
 | 
						|
  priv = gtk_gesture_get_instance_private (gesture);
 | 
						|
 | 
						|
  if (!g_hash_table_lookup_extended (priv->points, sequence,
 | 
						|
                                     NULL, (gpointer *) &data))
 | 
						|
    return FALSE;
 | 
						|
 | 
						|
  if (x)
 | 
						|
    *x = data->widget_x;
 | 
						|
  if (y)
 | 
						|
    *y = data->widget_y;
 | 
						|
 | 
						|
  return TRUE;
 | 
						|
}
 | 
						|
 | 
						|
gboolean
 | 
						|
_gtk_gesture_get_last_update_time (GtkGesture       *gesture,
 | 
						|
                                   GdkEventSequence *sequence,
 | 
						|
                                   guint32          *evtime)
 | 
						|
{
 | 
						|
  GtkGesturePrivate *priv;
 | 
						|
  PointData *data;
 | 
						|
 | 
						|
  g_return_val_if_fail (GTK_IS_GESTURE (gesture), FALSE);
 | 
						|
 | 
						|
  priv = gtk_gesture_get_instance_private (gesture);
 | 
						|
 | 
						|
  if (!g_hash_table_lookup_extended (priv->points, sequence,
 | 
						|
                                     NULL, (gpointer *) &data))
 | 
						|
    return FALSE;
 | 
						|
 | 
						|
  if (evtime)
 | 
						|
    *evtime = gdk_event_get_time (data->event);
 | 
						|
 | 
						|
  return TRUE;
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * gtk_gesture_get_bounding_box:
 | 
						|
 * @gesture: a #GtkGesture
 | 
						|
 * @rect: (out): bounding box containing all active touches.
 | 
						|
 *
 | 
						|
 * If there are touch sequences being currently handled by @gesture,
 | 
						|
 * this function returns %TRUE and fills in @rect with the bounding
 | 
						|
 * box containing all active touches. Otherwise, %FALSE will be
 | 
						|
 * returned.
 | 
						|
 *
 | 
						|
 * Note: This function will yield unexpected results on touchpad
 | 
						|
 * gestures. Since there is no correlation between physical and
 | 
						|
 * pixel distances, these will look as if constrained in an
 | 
						|
 * infinitely small area, @rect width and height will thus be 0
 | 
						|
 * regardless of the number of touchpoints.
 | 
						|
 *
 | 
						|
 * Returns: %TRUE if there are active touches, %FALSE otherwise
 | 
						|
 *
 | 
						|
 * Since: 3.14
 | 
						|
 **/
 | 
						|
gboolean
 | 
						|
gtk_gesture_get_bounding_box (GtkGesture   *gesture,
 | 
						|
                              GdkRectangle *rect)
 | 
						|
{
 | 
						|
  GtkGesturePrivate *priv;
 | 
						|
  gdouble x1, y1, x2, y2;
 | 
						|
  GHashTableIter iter;
 | 
						|
  guint n_points = 0;
 | 
						|
  PointData *data;
 | 
						|
 | 
						|
  g_return_val_if_fail (GTK_IS_GESTURE (gesture), FALSE);
 | 
						|
  g_return_val_if_fail (rect != NULL, FALSE);
 | 
						|
 | 
						|
  priv = gtk_gesture_get_instance_private (gesture);
 | 
						|
  x1 = y1 = G_MAXDOUBLE;
 | 
						|
  x2 = y2 = -G_MAXDOUBLE;
 | 
						|
 | 
						|
  g_hash_table_iter_init (&iter, priv->points);
 | 
						|
 | 
						|
  while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &data))
 | 
						|
    {
 | 
						|
      gdouble x, y;
 | 
						|
 | 
						|
      if (data->state == GTK_EVENT_SEQUENCE_DENIED)
 | 
						|
        continue;
 | 
						|
      if (data->event->type == GDK_TOUCH_END ||
 | 
						|
          data->event->type == GDK_BUTTON_RELEASE)
 | 
						|
        continue;
 | 
						|
 | 
						|
      gdk_event_get_coords (data->event, &x, &y);
 | 
						|
      n_points++;
 | 
						|
      x1 = MIN (x1, x);
 | 
						|
      y1 = MIN (y1, y);
 | 
						|
      x2 = MAX (x2, x);
 | 
						|
      y2 = MAX (y2, y);
 | 
						|
    }
 | 
						|
 | 
						|
  if (n_points == 0)
 | 
						|
    return FALSE;
 | 
						|
 | 
						|
  rect->x = x1;
 | 
						|
  rect->y = y1;
 | 
						|
  rect->width = x2 - x1;
 | 
						|
  rect->height = y2 - y1;
 | 
						|
 | 
						|
  return TRUE;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/**
 | 
						|
 * gtk_gesture_get_bounding_box_center:
 | 
						|
 * @gesture: a #GtkGesture
 | 
						|
 * @x: (out): X coordinate for the bounding box center
 | 
						|
 * @y: (out): Y coordinate for the bounding box center
 | 
						|
 *
 | 
						|
 * If there are touch sequences being currently handled by @gesture,
 | 
						|
 * this function returns %TRUE and fills in @x and @y with the center
 | 
						|
 * of the bounding box containing all active touches. Otherwise, %FALSE
 | 
						|
 * will be returned.
 | 
						|
 *
 | 
						|
 * Returns: %FALSE if no active touches are present, %TRUE otherwise
 | 
						|
 *
 | 
						|
 * Since: 3.14
 | 
						|
 **/
 | 
						|
gboolean
 | 
						|
gtk_gesture_get_bounding_box_center (GtkGesture *gesture,
 | 
						|
                                     gdouble    *x,
 | 
						|
                                     gdouble    *y)
 | 
						|
{
 | 
						|
  const GdkEvent *last_event;
 | 
						|
  GdkRectangle rect;
 | 
						|
  GdkEventSequence *sequence;
 | 
						|
 | 
						|
  g_return_val_if_fail (GTK_IS_GESTURE (gesture), FALSE);
 | 
						|
  g_return_val_if_fail (x != NULL && y != NULL, FALSE);
 | 
						|
 | 
						|
  sequence = gtk_gesture_get_last_updated_sequence (gesture);
 | 
						|
  last_event = gtk_gesture_get_last_event (gesture, sequence);
 | 
						|
 | 
						|
  if (EVENT_IS_TOUCHPAD_GESTURE (last_event))
 | 
						|
    return gtk_gesture_get_point (gesture, sequence, x, y);
 | 
						|
  else if (!gtk_gesture_get_bounding_box (gesture, &rect))
 | 
						|
    return FALSE;
 | 
						|
 | 
						|
  *x = rect.x + rect.width / 2;
 | 
						|
  *y = rect.y + rect.height / 2;
 | 
						|
  return TRUE;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * gtk_gesture_is_active:
 | 
						|
 * @gesture: a #GtkGesture
 | 
						|
 *
 | 
						|
 * Returns %TRUE if the gesture is currently active.
 | 
						|
 * A gesture is active meanwhile there are touch sequences
 | 
						|
 * interacting with it.
 | 
						|
 *
 | 
						|
 * Returns: %TRUE if gesture is active
 | 
						|
 *
 | 
						|
 * Since: 3.14
 | 
						|
 **/
 | 
						|
gboolean
 | 
						|
gtk_gesture_is_active (GtkGesture *gesture)
 | 
						|
{
 | 
						|
  g_return_val_if_fail (GTK_IS_GESTURE (gesture), FALSE);
 | 
						|
 | 
						|
  return _gtk_gesture_get_n_physical_points (gesture, TRUE) != 0;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * gtk_gesture_is_recognized:
 | 
						|
 * @gesture: a #GtkGesture
 | 
						|
 *
 | 
						|
 * Returns %TRUE if the gesture is currently recognized.
 | 
						|
 * A gesture is recognized if there are as many interacting
 | 
						|
 * touch sequences as required by @gesture, and #GtkGesture::check
 | 
						|
 * returned %TRUE for the sequences being currently interpreted.
 | 
						|
 *
 | 
						|
 * Returns: %TRUE if gesture is recognized
 | 
						|
 *
 | 
						|
 * Since: 3.14
 | 
						|
 **/
 | 
						|
gboolean
 | 
						|
gtk_gesture_is_recognized (GtkGesture *gesture)
 | 
						|
{
 | 
						|
  GtkGesturePrivate *priv;
 | 
						|
 | 
						|
  g_return_val_if_fail (GTK_IS_GESTURE (gesture), FALSE);
 | 
						|
 | 
						|
  priv = gtk_gesture_get_instance_private (gesture);
 | 
						|
 | 
						|
  return priv->recognized;
 | 
						|
}
 | 
						|
 | 
						|
gboolean
 | 
						|
_gtk_gesture_check (GtkGesture *gesture)
 | 
						|
{
 | 
						|
  GtkGesturePrivate *priv;
 | 
						|
 | 
						|
  g_return_val_if_fail (GTK_IS_GESTURE (gesture), FALSE);
 | 
						|
 | 
						|
  priv = gtk_gesture_get_instance_private (gesture);
 | 
						|
 | 
						|
  return _gtk_gesture_check_recognized (gesture, priv->last_sequence);
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * gtk_gesture_handles_sequence:
 | 
						|
 * @gesture: a #GtkGesture
 | 
						|
 * @sequence: (nullable): a #GdkEventSequence or %NULL
 | 
						|
 *
 | 
						|
 * Returns %TRUE if @gesture is currently handling events corresponding to
 | 
						|
 * @sequence.
 | 
						|
 *
 | 
						|
 * Returns: %TRUE if @gesture is handling @sequence, %FALSE otherwise
 | 
						|
 *
 | 
						|
 * Since: 3.14
 | 
						|
 **/
 | 
						|
gboolean
 | 
						|
gtk_gesture_handles_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;
 | 
						|
 | 
						|
  if (data->state == GTK_EVENT_SEQUENCE_DENIED)
 | 
						|
    return FALSE;
 | 
						|
 | 
						|
  return TRUE;
 | 
						|
}
 | 
						|
 | 
						|
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_remove_point (gesture, data->event);
 | 
						|
  _gtk_gesture_check_recognized (gesture, sequence);
 | 
						|
 | 
						|
  return TRUE;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * gtk_gesture_get_window:
 | 
						|
 * @gesture: a #GtkGesture
 | 
						|
 *
 | 
						|
 * Returns the user-defined window that receives the events
 | 
						|
 * handled by @gesture. See gtk_gesture_set_window() for more
 | 
						|
 * information.
 | 
						|
 *
 | 
						|
 * Returns: (nullable) (transfer none): the user defined window, or %NULL if none
 | 
						|
 *
 | 
						|
 * Since: 3.14
 | 
						|
 **/
 | 
						|
GdkWindow *
 | 
						|
gtk_gesture_get_window (GtkGesture *gesture)
 | 
						|
{
 | 
						|
  GtkGesturePrivate *priv;
 | 
						|
 | 
						|
  g_return_val_if_fail (GTK_IS_GESTURE (gesture), NULL);
 | 
						|
 | 
						|
  priv = gtk_gesture_get_instance_private (gesture);
 | 
						|
 | 
						|
  return priv->user_window;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * gtk_gesture_set_window:
 | 
						|
 * @gesture: a #GtkGesture
 | 
						|
 * @window: (allow-none): a #GdkWindow, or %NULL
 | 
						|
 *
 | 
						|
 * Sets a specific window to receive events about, so @gesture
 | 
						|
 * will effectively handle only events targeting @window, or
 | 
						|
 * a child of it. @window must pertain to gtk_event_controller_get_widget().
 | 
						|
 *
 | 
						|
 * Since: 3.14
 | 
						|
 **/
 | 
						|
void
 | 
						|
gtk_gesture_set_window (GtkGesture *gesture,
 | 
						|
                        GdkWindow  *window)
 | 
						|
{
 | 
						|
  GtkGesturePrivate *priv;
 | 
						|
 | 
						|
  g_return_if_fail (GTK_IS_GESTURE (gesture));
 | 
						|
  g_return_if_fail (!window || GDK_IS_WINDOW (window));
 | 
						|
 | 
						|
  priv = gtk_gesture_get_instance_private (gesture);
 | 
						|
 | 
						|
  if (window)
 | 
						|
    {
 | 
						|
      GtkWidget *window_widget;
 | 
						|
 | 
						|
      gdk_window_get_user_data (window, (gpointer*) &window_widget);
 | 
						|
      g_return_if_fail (window_widget ==
 | 
						|
                        gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (gesture)));
 | 
						|
    }
 | 
						|
 | 
						|
  if (priv->user_window == window)
 | 
						|
    return;
 | 
						|
 | 
						|
  priv->user_window = window;
 | 
						|
  g_object_notify (G_OBJECT (gesture), "window");
 | 
						|
}
 | 
						|
 | 
						|
GList *
 | 
						|
_gtk_gesture_get_group_link (GtkGesture *gesture)
 | 
						|
{
 | 
						|
  GtkGesturePrivate *priv;
 | 
						|
 | 
						|
  priv = gtk_gesture_get_instance_private (gesture);
 | 
						|
 | 
						|
  return priv->group_link;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * gtk_gesture_group:
 | 
						|
 * @gesture: a #GtkGesture
 | 
						|
 * @group_gesture: #GtkGesture to group @gesture with
 | 
						|
 *
 | 
						|
 * Adds @gesture to the same group than @group_gesture. Gestures
 | 
						|
 * are by default isolated in their own groups.
 | 
						|
 *
 | 
						|
 * When gestures are grouped, the state of #GdkEventSequences
 | 
						|
 * is kept in sync for all of those, so calling gtk_gesture_set_sequence_state(),
 | 
						|
 * on one will transfer the same value to the others.
 | 
						|
 *
 | 
						|
 * Groups also perform an "implicit grabbing" of sequences, if a
 | 
						|
 * #GdkEventSequence state is set to #GTK_EVENT_SEQUENCE_CLAIMED on one group,
 | 
						|
 * every other gesture group attached to the same #GtkWidget will switch the
 | 
						|
 * state for that sequence to #GTK_EVENT_SEQUENCE_DENIED.
 | 
						|
 *
 | 
						|
 * Since: 3.14
 | 
						|
 **/
 | 
						|
void
 | 
						|
gtk_gesture_group (GtkGesture *gesture,
 | 
						|
                   GtkGesture *group_gesture)
 | 
						|
{
 | 
						|
  GList *link, *group_link, *next;
 | 
						|
 | 
						|
  g_return_if_fail (GTK_IS_GESTURE (gesture));
 | 
						|
  g_return_if_fail (GTK_IS_GESTURE (group_gesture));
 | 
						|
  g_return_if_fail (gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (group_gesture)) ==
 | 
						|
                    gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (gesture)));
 | 
						|
 | 
						|
  link = _gtk_gesture_get_group_link (gesture);
 | 
						|
 | 
						|
  if (link->prev || link->next)
 | 
						|
    {
 | 
						|
      if (gtk_gesture_is_grouped_with (gesture, group_gesture))
 | 
						|
        return;
 | 
						|
      gtk_gesture_ungroup (gesture);
 | 
						|
    }
 | 
						|
 | 
						|
  group_link = _gtk_gesture_get_group_link (group_gesture);
 | 
						|
  next = group_link->next;
 | 
						|
 | 
						|
  /* Rewire link so it's inserted after the group_gesture elem */
 | 
						|
  link->prev = group_link;
 | 
						|
  link->next = next;
 | 
						|
  group_link->next = link;
 | 
						|
  if (next)
 | 
						|
    next->prev = link;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * gtk_gesture_ungroup:
 | 
						|
 * @gesture: a #GtkGesture
 | 
						|
 *
 | 
						|
 * Separates @gesture into an isolated group.
 | 
						|
 *
 | 
						|
 * Since: 3.14
 | 
						|
 **/
 | 
						|
void
 | 
						|
gtk_gesture_ungroup (GtkGesture *gesture)
 | 
						|
{
 | 
						|
  GList *link, *prev, *next;
 | 
						|
 | 
						|
  g_return_if_fail (GTK_IS_GESTURE (gesture));
 | 
						|
 | 
						|
  link = _gtk_gesture_get_group_link (gesture);
 | 
						|
  prev = link->prev;
 | 
						|
  next = link->next;
 | 
						|
 | 
						|
  /* Detach link from the group chain */
 | 
						|
  if (prev)
 | 
						|
    prev->next = next;
 | 
						|
  if (next)
 | 
						|
    next->prev = prev;
 | 
						|
 | 
						|
  link->next = link->prev = NULL;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * gtk_gesture_get_group:
 | 
						|
 * @gesture: a #GtkGesture
 | 
						|
 *
 | 
						|
 * Returns all gestures in the group of @gesture
 | 
						|
 *
 | 
						|
 * Returns: (element-type GtkGesture) (transfer container): The list
 | 
						|
 *   of #GtkGestures, free with g_list_free()
 | 
						|
 *
 | 
						|
 * Since: 3.14
 | 
						|
 **/
 | 
						|
GList *
 | 
						|
gtk_gesture_get_group (GtkGesture *gesture)
 | 
						|
{
 | 
						|
  GList *link;
 | 
						|
 | 
						|
  g_return_val_if_fail (GTK_IS_GESTURE (gesture), NULL);
 | 
						|
 | 
						|
  link = _gtk_gesture_get_group_link (gesture);
 | 
						|
 | 
						|
  return g_list_copy (g_list_first (link));
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * gtk_gesture_is_grouped_with:
 | 
						|
 * @gesture: a #GtkGesture
 | 
						|
 * @other: another #GtkGesture
 | 
						|
 *
 | 
						|
 * Returns %TRUE if both gestures pertain to the same group.
 | 
						|
 *
 | 
						|
 * Returns: whether the gestures are grouped
 | 
						|
 *
 | 
						|
 * Since: 3.14
 | 
						|
 **/
 | 
						|
gboolean
 | 
						|
gtk_gesture_is_grouped_with (GtkGesture *gesture,
 | 
						|
                             GtkGesture *other)
 | 
						|
{
 | 
						|
  GList *link;
 | 
						|
 | 
						|
  g_return_val_if_fail (GTK_IS_GESTURE (gesture), FALSE);
 | 
						|
  g_return_val_if_fail (GTK_IS_GESTURE (other), FALSE);
 | 
						|
 | 
						|
  link = _gtk_gesture_get_group_link (gesture);
 | 
						|
  link = g_list_first (link);
 | 
						|
 | 
						|
  return g_list_find (link, other) != NULL;
 | 
						|
}
 | 
						|
 | 
						|
gboolean
 | 
						|
_gtk_gesture_handled_sequence_press (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;
 | 
						|
 | 
						|
  return data->press_handled;
 | 
						|
}
 | 
						|
 | 
						|
gboolean
 | 
						|
_gtk_gesture_get_pointer_emulating_sequence (GtkGesture        *gesture,
 | 
						|
                                             GdkEventSequence **sequence)
 | 
						|
{
 | 
						|
  GtkGesturePrivate *priv;
 | 
						|
  GdkEventSequence *seq;
 | 
						|
  GHashTableIter iter;
 | 
						|
  PointData *data;
 | 
						|
 | 
						|
  g_return_val_if_fail (GTK_IS_GESTURE (gesture), FALSE);
 | 
						|
 | 
						|
  priv = gtk_gesture_get_instance_private (gesture);
 | 
						|
  g_hash_table_iter_init (&iter, priv->points);
 | 
						|
 | 
						|
  while (g_hash_table_iter_next (&iter, (gpointer*) &seq, (gpointer*) &data))
 | 
						|
    {
 | 
						|
      switch (data->event->type)
 | 
						|
        {
 | 
						|
        case GDK_TOUCH_BEGIN:
 | 
						|
        case GDK_TOUCH_UPDATE:
 | 
						|
        case GDK_TOUCH_END:
 | 
						|
          if (!data->event->touch.emulating_pointer)
 | 
						|
            continue;
 | 
						|
          /* Fall through */
 | 
						|
        case GDK_BUTTON_PRESS:
 | 
						|
        case GDK_BUTTON_RELEASE:
 | 
						|
        case GDK_MOTION_NOTIFY:
 | 
						|
          *sequence = seq;
 | 
						|
          return TRUE;
 | 
						|
        default:
 | 
						|
          break;
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
  return FALSE;
 | 
						|
}
 |