/* * Copyright (C) 2012, One Laptop Per Child. * * 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, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. * * Author(s): Carlos Garnacho */ #include "sugar-long-press-controller.h" #define DEFAULT_THRESHOLD 32 #define DEFAULT_TIMEOUT 800 typedef struct _SugarLongPressControllerPriv SugarLongPressControllerPriv; enum { PROP_0, PROP_THRESHOLD, PROP_TIMEOUT }; struct _SugarLongPressControllerPriv { GdkDevice *device; GdkEventSequence *sequence; gint x; gint y; guint timeout_id; guint threshold; guint timeout; guint cancelled : 1; guint triggered : 1; }; G_DEFINE_TYPE (SugarLongPressController, sugar_long_press_controller, SUGAR_TYPE_EVENT_CONTROLLER) static void sugar_long_press_controller_init (SugarLongPressController *controller) { SugarLongPressControllerPriv *priv; controller->_priv = priv = G_TYPE_INSTANCE_GET_PRIVATE (controller, SUGAR_TYPE_LONG_PRESS_CONTROLLER, SugarLongPressControllerPriv); priv->threshold = DEFAULT_THRESHOLD; priv->timeout = DEFAULT_TIMEOUT; priv->x = priv->y = -1; } static void _sugar_long_press_controller_unset_device (SugarLongPressController *controller) { SugarLongPressControllerPriv *priv = controller->_priv; if (priv->device) { g_object_unref (priv->device); priv->device = NULL; } priv->sequence = NULL; priv->x = priv->y = -1; priv->cancelled = priv->triggered = FALSE; } static gboolean _sugar_long_press_controller_cancel (SugarLongPressController *controller) { SugarLongPressControllerPriv *priv = controller->_priv; if (priv->timeout_id) { g_source_remove (priv->timeout_id); priv->timeout_id = 0; priv->cancelled = TRUE; g_object_notify (G_OBJECT (controller), "state"); return TRUE; } return FALSE; } static void sugar_long_press_controller_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { SugarLongPressControllerPriv *priv = SUGAR_LONG_PRESS_CONTROLLER (object)->_priv; switch (prop_id) { case PROP_THRESHOLD: g_value_set_uint (value, priv->threshold); break; case PROP_TIMEOUT: g_value_set_uint (value, priv->timeout); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void sugar_long_press_controller_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { SugarLongPressControllerPriv *priv = SUGAR_LONG_PRESS_CONTROLLER (object)->_priv; switch (prop_id) { case PROP_THRESHOLD: priv->threshold = g_value_get_uint (value); break; case PROP_TIMEOUT: priv->timeout = g_value_get_uint (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void sugar_long_press_controller_finalize (GObject *object) { SugarLongPressController *controller = SUGAR_LONG_PRESS_CONTROLLER (object); _sugar_long_press_controller_cancel (controller); _sugar_long_press_controller_unset_device (controller); G_OBJECT_CLASS (sugar_long_press_controller_parent_class)->finalize (object); } static gboolean _sugar_long_press_controller_timeout (gpointer user_data) { SugarLongPressController *controller = user_data; SugarLongPressControllerPriv *priv = controller->_priv; priv->timeout_id = 0; priv->triggered = TRUE; g_signal_emit_by_name (controller, "started"); return FALSE; } static SugarEventControllerState sugar_long_press_controller_get_state (SugarEventController *controller) { SugarLongPressControllerPriv *priv; priv = SUGAR_LONG_PRESS_CONTROLLER (controller)->_priv; if (priv->device) { if (priv->timeout_id) return SUGAR_EVENT_CONTROLLER_STATE_COLLECTING; else if (priv->cancelled) return SUGAR_EVENT_CONTROLLER_STATE_NOT_RECOGNIZED; else if (priv->triggered) return SUGAR_EVENT_CONTROLLER_STATE_RECOGNIZED; } return SUGAR_EVENT_CONTROLLER_STATE_NONE; } static void sugar_long_press_controller_reset (SugarEventController *controller) { SugarLongPressControllerPriv *priv; priv = SUGAR_LONG_PRESS_CONTROLLER (controller)->_priv; if (priv->triggered) g_signal_emit_by_name (controller, "finished"); _sugar_long_press_controller_cancel (SUGAR_LONG_PRESS_CONTROLLER (controller)); _sugar_long_press_controller_unset_device (SUGAR_LONG_PRESS_CONTROLLER (controller)); g_object_notify (G_OBJECT (controller), "state"); } static gboolean sugar_long_press_controller_handle_event (SugarEventController *controller, GdkEvent *event) { SugarLongPressControllerPriv *priv; GdkEventSequence *sequence; gboolean handled = TRUE; GdkDevice *device; priv = SUGAR_LONG_PRESS_CONTROLLER (controller)->_priv; device = gdk_event_get_device (event); sequence = gdk_event_get_event_sequence (event); if (priv->device) { if (priv->device != device) return FALSE; if (sequence && priv->sequence != sequence) { /* Another touch is simultaneously operating, * give up on recognizing a long press. */ _sugar_long_press_controller_cancel (SUGAR_LONG_PRESS_CONTROLLER (controller)); return FALSE; } } switch (event->type) { case GDK_TOUCH_BEGIN: priv->device = g_object_ref (device); priv->x = event->touch.x; priv->y = event->touch.y; priv->sequence = sequence; priv->timeout_id = gdk_threads_add_timeout (priv->timeout, _sugar_long_press_controller_timeout, controller); g_object_notify (G_OBJECT (controller), "state"); break; case GDK_TOUCH_UPDATE: if (ABS (priv->x - event->touch.x) > priv->threshold || ABS (priv->y - event->touch.y) > priv->threshold) _sugar_long_press_controller_cancel (SUGAR_LONG_PRESS_CONTROLLER (controller)); break; case GDK_TOUCH_END: sugar_event_controller_reset (controller); break; default: handled = FALSE; break; } return handled; } static void sugar_long_press_controller_class_init (SugarLongPressControllerClass *klass) { SugarEventControllerClass *controller_class; GObjectClass *object_class; object_class = G_OBJECT_CLASS (klass); object_class->get_property = sugar_long_press_controller_get_property; object_class->set_property = sugar_long_press_controller_set_property; object_class->finalize = sugar_long_press_controller_finalize; controller_class = SUGAR_EVENT_CONTROLLER_CLASS (klass); controller_class->handle_event = sugar_long_press_controller_handle_event; controller_class->get_state = sugar_long_press_controller_get_state; controller_class->reset = sugar_long_press_controller_reset; g_object_class_install_property (object_class, PROP_THRESHOLD, g_param_spec_uint ("threshold", "Threshold", "Threshold in pixels where the long " "press operation remains valid", 0, G_MAXUINT, DEFAULT_THRESHOLD, G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB)); g_object_class_install_property (object_class, PROP_TIMEOUT, g_param_spec_uint ("timeout", "Timeout", "Value in milliseconds to timeout the triggering", 0, G_MAXUINT, DEFAULT_TIMEOUT, G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB)); g_type_class_add_private (klass, sizeof (SugarLongPressControllerPriv)); } SugarEventController * sugar_long_press_controller_new (void) { return g_object_new (SUGAR_TYPE_LONG_PRESS_CONTROLLER, NULL); }