/* 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 . * * Author(s): Carlos Garnacho */ /** * 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 }; enum { PROP_DELAY_FACTOR = 1 }; struct _GtkGestureLongPressPrivate { gdouble initial_x; gdouble initial_y; gdouble delay_factor; 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) { GtkGestureLongPressPrivate *priv; priv = gtk_gesture_long_press_get_instance_private (GTK_GESTURE_LONG_PRESS (gesture)); priv->delay_factor = 1.0; } 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 G_SOURCE_REMOVE; } static void gtk_gesture_long_press_begin (GtkGesture *gesture, GdkEventSequence *sequence) { GtkGestureLongPressPrivate *priv; const GdkEvent *event; GdkEventType event_type; 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) return; event_type = gdk_event_get_event_type (event); if (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); delay = (gint)(priv->delay_factor * delay); 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); g_source_set_name_by_id (priv->timeout_id, "[gtk+] _gtk_gesture_long_press_timeout"); } 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_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { GtkGestureLongPressPrivate *priv; priv = gtk_gesture_long_press_get_instance_private (GTK_GESTURE_LONG_PRESS (object)); switch (property_id) { case PROP_DELAY_FACTOR: g_value_set_double (value, priv->delay_factor); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void gtk_gesture_long_press_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { GtkGestureLongPressPrivate *priv; priv = gtk_gesture_long_press_get_instance_private (GTK_GESTURE_LONG_PRESS (object)); switch (property_id) { case PROP_DELAY_FACTOR: priv->delay_factor = g_value_get_double (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } 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; object_class->get_property = gtk_gesture_long_press_get_property; object_class->set_property = gtk_gesture_long_press_set_property; 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; g_object_class_install_property (object_class, PROP_DELAY_FACTOR, g_param_spec_double ("delay-factor", P_("Delay factor"), P_("Factor by which to modify the default timeout"), 0.5, 2.0, 1.0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); /** * 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 (I_("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 (I_("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); }