forked from AuroraMiddleware/gtk
cab1dcb696
Make GdkEvents hold a single GdkDevice. This device is closer to the logical device conceptually, although it must be sufficient for device checks (i.e. GdkInputSource), which makes it similar to the physical devices. Make the logical devices have a more accurate GdkInputSource where needed, and conflate the event devices altogether.
457 lines
14 KiB
C
457 lines
14 KiB
C
/* GTK - The GIMP Toolkit
|
|
* 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:gtkgestureclick
|
|
* @Short_description: Multipress gesture
|
|
* @Title: GtkGestureClick
|
|
*
|
|
* #GtkGestureClick is a #GtkGesture implementation able to recognize
|
|
* multiple clicks on a nearby zone, which can be listened for through
|
|
* the #GtkGestureClick::pressed signal. Whenever time or distance
|
|
* between clicks exceed the GTK+ defaults, #GtkGestureClick::stopped
|
|
* is emitted, and the click counter is reset.
|
|
*
|
|
* Callers may also restrict the area that is considered valid for a >1
|
|
* touch/button press through gtk_gesture_click_set_area(), so any
|
|
* click happening outside that area is considered to be a first click
|
|
* of its own.
|
|
*/
|
|
|
|
#include "config.h"
|
|
#include "gtkgestureprivate.h"
|
|
#include "gtkgestureclick.h"
|
|
#include "gtkgestureclickprivate.h"
|
|
#include "gtkprivate.h"
|
|
#include "gtkintl.h"
|
|
#include "gtkmarshalers.h"
|
|
|
|
typedef struct _GtkGestureClickPrivate GtkGestureClickPrivate;
|
|
|
|
struct _GtkGestureClickPrivate
|
|
{
|
|
GdkDevice *current_device;
|
|
double initial_press_x;
|
|
double initial_press_y;
|
|
guint double_click_timeout_id;
|
|
guint n_presses;
|
|
guint n_release;
|
|
guint current_button;
|
|
};
|
|
|
|
enum {
|
|
PRESSED,
|
|
RELEASED,
|
|
STOPPED,
|
|
UNPAIRED_RELEASE,
|
|
LAST_SIGNAL
|
|
};
|
|
|
|
static guint signals[LAST_SIGNAL] = { 0 };
|
|
|
|
G_DEFINE_TYPE_WITH_PRIVATE (GtkGestureClick, gtk_gesture_click, GTK_TYPE_GESTURE_SINGLE)
|
|
|
|
static void
|
|
gtk_gesture_click_finalize (GObject *object)
|
|
{
|
|
GtkGestureClickPrivate *priv;
|
|
GtkGestureClick *gesture;
|
|
|
|
gesture = GTK_GESTURE_CLICK (object);
|
|
priv = gtk_gesture_click_get_instance_private (gesture);
|
|
|
|
if (priv->double_click_timeout_id)
|
|
{
|
|
g_source_remove (priv->double_click_timeout_id);
|
|
priv->double_click_timeout_id = 0;
|
|
}
|
|
|
|
G_OBJECT_CLASS (gtk_gesture_click_parent_class)->finalize (object);
|
|
}
|
|
|
|
static gboolean
|
|
gtk_gesture_click_check (GtkGesture *gesture)
|
|
{
|
|
GtkGestureClick *click;
|
|
GtkGestureClickPrivate *priv;
|
|
GList *sequences;
|
|
gboolean active;
|
|
|
|
click = GTK_GESTURE_CLICK (gesture);
|
|
priv = gtk_gesture_click_get_instance_private (click);
|
|
sequences = gtk_gesture_get_sequences (gesture);
|
|
|
|
active = g_list_length (sequences) == 1 || priv->double_click_timeout_id;
|
|
g_list_free (sequences);
|
|
|
|
return active;
|
|
}
|
|
|
|
static void
|
|
_gtk_gesture_click_stop (GtkGestureClick *gesture)
|
|
{
|
|
GtkGestureClickPrivate *priv;
|
|
|
|
priv = gtk_gesture_click_get_instance_private (gesture);
|
|
|
|
if (priv->n_presses == 0)
|
|
return;
|
|
|
|
priv->current_device = NULL;
|
|
priv->current_button = 0;
|
|
priv->n_presses = 0;
|
|
g_signal_emit (gesture, signals[STOPPED], 0);
|
|
_gtk_gesture_check (GTK_GESTURE (gesture));
|
|
}
|
|
|
|
static gboolean
|
|
_double_click_timeout_cb (gpointer user_data)
|
|
{
|
|
GtkGestureClick *gesture = user_data;
|
|
GtkGestureClickPrivate *priv;
|
|
|
|
priv = gtk_gesture_click_get_instance_private (gesture);
|
|
priv->double_click_timeout_id = 0;
|
|
_gtk_gesture_click_stop (gesture);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
_gtk_gesture_click_update_timeout (GtkGestureClick *gesture)
|
|
{
|
|
GtkGestureClickPrivate *priv;
|
|
guint double_click_time;
|
|
GtkSettings *settings;
|
|
GtkWidget *widget;
|
|
|
|
priv = gtk_gesture_click_get_instance_private (gesture);
|
|
|
|
if (priv->double_click_timeout_id)
|
|
g_source_remove (priv->double_click_timeout_id);
|
|
|
|
widget = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (gesture));
|
|
settings = gtk_widget_get_settings (widget);
|
|
g_object_get (settings, "gtk-double-click-time", &double_click_time, NULL);
|
|
|
|
priv->double_click_timeout_id = g_timeout_add (double_click_time, _double_click_timeout_cb, gesture);
|
|
g_source_set_name_by_id (priv->double_click_timeout_id, "[gtk] _double_click_timeout_cb");
|
|
}
|
|
|
|
static gboolean
|
|
_gtk_gesture_click_check_within_threshold (GtkGestureClick *gesture,
|
|
double x,
|
|
double y)
|
|
{
|
|
GtkGestureClickPrivate *priv;
|
|
guint double_click_distance;
|
|
GtkSettings *settings;
|
|
GtkWidget *widget;
|
|
|
|
priv = gtk_gesture_click_get_instance_private (gesture);
|
|
|
|
if (priv->n_presses == 0)
|
|
return TRUE;
|
|
|
|
widget = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (gesture));
|
|
settings = gtk_widget_get_settings (widget);
|
|
g_object_get (settings,
|
|
"gtk-double-click-distance", &double_click_distance,
|
|
NULL);
|
|
|
|
if (ABS (priv->initial_press_x - x) < double_click_distance &&
|
|
ABS (priv->initial_press_y - y) < double_click_distance)
|
|
return TRUE;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
gtk_gesture_click_begin (GtkGesture *gesture,
|
|
GdkEventSequence *sequence)
|
|
{
|
|
GtkGestureClick *click;
|
|
GtkGestureClickPrivate *priv;
|
|
guint n_presses, button = 1;
|
|
GdkEventSequence *current;
|
|
GdkEvent *event;
|
|
GdkEventType event_type;
|
|
GdkDevice *device;
|
|
double x, y;
|
|
|
|
if (!gtk_gesture_handles_sequence (gesture, sequence))
|
|
return;
|
|
|
|
click = GTK_GESTURE_CLICK (gesture);
|
|
priv = gtk_gesture_click_get_instance_private (click);
|
|
event = gtk_gesture_get_last_event (gesture, sequence);
|
|
current = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture));
|
|
device = gdk_event_get_device (event);
|
|
event_type = gdk_event_get_event_type (event);
|
|
|
|
if (event_type == GDK_BUTTON_PRESS)
|
|
button = gdk_button_event_get_button (event);
|
|
else if (event_type == GDK_TOUCH_BEGIN)
|
|
button = 1;
|
|
else
|
|
return;
|
|
|
|
/* Reset the gesture if the button number changes mid-recognition */
|
|
if (priv->n_presses > 0 &&
|
|
priv->current_button != button)
|
|
_gtk_gesture_click_stop (click);
|
|
|
|
/* Reset also if the device changed */
|
|
if (priv->current_device && priv->current_device != device)
|
|
_gtk_gesture_click_stop (click);
|
|
|
|
priv->current_device = device;
|
|
priv->current_button = button;
|
|
_gtk_gesture_click_update_timeout (click);
|
|
gtk_gesture_get_point (gesture, current, &x, &y);
|
|
|
|
if (!_gtk_gesture_click_check_within_threshold (click, x, y))
|
|
_gtk_gesture_click_stop (click);
|
|
|
|
/* Increment later the real counter, just if the gesture is
|
|
* reset on the pressed handler */
|
|
n_presses = priv->n_release = priv->n_presses + 1;
|
|
|
|
g_signal_emit (gesture, signals[PRESSED], 0, n_presses, x, y);
|
|
|
|
if (priv->n_presses == 0)
|
|
{
|
|
priv->initial_press_x = x;
|
|
priv->initial_press_y = y;
|
|
}
|
|
|
|
priv->n_presses++;
|
|
}
|
|
|
|
static void
|
|
gtk_gesture_click_update (GtkGesture *gesture,
|
|
GdkEventSequence *sequence)
|
|
{
|
|
GtkGestureClick *click;
|
|
GdkEventSequence *current;
|
|
double x, y;
|
|
|
|
click = GTK_GESTURE_CLICK (gesture);
|
|
current = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture));
|
|
gtk_gesture_get_point (gesture, current, &x, &y);
|
|
|
|
if (!_gtk_gesture_click_check_within_threshold (click, x, y))
|
|
_gtk_gesture_click_stop (click);
|
|
}
|
|
|
|
static void
|
|
gtk_gesture_click_end (GtkGesture *gesture,
|
|
GdkEventSequence *sequence)
|
|
{
|
|
GtkGestureClick *click;
|
|
GtkGestureClickPrivate *priv;
|
|
GdkEventSequence *current;
|
|
double x, y;
|
|
gboolean interpreted;
|
|
GtkEventSequenceState state;
|
|
|
|
click = GTK_GESTURE_CLICK (gesture);
|
|
priv = gtk_gesture_click_get_instance_private (click);
|
|
current = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture));
|
|
interpreted = gtk_gesture_get_point (gesture, current, &x, &y);
|
|
state = gtk_gesture_get_sequence_state (gesture, current);
|
|
|
|
if (state != GTK_EVENT_SEQUENCE_DENIED && interpreted)
|
|
g_signal_emit (gesture, signals[RELEASED], 0, priv->n_release, x, y);
|
|
|
|
priv->n_release = 0;
|
|
}
|
|
|
|
static void
|
|
gtk_gesture_click_cancel (GtkGesture *gesture,
|
|
GdkEventSequence *sequence)
|
|
{
|
|
_gtk_gesture_click_stop (GTK_GESTURE_CLICK (gesture));
|
|
GTK_GESTURE_CLASS (gtk_gesture_click_parent_class)->cancel (gesture, sequence);
|
|
}
|
|
|
|
static void
|
|
gtk_gesture_click_reset (GtkEventController *controller)
|
|
{
|
|
_gtk_gesture_click_stop (GTK_GESTURE_CLICK (controller));
|
|
GTK_EVENT_CONTROLLER_CLASS (gtk_gesture_click_parent_class)->reset (controller);
|
|
}
|
|
|
|
static gboolean
|
|
gtk_gesture_click_handle_event (GtkEventController *controller,
|
|
GdkEvent *event,
|
|
double x,
|
|
double y)
|
|
{
|
|
GtkEventControllerClass *parent_controller;
|
|
GtkGestureClickPrivate *priv;
|
|
GdkEventSequence *sequence;
|
|
guint button;
|
|
|
|
priv = gtk_gesture_click_get_instance_private (GTK_GESTURE_CLICK (controller));
|
|
parent_controller = GTK_EVENT_CONTROLLER_CLASS (gtk_gesture_click_parent_class);
|
|
sequence = gdk_event_get_event_sequence (event);
|
|
|
|
if (priv->n_presses == 0 &&
|
|
!gtk_gesture_handles_sequence (GTK_GESTURE (controller), sequence) &&
|
|
(gdk_event_get_event_type (event) == GDK_BUTTON_RELEASE ||
|
|
gdk_event_get_event_type (event) == GDK_TOUCH_END))
|
|
{
|
|
if (gdk_event_get_event_type (event) == GDK_BUTTON_RELEASE)
|
|
button = gdk_button_event_get_button (event);
|
|
else
|
|
button = 0;
|
|
g_signal_emit (controller, signals[UNPAIRED_RELEASE], 0,
|
|
x, y, button, sequence);
|
|
}
|
|
|
|
return parent_controller->handle_event (controller, event, x, y);
|
|
}
|
|
|
|
static void
|
|
gtk_gesture_click_class_init (GtkGestureClickClass *klass)
|
|
{
|
|
GtkEventControllerClass *controller_class = GTK_EVENT_CONTROLLER_CLASS (klass);
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
GtkGestureClass *gesture_class = GTK_GESTURE_CLASS (klass);
|
|
|
|
object_class->finalize = gtk_gesture_click_finalize;
|
|
|
|
gesture_class->check = gtk_gesture_click_check;
|
|
gesture_class->begin = gtk_gesture_click_begin;
|
|
gesture_class->update = gtk_gesture_click_update;
|
|
gesture_class->end = gtk_gesture_click_end;
|
|
gesture_class->cancel = gtk_gesture_click_cancel;
|
|
|
|
controller_class->reset = gtk_gesture_click_reset;
|
|
controller_class->handle_event = gtk_gesture_click_handle_event;
|
|
|
|
/**
|
|
* GtkGestureClick::pressed:
|
|
* @gesture: the object which received the signal
|
|
* @n_press: how many touch/button presses happened with this one
|
|
* @x: The X coordinate, in widget allocation coordinates
|
|
* @y: The Y coordinate, in widget allocation coordinates
|
|
*
|
|
* This signal is emitted whenever a button or touch press happens.
|
|
*/
|
|
signals[PRESSED] =
|
|
g_signal_new (I_("pressed"),
|
|
G_TYPE_FROM_CLASS (klass),
|
|
G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (GtkGestureClickClass, pressed),
|
|
NULL, NULL,
|
|
_gtk_marshal_VOID__INT_DOUBLE_DOUBLE,
|
|
G_TYPE_NONE, 3, G_TYPE_INT,
|
|
G_TYPE_DOUBLE, G_TYPE_DOUBLE);
|
|
g_signal_set_va_marshaller (signals[PRESSED],
|
|
G_TYPE_FROM_CLASS (klass),
|
|
_gtk_marshal_VOID__INT_DOUBLE_DOUBLEv);
|
|
|
|
/**
|
|
* GtkGestureClick::released:
|
|
* @gesture: the object which received the signal
|
|
* @n_press: number of press that is paired with this release
|
|
* @x: The X coordinate, in widget allocation coordinates
|
|
* @y: The Y coordinate, in widget allocation coordinates
|
|
*
|
|
* This signal is emitted when a button or touch is released. @n_press
|
|
* will report the number of press that is paired to this event, note
|
|
* that #GtkGestureClick::stopped may have been emitted between the
|
|
* press and its release, @n_press will only start over at the next press.
|
|
*/
|
|
signals[RELEASED] =
|
|
g_signal_new (I_("released"),
|
|
G_TYPE_FROM_CLASS (klass),
|
|
G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (GtkGestureClickClass, released),
|
|
NULL, NULL,
|
|
_gtk_marshal_VOID__INT_DOUBLE_DOUBLE,
|
|
G_TYPE_NONE, 3, G_TYPE_INT,
|
|
G_TYPE_DOUBLE, G_TYPE_DOUBLE);
|
|
g_signal_set_va_marshaller (signals[RELEASED],
|
|
G_TYPE_FROM_CLASS (klass),
|
|
_gtk_marshal_VOID__INT_DOUBLE_DOUBLEv);
|
|
/**
|
|
* GtkGestureClick::stopped:
|
|
* @gesture: the object which received the signal
|
|
*
|
|
* This signal is emitted whenever any time/distance threshold has
|
|
* been exceeded.
|
|
*/
|
|
signals[STOPPED] =
|
|
g_signal_new (I_("stopped"),
|
|
G_TYPE_FROM_CLASS (klass),
|
|
G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (GtkGestureClickClass, stopped),
|
|
NULL, NULL, NULL,
|
|
G_TYPE_NONE, 0);
|
|
|
|
/**
|
|
* GtkGestureClick::unpaired-release
|
|
* @gesture: the object which received the signal
|
|
* @x: X coordinate of the event
|
|
* @y: Y coordinate of the event
|
|
* @button: Button being released
|
|
* @sequence: Sequence being released
|
|
*
|
|
* This signal is emitted whenever the gesture receives a release
|
|
* event that had no previous corresponding press. Due to implicit
|
|
* grabs, this can only happen on situations where input is grabbed
|
|
* elsewhere mid-press or the pressed widget voluntarily relinquishes
|
|
* its implicit grab.
|
|
*/
|
|
signals[UNPAIRED_RELEASE] =
|
|
g_signal_new (I_("unpaired-release"),
|
|
G_TYPE_FROM_CLASS (klass),
|
|
G_SIGNAL_RUN_LAST,
|
|
0, NULL, NULL,
|
|
_gtk_marshal_VOID__DOUBLE_DOUBLE_UINT_BOXED,
|
|
G_TYPE_NONE, 4,
|
|
G_TYPE_DOUBLE, G_TYPE_DOUBLE,
|
|
G_TYPE_UINT, GDK_TYPE_EVENT_SEQUENCE);
|
|
g_signal_set_va_marshaller (signals[UNPAIRED_RELEASE],
|
|
G_TYPE_FROM_CLASS (klass),
|
|
_gtk_marshal_VOID__DOUBLE_DOUBLE_UINT_BOXEDv);
|
|
}
|
|
|
|
static void
|
|
gtk_gesture_click_init (GtkGestureClick *gesture)
|
|
{
|
|
}
|
|
|
|
/**
|
|
* gtk_gesture_click_new:
|
|
*
|
|
* Returns a newly created #GtkGesture that recognizes single and multiple
|
|
* presses.
|
|
*
|
|
* Returns: a newly created #GtkGestureClick
|
|
**/
|
|
GtkGesture *
|
|
gtk_gesture_click_new (void)
|
|
{
|
|
return g_object_new (GTK_TYPE_GESTURE_CLICK, NULL);
|
|
}
|