gtk2/gtk/gtkgesturelongpress.c
Carlos Garnacho 6d8842ca96 gesturesingle: Implement GtkGesture::cancel better than GtkEventController:reset
The former can be called individually on each sequence, and the latter will
always call the former on all currently active sequences, so only implementing
resetting on cancel() works for both cases. Also, chain up on subclasses
implementing cancel.

This fixes clicking on nautilus' file list after popping up a menu, as broken
grabs are one of those situations where sequences get cancelled individually,
the "current button" wasn't properly reset, and further clicks with button != 3
were ignored.
2014-06-13 01:27:31 +02:00

279 lines
8.2 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:gtkgesturelongpress
* @Short_description: "Press and Hold" gesture
* @Title: GtkGestureLongPress
*
* #GtkGestureLongPress is a #GtkGesture implementation able to recognize
* long presses, triggering the #GtkGestureLongPress::pressed after the
* timeout is exceeded.
*
* If the touchpoint is lifted before the timeout passes, or if it drifts
* too far of the initial press point, the #GtkGestureLongPress::cancelled
* signal will be emitted.
*/
#include "config.h"
#include "gtkgesturelongpress.h"
#include "gtkgesturelongpressprivate.h"
#include "gtkgestureprivate.h"
#include "gtkmarshalers.h"
#include "gtkdnd.h"
#include "gtkprivate.h"
#include "gtkintl.h"
typedef struct _GtkGestureLongPressPrivate GtkGestureLongPressPrivate;
enum {
PRESSED,
CANCELLED,
N_SIGNALS
};
struct _GtkGestureLongPressPrivate
{
gdouble initial_x;
gdouble initial_y;
guint timeout_id;
guint delay;
guint cancelled : 1;
guint triggered : 1;
};
static guint signals[N_SIGNALS] = { 0 };
G_DEFINE_TYPE_WITH_PRIVATE (GtkGestureLongPress, gtk_gesture_long_press, GTK_TYPE_GESTURE_SINGLE)
static void
gtk_gesture_long_press_init (GtkGestureLongPress *gesture)
{
}
static gboolean
gtk_gesture_long_press_check (GtkGesture *gesture)
{
GtkGestureLongPressPrivate *priv;
priv = gtk_gesture_long_press_get_instance_private (GTK_GESTURE_LONG_PRESS (gesture));
if (priv->cancelled)
return FALSE;
return GTK_GESTURE_CLASS (gtk_gesture_long_press_parent_class)->check (gesture);
}
static gboolean
_gtk_gesture_long_press_timeout (gpointer user_data)
{
GtkGestureLongPress *gesture = user_data;
GtkGestureLongPressPrivate *priv;
GdkEventSequence *sequence;
gdouble x, y;
priv = gtk_gesture_long_press_get_instance_private (gesture);
sequence = gtk_gesture_get_last_updated_sequence (GTK_GESTURE (gesture));
gtk_gesture_get_point (GTK_GESTURE (gesture), sequence, &x, &y);
priv->timeout_id = 0;
priv->triggered = TRUE;
g_signal_emit (gesture, signals[PRESSED], 0, x, y);
return FALSE;
}
static void
gtk_gesture_long_press_begin (GtkGesture *gesture,
GdkEventSequence *sequence)
{
GtkGestureLongPressPrivate *priv;
const GdkEvent *event;
GtkWidget *widget;
gint delay;
priv = gtk_gesture_long_press_get_instance_private (GTK_GESTURE_LONG_PRESS (gesture));
sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture));
event = gtk_gesture_get_last_event (gesture, sequence);
if (!event ||
(event->type != GDK_BUTTON_PRESS &&
event->type != GDK_TOUCH_BEGIN))
return;
widget = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (gesture));
g_object_get (gtk_widget_get_settings (widget),
"gtk-long-press-time", &delay, NULL);
gtk_gesture_get_point (gesture, sequence,
&priv->initial_x, &priv->initial_y);
priv->timeout_id =
gdk_threads_add_timeout (delay,
_gtk_gesture_long_press_timeout,
gesture);
}
static void
gtk_gesture_long_press_update (GtkGesture *gesture,
GdkEventSequence *sequence)
{
GtkGestureLongPressPrivate *priv;
GtkWidget *widget;
gdouble x, y;
widget = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (gesture));
priv = gtk_gesture_long_press_get_instance_private (GTK_GESTURE_LONG_PRESS (gesture));
gtk_gesture_get_point (gesture, sequence, &x, &y);
if (gtk_drag_check_threshold (widget, priv->initial_x, priv->initial_y, x, y))
{
if (priv->timeout_id)
{
g_source_remove (priv->timeout_id);
priv->timeout_id = 0;
g_signal_emit (gesture, signals[CANCELLED], 0);
}
priv->cancelled = TRUE;
_gtk_gesture_check (gesture);
}
}
static void
gtk_gesture_long_press_end (GtkGesture *gesture,
GdkEventSequence *sequence)
{
GtkGestureLongPressPrivate *priv;
priv = gtk_gesture_long_press_get_instance_private (GTK_GESTURE_LONG_PRESS (gesture));
if (priv->timeout_id)
{
g_source_remove (priv->timeout_id);
priv->timeout_id = 0;
g_signal_emit (gesture, signals[CANCELLED], 0);
}
priv->cancelled = priv->triggered = FALSE;
}
static void
gtk_gesture_long_press_cancel (GtkGesture *gesture,
GdkEventSequence *sequence)
{
gtk_gesture_long_press_end (gesture, sequence);
GTK_GESTURE_CLASS (gtk_gesture_long_press_parent_class)->cancel (gesture, sequence);
}
static void
gtk_gesture_long_press_sequence_state_changed (GtkGesture *gesture,
GdkEventSequence *sequence,
GtkEventSequenceState state)
{
if (state == GTK_EVENT_SEQUENCE_DENIED)
gtk_gesture_long_press_end (gesture, sequence);
}
static void
gtk_gesture_long_press_finalize (GObject *object)
{
GtkGestureLongPressPrivate *priv;
priv = gtk_gesture_long_press_get_instance_private (GTK_GESTURE_LONG_PRESS (object));
if (priv->timeout_id)
g_source_remove (priv->timeout_id);
G_OBJECT_CLASS (gtk_gesture_long_press_parent_class)->finalize (object);
}
static void
gtk_gesture_long_press_class_init (GtkGestureLongPressClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GtkGestureClass *gesture_class = GTK_GESTURE_CLASS (klass);
object_class->finalize = gtk_gesture_long_press_finalize;
gesture_class->check = gtk_gesture_long_press_check;
gesture_class->begin = gtk_gesture_long_press_begin;
gesture_class->update = gtk_gesture_long_press_update;
gesture_class->end = gtk_gesture_long_press_end;
gesture_class->cancel = gtk_gesture_long_press_cancel;
gesture_class->sequence_state_changed =
gtk_gesture_long_press_sequence_state_changed;
/**
* GtkGestureLongPress::pressed:
* @gesture: the object which received the signal
* @x: the X coordinate where the press happened, relative to the widget allocation
* @y: the Y coordinate where the press happened, relative to the widget allocation
*
* This signal is emitted whenever a press goes unmoved/unreleased longer than
* what the GTK+ defaults tell.
*
* Since: 3.14
*/
signals[PRESSED] =
g_signal_new ("pressed",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (GtkGestureLongPressClass, pressed),
NULL, NULL, NULL,
G_TYPE_NONE, 2, G_TYPE_DOUBLE, G_TYPE_DOUBLE);
/**
* GtkGestureLongPress::cancelled:
* @gesture: the object which received the signal
*
* This signal is emitted whenever a press moved too far, or was released
* before #GtkGestureLongPress:pressed happened.
*
* Since: 3.14
*/
signals[CANCELLED] =
g_signal_new ("cancelled",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (GtkGestureLongPressClass, cancelled),
NULL, NULL, NULL,
G_TYPE_NONE, 0);
}
/**
* gtk_gesture_long_press_new:
* @widget: a #GtkWidget
*
* Returns a newly created #GtkGesture that recognizes long presses.
*
* Returns: a newly created #GtkGestureLongPress
*
* Since: 3.14
**/
GtkGesture *
gtk_gesture_long_press_new (GtkWidget *widget)
{
g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL);
return g_object_new (GTK_TYPE_GESTURE_LONG_PRESS,
"widget", widget,
NULL);
}