You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

307 lines
9.5 KiB
C

/*
* 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 <carlos@lanedo.com>
*/
#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);
}