[clutter] click-action: Add support for long press detection
- From: Emmanuele Bassi <ebassi src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [clutter] click-action: Add support for long press detection
- Date: Thu, 9 Jun 2011 14:13:49 +0000 (UTC)
commit 23a9980c721c3f6c2dbd35336ab70f05dda72031
Author: Emmanuele Bassi <ebassi linux intel com>
Date: Wed May 18 11:21:58 2011 +0100
click-action: Add support for long press detection
A long press is a special form of click action; the default
implementation uses a single signal with multiple states: query, action
and cancel. On click we use the "query" state to check whether the
ClutterClickAction supports long presses; if the callback returns TRUE
then we install a timeout and we either emit the "activate" state when
the timeout expires or we emit the "cancel" state if the pointer leaves
the actor, or if the pointer moves outside a certain threshold. If the
long press reached the "activate" state then we skip the clicked signal
emission.
clutter/clutter-click-action.c | 362 +++++++++++++++++++++++++++++++++++++++-
clutter/clutter-click-action.h | 32 ++++-
clutter/clutter-marshal.list | 1 +
3 files changed, 384 insertions(+), 11 deletions(-)
---
diff --git a/clutter/clutter-click-action.c b/clutter/clutter-click-action.c
index e93deb3..a124634 100644
--- a/clutter/clutter-click-action.c
+++ b/clutter/clutter-click-action.c
@@ -45,6 +45,50 @@
* g_signal_connect (action, "clicked", G_CALLBACK (on_clicked), NULL);
* ]|
*
+ * #ClutterClickAction also supports long press gestures: a long press is
+ * activated if the pointer remains pressed within a certain threshold (as
+ * defined by the #ClutterClickAction:long-press-threshold property) for a
+ * minimum amount of time (as the defined by the
+ * #ClutterClickAction:long-press-duration property).
+ * The #ClutterClickAction::long-press signal is emitted multiple times,
+ * using different #ClutterLongPressState values; to handle long presses
+ * you should connect to the #ClutterClickAction::long-press signal and
+ * handle the different states:
+ *
+ * |[
+ * static gboolean
+ * on_long_press (ClutterClickAction *action,
+ * ClutterActor *actor,
+ * ClutterLongPressState state)
+ * {
+ * switch (state)
+ * {
+ * case CLUTTER_LONG_PRESS_QUERY:
+ * /* return TRUE if the actor should support long press
+ * * gestures, and FALSE otherwise; this state will be
+ * * emitted on button presses
+ * */
+ * return TRUE;
+ *
+ * case CLUTTER_LONG_PRESS_ACTIVATE:
+ * /* this state is emitted if the minimum duration has
+ * * been reached without the gesture being cancelled.
+ * * the return value is not used
+ * */
+ * return TRUE;
+ *
+ * case CLUTTER_LONG_PRESS_CANCEL:
+ * /* this state is emitted if the long press was cancelled;
+ * * for instance, the pointer went outside the actor or the
+ * * allowed threshold, or the button was released before
+ * * the minimum duration was reached. the return value is
+ * * not used
+ * */
+ * return FALSE;
+ * }
+ * }
+ * ]|
+ *
* #ClutterClickAction is available since Clutter 1.4
*/
@@ -64,10 +108,17 @@ struct _ClutterClickActionPrivate
ClutterActor *stage;
guint event_id;
- gulong capture_id;
+ guint capture_id;
+ guint long_press_id;
+
+ gint long_press_threshold;
+ gint long_press_duration;
+ gint drag_threshold;
guint press_button;
ClutterModifierType modifier_state;
+ gfloat press_x;
+ gfloat press_y;
guint is_held : 1;
guint is_pressed : 1;
@@ -79,15 +130,18 @@ enum
PROP_HELD,
PROP_PRESSED,
+ PROP_LONG_PRESS_THRESHOLD,
+ PROP_LONG_PRESS_DURATION,
PROP_LAST
};
-static GParamSpec *obj_props[PROP_LAST];
+static GParamSpec *obj_props[PROP_LAST] = { NULL, };
enum
{
CLICKED,
+ LONG_PRESS,
LAST_SIGNAL
};
@@ -107,6 +161,8 @@ click_action_set_pressed (ClutterClickAction *action,
{
ClutterClickActionPrivate *priv = action->priv;
+ is_pressed = !!is_pressed;
+
if (priv->is_pressed == is_pressed)
return;
@@ -114,6 +170,107 @@ click_action_set_pressed (ClutterClickAction *action,
g_object_notify_by_pspec (G_OBJECT (action), obj_props[PROP_PRESSED]);
}
+static inline void
+click_action_set_held (ClutterClickAction *action,
+ gboolean is_held)
+{
+ ClutterClickActionPrivate *priv = action->priv;
+
+ is_held = !!is_held;
+
+ if (priv->is_held == is_held)
+ return;
+
+ priv->is_held = is_held;
+ g_object_notify_by_pspec (G_OBJECT (action), obj_props[PROP_HELD]);
+}
+
+static gboolean
+click_action_emit_long_press (gpointer data)
+{
+ ClutterClickAction *action = data;
+ ClutterClickActionPrivate *priv = action->priv;
+ ClutterActor *actor;
+ gboolean result;
+
+ priv->long_press_id = 0;
+
+ actor = clutter_actor_meta_get_actor (data);
+
+ g_signal_emit (action, click_signals[LONG_PRESS], 0,
+ actor,
+ CLUTTER_LONG_PRESS_ACTIVATE,
+ &result);
+
+ if (priv->capture_id != 0)
+ {
+ g_signal_handler_disconnect (priv->stage, priv->capture_id);
+ priv->capture_id = 0;
+ }
+
+ click_action_set_pressed (action, FALSE);
+ click_action_set_held (action, FALSE);
+
+ return FALSE;
+}
+
+static inline void
+click_action_query_long_press (ClutterClickAction *action)
+{
+ ClutterClickActionPrivate *priv = action->priv;
+ ClutterActor *actor;
+ gboolean result = FALSE;
+ gint timeout;
+
+ if (priv->long_press_duration < 0)
+ {
+ ClutterSettings *settings = clutter_settings_get_default ();
+
+ g_object_get (settings,
+ "long-press-duration", &timeout,
+ NULL);
+ }
+ else
+ timeout = priv->long_press_duration;
+
+ actor = clutter_actor_meta_get_actor (CLUTTER_ACTOR_META (action));
+
+ g_signal_emit (action, click_signals[LONG_PRESS], 0,
+ actor,
+ CLUTTER_LONG_PRESS_QUERY,
+ &result);
+
+ if (result)
+ {
+ priv->long_press_id =
+ clutter_threads_add_timeout (timeout,
+ click_action_emit_long_press,
+ action);
+ }
+}
+
+static inline void
+click_action_cancel_long_press (ClutterClickAction *action)
+{
+ ClutterClickActionPrivate *priv = action->priv;
+
+ if (priv->long_press_id != 0)
+ {
+ ClutterActor *actor;
+ gboolean result;
+
+ actor = clutter_actor_meta_get_actor (CLUTTER_ACTOR_META (action));
+
+ g_source_remove (priv->long_press_id);
+ priv->long_press_id = 0;
+
+ g_signal_emit (action, click_signals[LONG_PRESS], 0,
+ actor,
+ CLUTTER_LONG_PRESS_CANCEL,
+ &result);
+ }
+}
+
static gboolean
on_event (ClutterActor *actor,
ClutterEvent *event,
@@ -136,9 +293,20 @@ on_event (ClutterActor *actor,
if (!clutter_actor_contains (actor, clutter_event_get_source (event)))
return FALSE;
- priv->is_held = TRUE;
priv->press_button = clutter_event_get_button (event);
priv->modifier_state = clutter_event_get_state (event);
+ clutter_event_get_coords (event, &priv->press_x, &priv->press_y);
+
+ if (priv->long_press_threshold < 0)
+ {
+ ClutterSettings *settings = clutter_settings_get_default ();
+
+ g_object_get (settings,
+ "dnd-drag-threshold", &priv->drag_threshold,
+ NULL);
+ }
+ else
+ priv->drag_threshold = priv->long_press_threshold;
if (priv->stage == NULL)
priv->stage = clutter_actor_get_stage (actor);
@@ -148,6 +316,8 @@ on_event (ClutterActor *actor,
action);
click_action_set_pressed (action, TRUE);
+ click_action_set_held (action, TRUE);
+ click_action_query_long_press (action);
break;
case CLUTTER_ENTER:
@@ -156,6 +326,7 @@ on_event (ClutterActor *actor,
case CLUTTER_LEAVE:
click_action_set_pressed (action, priv->is_held);
+ click_action_cancel_long_press (action);
break;
default:
@@ -186,7 +357,8 @@ on_captured_event (ClutterActor *stage,
clutter_event_get_click_count (event) != 1)
return FALSE;
- priv->is_held = FALSE;
+ click_action_set_held (action, FALSE);
+ click_action_cancel_long_press (action);
/* disconnect the capture */
if (priv->capture_id != 0)
@@ -195,6 +367,12 @@ on_captured_event (ClutterActor *stage,
priv->capture_id = 0;
}
+ if (priv->long_press_id != 0)
+ {
+ g_source_remove (priv->long_press_id);
+ priv->long_press_id = 0;
+ }
+
if (!clutter_actor_contains (actor, clutter_event_get_source (event)))
return FALSE;
@@ -218,6 +396,25 @@ on_captured_event (ClutterActor *stage,
g_signal_emit (action, click_signals[CLICKED], 0, actor);
break;
+ case CLUTTER_MOTION:
+ {
+ gfloat motion_x, motion_y;
+ gfloat delta_x, delta_y;
+
+ if (!priv->is_held)
+ return FALSE;
+
+ clutter_event_get_coords (event, &motion_x, &motion_y);
+
+ delta_x = ABS (motion_x - priv->press_x);
+ delta_y = ABS (motion_y - priv->press_y);
+
+ if (delta_x > priv->drag_threshold ||
+ delta_y > priv->drag_threshold)
+ click_action_cancel_long_press (action);
+ }
+ break;
+
default:
break;
}
@@ -229,7 +426,8 @@ static void
clutter_click_action_set_actor (ClutterActorMeta *meta,
ClutterActor *actor)
{
- ClutterClickActionPrivate *priv = CLUTTER_CLICK_ACTION (meta)->priv;
+ ClutterClickAction *action = CLUTTER_CLICK_ACTION (meta);
+ ClutterClickActionPrivate *priv = action->priv;
if (priv->event_id != 0)
{
@@ -246,15 +444,48 @@ clutter_click_action_set_actor (ClutterActorMeta *meta,
priv->stage = NULL;
}
+ if (priv->long_press_id != 0)
+ {
+ g_source_remove (priv->long_press_id);
+ priv->long_press_id = 0;
+ }
+
+ click_action_set_pressed (action, FALSE);
+ click_action_set_held (action, FALSE);
+
if (actor != NULL)
priv->event_id = g_signal_connect (actor, "event",
G_CALLBACK (on_event),
- meta);
+ action);
CLUTTER_ACTOR_META_CLASS (clutter_click_action_parent_class)->set_actor (meta, actor);
}
static void
+clutter_click_action_set_property (GObject *gobject,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ ClutterClickActionPrivate *priv = CLUTTER_CLICK_ACTION (gobject)->priv;
+
+ switch (prop_id)
+ {
+ case PROP_LONG_PRESS_DURATION:
+ priv->long_press_duration = g_value_get_int (value);
+ break;
+
+ case PROP_LONG_PRESS_THRESHOLD:
+ priv->long_press_threshold = g_value_get_int (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+ break;
+ }
+}
+
+static void
clutter_click_action_get_property (GObject *gobject,
guint prop_id,
GValue *value,
@@ -272,6 +503,14 @@ clutter_click_action_get_property (GObject *gobject,
g_value_set_boolean (value, priv->is_pressed);
break;
+ case PROP_LONG_PRESS_DURATION:
+ g_value_set_int (value, priv->long_press_duration);
+ break;
+
+ case PROP_LONG_PRESS_THRESHOLD:
+ g_value_set_int (value, priv->long_press_threshold);
+ break;
+
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
break;
@@ -288,6 +527,7 @@ clutter_click_action_class_init (ClutterClickActionClass *klass)
meta_class->set_actor = clutter_click_action_set_actor;
+ gobject_class->set_property = clutter_click_action_set_property;
gobject_class->get_property = clutter_click_action_get_property;
/**
@@ -318,6 +558,44 @@ clutter_click_action_class_init (ClutterClickActionClass *klass)
FALSE,
CLUTTER_PARAM_READABLE);
+ /**
+ * ClutterClickAction:long-press-duration:
+ *
+ * The minimum duration of a press for it to be recognized as a long
+ * press gesture, in milliseconds.
+ *
+ * A value of -1 will make the #ClutterClickAction use the value of
+ * the #ClutterSettings:long-press-duration property.
+ *
+ * Since: 1.8
+ */
+ obj_props[PROP_LONG_PRESS_DURATION] =
+ g_param_spec_int ("long-press-duration",
+ P_("Long Press Duration"),
+ P_("The minimum duration of a long press to recognize the gesture"),
+ -1, G_MAXINT,
+ -1,
+ CLUTTER_PARAM_READWRITE);
+
+ /**
+ * ClutterClickAction:long-press-threshold:
+ *
+ * The maximum allowed distance that can be covered (on both axes) before
+ * a long press gesture is cancelled, in pixels.
+ *
+ * A value of -1 will make the #ClutterClickAction use the value of
+ * the #ClutterSettings:dnd-drag-threshold property.
+ *
+ * Since: 1.8
+ */
+ obj_props[PROP_LONG_PRESS_THRESHOLD] =
+ g_param_spec_int ("long-press-threshold",
+ P_("Long Press Threshold"),
+ P_("The maximum threshold before a long press is cancelled"),
+ -1, G_MAXINT,
+ -1,
+ CLUTTER_PARAM_READWRITE);
+
g_object_class_install_properties (gobject_class,
PROP_LAST,
obj_props);
@@ -342,6 +620,44 @@ clutter_click_action_class_init (ClutterClickActionClass *klass)
_clutter_marshal_VOID__OBJECT,
G_TYPE_NONE, 1,
CLUTTER_TYPE_ACTOR);
+
+ /**
+ * ClutterClickAction::long-press:
+ * @action: the #ClutterClickAction that emitted the signal
+ * @actor: the #ClutterActor attached to the @action
+ * @state: the long press state
+ *
+ * The ::long-press signal is emitted during the long press gesture
+ * handling.
+ *
+ * This signal can be emitted multiple times with different states.
+ *
+ * The %CLUTTER_LONG_PRESS_QUERY state will be emitted on button presses,
+ * and its return value will determine whether the long press handling
+ * should be initiated. If the signal handlers will return %TRUE, the
+ * %CLUTTER_LONG_PRESS_QUERY state will be followed either by a signal
+ * emission with the %CLUTTER_LONG_PRESS_ACTIVATE state if the long press
+ * constraints were respected, or by a signal emission with the
+ * %CLUTTER_LONG_PRESS_CANCEL state if the long press was cancelled.
+ *
+ * It is possible to forcibly cancel a long press detection using
+ * clutter_click_action_release().
+ *
+ * Return value: Only the %CLUTTER_LONG_PRESS_QUERY state uses the
+ * returned value of the handler; other states will ignore it
+ *
+ * Since: 1.8
+ */
+ click_signals[LONG_PRESS] =
+ g_signal_new (I_("long-press"),
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (ClutterClickActionClass, long_press),
+ NULL, NULL,
+ _clutter_marshal_BOOLEAN__OBJECT_ENUM,
+ G_TYPE_BOOLEAN, 2,
+ CLUTTER_TYPE_ACTOR,
+ CLUTTER_TYPE_LONG_PRESS_STATE);
}
static void
@@ -349,6 +665,9 @@ clutter_click_action_init (ClutterClickAction *self)
{
self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, CLUTTER_TYPE_CLICK_ACTION,
ClutterClickActionPrivate);
+
+ self->priv->long_press_threshold = -1;
+ self->priv->long_press_duration = -1;
}
/**
@@ -373,6 +692,9 @@ clutter_click_action_new (void)
* Emulates a release of the pointer button, which ungrabs the pointer
* and unsets the #ClutterClickAction:pressed state.
*
+ * This function will also cancel the long press gesture if one was
+ * initiated.
+ *
* This function is useful to break a grab, for instance after a certain
* amount of time has passed.
*
@@ -390,8 +712,6 @@ clutter_click_action_release (ClutterClickAction *action)
if (!priv->is_held)
return;
- priv->is_held = FALSE;
-
/* disconnect the capture */
if (priv->capture_id != 0)
{
@@ -399,6 +719,8 @@ clutter_click_action_release (ClutterClickAction *action)
priv->capture_id = 0;
}
+ click_action_cancel_long_press (action);
+ click_action_set_held (action, FALSE);
click_action_set_pressed (action, FALSE);
}
@@ -437,3 +759,27 @@ clutter_click_action_get_state (ClutterClickAction *action)
return action->priv->modifier_state;
}
+
+/**
+ * clutter_click_action_get_coords:
+ * @action: a #ClutterClickAction
+ * @press_x: (out): return location for the X coordinate, or %NULL
+ * @press_y: (out): return location for the Y coordinate, or %NULL
+ *
+ * Retrieves the screen coordinates of the button press.
+ *
+ * Since: 1.8
+ */
+void
+clutter_click_action_get_coords (ClutterClickAction *action,
+ gfloat *press_x,
+ gfloat *press_y)
+{
+ g_return_if_fail (CLUTTER_IS_ACTION (action));
+
+ if (press_x != NULL)
+ *press_x = action->priv->press_x;
+
+ if (press_y != NULL)
+ *press_y = action->priv->press_y;
+}
diff --git a/clutter/clutter-click-action.h b/clutter/clutter-click-action.h
index 0617462..e06b09d 100644
--- a/clutter/clutter-click-action.h
+++ b/clutter/clutter-click-action.h
@@ -49,6 +49,23 @@ typedef struct _ClutterClickActionPrivate ClutterClickActionPrivate;
typedef struct _ClutterClickActionClass ClutterClickActionClass;
/**
+ * ClutterLongPressState:
+ * @CLUTTER_LONG_PRESS_QUERY: Queries the action whether it supports
+ * long presses
+ * @CLUTTER_LONG_PRESS_ACTIVATE: Activates the action on a long press
+ * @CLUTTER_LONG_PRESS_CANCEL: The long press was cancelled
+ *
+ * The states for the #ClutterClikAction::long-press signal.
+ *
+ * Since: 1.8
+ */
+typedef enum { /*< prefix=CLUTTER_LONG_PRESS >*/
+ CLUTTER_LONG_PRESS_QUERY,
+ CLUTTER_LONG_PRESS_ACTIVATE,
+ CLUTTER_LONG_PRESS_CANCEL
+} ClutterLongPressState;
+
+/**
* ClutterClickAction:
*
* The <structname>ClutterClickAction</structname> structure contains
@@ -67,6 +84,7 @@ struct _ClutterClickAction
/**
* ClutterClickActionClass:
* @clicked: class handler for the #ClutterClickAction::clicked signal
+ * @long_press: class handler for the #ClutterClickAction::long-press signal
*
* The <structname>ClutterClickActionClass</structname> structure
* contains only private data
@@ -79,8 +97,12 @@ struct _ClutterClickActionClass
ClutterActionClass parent_class;
/*< public >*/
- void (* clicked) (ClutterClickAction *action,
- ClutterActor *actor);
+ void (* clicked) (ClutterClickAction *action,
+ ClutterActor *actor);
+
+ gboolean (* long_press) (ClutterClickAction *action,
+ ClutterActor *actor,
+ ClutterLongPressState state);
/*< private >*/
void (* _clutter_click_action1) (void);
@@ -94,10 +116,14 @@ struct _ClutterClickActionClass
GType clutter_click_action_get_type (void) G_GNUC_CONST;
-ClutterAction *clutter_click_action_new (void);
+ClutterAction * clutter_click_action_new (void);
guint clutter_click_action_get_button (ClutterClickAction *action);
ClutterModifierType clutter_click_action_get_state (ClutterClickAction *action);
+void clutter_click_action_get_coords (ClutterClickAction *action,
+ gfloat *press_x,
+ gfloat *press_y);
+
void clutter_click_action_release (ClutterClickAction *action);
G_END_DECLS
diff --git a/clutter/clutter-marshal.list b/clutter/clutter-marshal.list
index 49745ef..563508f 100644
--- a/clutter/clutter-marshal.list
+++ b/clutter/clutter-marshal.list
@@ -1,4 +1,5 @@
BOOLEAN:BOXED
+BOOLEAN:OBJECT,ENUM
BOOLEAN:STRING,UINT,FLAGS
BOXED:UINT,UINT
DOUBLE:VOID
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]