4 * An OpenGL based 'interactive canvas' library.
6 * Copyright (C) 2010 Intel Corporation.
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public
10 * License as published by the Free Software Foundation; either
11 * version 2 of the License, or (at your option) any later version.
13 * This library is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Lesser General Public License for more details.
18 * You should have received a copy of the GNU Lesser General Public
19 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
22 * Emmanuele Bassi <ebassi@linux.intel.com>
26 * SECTION:clutter-click-action
27 * @Title: ClutterClickAction
28 * @Short_Description: Action for clickable actors
30 * #ClutterClickAction is a sub-class of #ClutterAction that implements
31 * the logic for clickable actors, by using the low level events of
32 * #ClutterActor, such as #ClutterActor::button-press-event and
33 * #ClutterActor::button-release-event, to synthesize the high level
34 * #ClutterClickAction::clicked signal.
36 * To use #ClutterClickAction you just need to apply it to a #ClutterActor
37 * using clutter_actor_add_action() and connect to the
38 * #ClutterClickAction::clicked signal:
41 * ClutterAction *action = clutter_click_action_new ();
43 * clutter_actor_add_action (actor, action);
45 * g_signal_connect (action, "clicked", G_CALLBACK (on_clicked), NULL);
48 * #ClutterClickAction also supports long press gestures: a long press is
49 * activated if the pointer remains pressed within a certain threshold (as
50 * defined by the #ClutterClickAction:long-press-threshold property) for a
51 * minimum amount of time (as the defined by the
52 * #ClutterClickAction:long-press-duration property).
53 * The #ClutterClickAction::long-press signal is emitted multiple times,
54 * using different #ClutterLongPressState values; to handle long presses
55 * you should connect to the #ClutterClickAction::long-press signal and
56 * handle the different states:
60 * on_long_press (ClutterClickAction *action,
61 * ClutterActor *actor,
62 * ClutterLongPressState state)
66 * case CLUTTER_LONG_PRESS_QUERY:
67 * /* return TRUE if the actor should support long press
68 * * gestures, and FALSE otherwise; this state will be
69 * * emitted on button presses
73 * case CLUTTER_LONG_PRESS_ACTIVATE:
74 * /* this state is emitted if the minimum duration has
75 * * been reached without the gesture being cancelled.
76 * * the return value is not used
80 * case CLUTTER_LONG_PRESS_CANCEL:
81 * /* this state is emitted if the long press was cancelled;
82 * * for instance, the pointer went outside the actor or the
83 * * allowed threshold, or the button was released before
84 * * the minimum duration was reached. the return value is
92 * #ClutterClickAction is available since Clutter 1.4
99 #include "clutter-click-action.h"
101 #include "clutter-debug.h"
102 #include "clutter-enum-types.h"
103 #include "clutter-marshal.h"
104 #include "clutter-private.h"
106 struct _ClutterClickActionPrivate
114 gint long_press_threshold;
115 gint long_press_duration;
119 ClutterModifierType modifier_state;
124 guint is_pressed : 1;
133 PROP_LONG_PRESS_THRESHOLD,
134 PROP_LONG_PRESS_DURATION,
139 static GParamSpec *obj_props[PROP_LAST] = { NULL, };
149 static guint click_signals[LAST_SIGNAL] = { 0, };
151 G_DEFINE_TYPE (ClutterClickAction, clutter_click_action, CLUTTER_TYPE_ACTION);
153 /* forward declaration */
154 static gboolean on_captured_event (ClutterActor *stage,
156 ClutterClickAction *action);
159 click_action_set_pressed (ClutterClickAction *action,
162 ClutterClickActionPrivate *priv = action->priv;
164 is_pressed = !!is_pressed;
166 if (priv->is_pressed == is_pressed)
169 priv->is_pressed = is_pressed;
170 g_object_notify_by_pspec (G_OBJECT (action), obj_props[PROP_PRESSED]);
174 click_action_set_held (ClutterClickAction *action,
177 ClutterClickActionPrivate *priv = action->priv;
181 if (priv->is_held == is_held)
184 priv->is_held = is_held;
185 g_object_notify_by_pspec (G_OBJECT (action), obj_props[PROP_HELD]);
189 click_action_emit_long_press (gpointer data)
191 ClutterClickAction *action = data;
192 ClutterClickActionPrivate *priv = action->priv;
196 priv->long_press_id = 0;
198 actor = clutter_actor_meta_get_actor (data);
200 g_signal_emit (action, click_signals[LONG_PRESS], 0,
202 CLUTTER_LONG_PRESS_ACTIVATE,
205 if (priv->capture_id != 0)
207 g_signal_handler_disconnect (priv->stage, priv->capture_id);
208 priv->capture_id = 0;
211 click_action_set_pressed (action, FALSE);
212 click_action_set_held (action, FALSE);
218 click_action_query_long_press (ClutterClickAction *action)
220 ClutterClickActionPrivate *priv = action->priv;
222 gboolean result = FALSE;
225 if (priv->long_press_duration < 0)
227 ClutterSettings *settings = clutter_settings_get_default ();
229 g_object_get (settings,
230 "long-press-duration", &timeout,
234 timeout = priv->long_press_duration;
236 actor = clutter_actor_meta_get_actor (CLUTTER_ACTOR_META (action));
238 g_signal_emit (action, click_signals[LONG_PRESS], 0,
240 CLUTTER_LONG_PRESS_QUERY,
245 priv->long_press_id =
246 clutter_threads_add_timeout (timeout,
247 click_action_emit_long_press,
253 click_action_cancel_long_press (ClutterClickAction *action)
255 ClutterClickActionPrivate *priv = action->priv;
257 if (priv->long_press_id != 0)
262 actor = clutter_actor_meta_get_actor (CLUTTER_ACTOR_META (action));
264 g_source_remove (priv->long_press_id);
265 priv->long_press_id = 0;
267 g_signal_emit (action, click_signals[LONG_PRESS], 0,
269 CLUTTER_LONG_PRESS_CANCEL,
275 on_event (ClutterActor *actor,
277 ClutterClickAction *action)
279 ClutterClickActionPrivate *priv = action->priv;
281 if (!clutter_actor_meta_get_enabled (CLUTTER_ACTOR_META (action)))
282 return CLUTTER_EVENT_PROPAGATE;
284 switch (clutter_event_type (event))
286 case CLUTTER_BUTTON_PRESS:
287 if (clutter_event_get_click_count (event) != 1)
288 return CLUTTER_EVENT_PROPAGATE;
291 return CLUTTER_EVENT_STOP;
293 if (!clutter_actor_contains (actor, clutter_event_get_source (event)))
294 return CLUTTER_EVENT_PROPAGATE;
296 priv->press_button = clutter_event_get_button (event);
297 priv->modifier_state = clutter_event_get_state (event);
298 clutter_event_get_coords (event, &priv->press_x, &priv->press_y);
300 if (priv->long_press_threshold < 0)
302 ClutterSettings *settings = clutter_settings_get_default ();
304 g_object_get (settings,
305 "dnd-drag-threshold", &priv->drag_threshold,
309 priv->drag_threshold = priv->long_press_threshold;
311 if (priv->stage == NULL)
312 priv->stage = clutter_actor_get_stage (actor);
314 priv->capture_id = g_signal_connect_after (priv->stage, "captured-event",
315 G_CALLBACK (on_captured_event),
318 click_action_set_pressed (action, TRUE);
319 click_action_set_held (action, TRUE);
320 click_action_query_long_press (action);
324 click_action_set_pressed (action, priv->is_held);
328 click_action_set_pressed (action, priv->is_held);
329 click_action_cancel_long_press (action);
336 return CLUTTER_EVENT_PROPAGATE;
340 on_captured_event (ClutterActor *stage,
342 ClutterClickAction *action)
344 ClutterClickActionPrivate *priv = action->priv;
346 ClutterModifierType modifier_state;
348 actor = clutter_actor_meta_get_actor (CLUTTER_ACTOR_META (action));
350 switch (clutter_event_type (event))
352 case CLUTTER_BUTTON_RELEASE:
354 return CLUTTER_EVENT_STOP;
356 if (clutter_event_get_button (event) != priv->press_button ||
357 clutter_event_get_click_count (event) != 1)
358 return CLUTTER_EVENT_PROPAGATE;
360 click_action_set_held (action, FALSE);
361 click_action_cancel_long_press (action);
363 /* disconnect the capture */
364 if (priv->capture_id != 0)
366 g_signal_handler_disconnect (priv->stage, priv->capture_id);
367 priv->capture_id = 0;
370 if (priv->long_press_id != 0)
372 g_source_remove (priv->long_press_id);
373 priv->long_press_id = 0;
376 if (!clutter_actor_contains (actor, clutter_event_get_source (event)))
377 return CLUTTER_EVENT_PROPAGATE;
379 /* exclude any button-mask so that we can compare
380 * the press and release states properly */
381 modifier_state = clutter_event_get_state (event) &
382 ~(CLUTTER_BUTTON1_MASK |
383 CLUTTER_BUTTON2_MASK |
384 CLUTTER_BUTTON3_MASK |
385 CLUTTER_BUTTON4_MASK |
386 CLUTTER_BUTTON5_MASK);
388 /* if press and release states don't match we
389 * simply ignore modifier keys. i.e. modifier keys
390 * are expected to be pressed throughout the whole
392 if (modifier_state != priv->modifier_state)
393 priv->modifier_state = 0;
395 click_action_set_pressed (action, FALSE);
396 g_signal_emit (action, click_signals[CLICKED], 0, actor);
401 gfloat motion_x, motion_y;
402 gfloat delta_x, delta_y;
405 return CLUTTER_EVENT_PROPAGATE;
407 clutter_event_get_coords (event, &motion_x, &motion_y);
409 delta_x = ABS (motion_x - priv->press_x);
410 delta_y = ABS (motion_y - priv->press_y);
412 if (delta_x > priv->drag_threshold ||
413 delta_y > priv->drag_threshold)
414 click_action_cancel_long_press (action);
422 return CLUTTER_EVENT_STOP;
426 clutter_click_action_set_actor (ClutterActorMeta *meta,
429 ClutterClickAction *action = CLUTTER_CLICK_ACTION (meta);
430 ClutterClickActionPrivate *priv = action->priv;
432 if (priv->event_id != 0)
434 ClutterActor *old_actor = clutter_actor_meta_get_actor (meta);
436 if (old_actor != NULL)
437 g_signal_handler_disconnect (old_actor, priv->event_id);
442 if (priv->capture_id != 0)
444 if (priv->stage != NULL)
445 g_signal_handler_disconnect (priv->stage, priv->capture_id);
447 priv->capture_id = 0;
451 if (priv->long_press_id != 0)
453 g_source_remove (priv->long_press_id);
454 priv->long_press_id = 0;
457 click_action_set_pressed (action, FALSE);
458 click_action_set_held (action, FALSE);
461 priv->event_id = g_signal_connect (actor, "event",
462 G_CALLBACK (on_event),
465 CLUTTER_ACTOR_META_CLASS (clutter_click_action_parent_class)->set_actor (meta, actor);
469 clutter_click_action_set_property (GObject *gobject,
474 ClutterClickActionPrivate *priv = CLUTTER_CLICK_ACTION (gobject)->priv;
478 case PROP_LONG_PRESS_DURATION:
479 priv->long_press_duration = g_value_get_int (value);
482 case PROP_LONG_PRESS_THRESHOLD:
483 priv->long_press_threshold = g_value_get_int (value);
487 G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
493 clutter_click_action_get_property (GObject *gobject,
498 ClutterClickActionPrivate *priv = CLUTTER_CLICK_ACTION (gobject)->priv;
503 g_value_set_boolean (value, priv->is_held);
507 g_value_set_boolean (value, priv->is_pressed);
510 case PROP_LONG_PRESS_DURATION:
511 g_value_set_int (value, priv->long_press_duration);
514 case PROP_LONG_PRESS_THRESHOLD:
515 g_value_set_int (value, priv->long_press_threshold);
519 G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
525 clutter_click_action_class_init (ClutterClickActionClass *klass)
527 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
528 ClutterActorMetaClass *meta_class = CLUTTER_ACTOR_META_CLASS (klass);
530 g_type_class_add_private (klass, sizeof (ClutterClickActionPrivate));
532 meta_class->set_actor = clutter_click_action_set_actor;
534 gobject_class->set_property = clutter_click_action_set_property;
535 gobject_class->get_property = clutter_click_action_get_property;
538 * ClutterClickAction:pressed:
540 * Whether the clickable actor should be in "pressed" state
544 obj_props[PROP_PRESSED] =
545 g_param_spec_boolean ("pressed",
547 P_("Whether the clickable should be in pressed state"),
549 CLUTTER_PARAM_READABLE);
552 * ClutterClickAction:held:
554 * Whether the clickable actor has the pointer grabbed
558 obj_props[PROP_HELD] =
559 g_param_spec_boolean ("held",
561 P_("Whether the clickable has a grab"),
563 CLUTTER_PARAM_READABLE);
566 * ClutterClickAction:long-press-duration:
568 * The minimum duration of a press for it to be recognized as a long
569 * press gesture, in milliseconds.
571 * A value of -1 will make the #ClutterClickAction use the value of
572 * the #ClutterSettings:long-press-duration property.
576 obj_props[PROP_LONG_PRESS_DURATION] =
577 g_param_spec_int ("long-press-duration",
578 P_("Long Press Duration"),
579 P_("The minimum duration of a long press to recognize the gesture"),
582 CLUTTER_PARAM_READWRITE);
585 * ClutterClickAction:long-press-threshold:
587 * The maximum allowed distance that can be covered (on both axes) before
588 * a long press gesture is cancelled, in pixels.
590 * A value of -1 will make the #ClutterClickAction use the value of
591 * the #ClutterSettings:dnd-drag-threshold property.
595 obj_props[PROP_LONG_PRESS_THRESHOLD] =
596 g_param_spec_int ("long-press-threshold",
597 P_("Long Press Threshold"),
598 P_("The maximum threshold before a long press is cancelled"),
601 CLUTTER_PARAM_READWRITE);
603 g_object_class_install_properties (gobject_class,
608 * ClutterClickAction::clicked:
609 * @action: the #ClutterClickAction that emitted the signal
610 * @actor: the #ClutterActor attached to the @action
612 * The ::clicked signal is emitted when the #ClutterActor to which
613 * a #ClutterClickAction has been applied should respond to a
614 * pointer button press and release events
618 click_signals[CLICKED] =
619 g_signal_new (I_("clicked"),
620 G_TYPE_FROM_CLASS (klass),
622 G_STRUCT_OFFSET (ClutterClickActionClass, clicked),
624 _clutter_marshal_VOID__OBJECT,
629 * ClutterClickAction::long-press:
630 * @action: the #ClutterClickAction that emitted the signal
631 * @actor: the #ClutterActor attached to the @action
632 * @state: the long press state
634 * The ::long-press signal is emitted during the long press gesture
637 * This signal can be emitted multiple times with different states.
639 * The %CLUTTER_LONG_PRESS_QUERY state will be emitted on button presses,
640 * and its return value will determine whether the long press handling
641 * should be initiated. If the signal handlers will return %TRUE, the
642 * %CLUTTER_LONG_PRESS_QUERY state will be followed either by a signal
643 * emission with the %CLUTTER_LONG_PRESS_ACTIVATE state if the long press
644 * constraints were respected, or by a signal emission with the
645 * %CLUTTER_LONG_PRESS_CANCEL state if the long press was cancelled.
647 * It is possible to forcibly cancel a long press detection using
648 * clutter_click_action_release().
650 * Return value: Only the %CLUTTER_LONG_PRESS_QUERY state uses the
651 * returned value of the handler; other states will ignore it
655 click_signals[LONG_PRESS] =
656 g_signal_new (I_("long-press"),
657 G_TYPE_FROM_CLASS (klass),
659 G_STRUCT_OFFSET (ClutterClickActionClass, long_press),
661 _clutter_marshal_BOOLEAN__OBJECT_ENUM,
664 CLUTTER_TYPE_LONG_PRESS_STATE);
668 clutter_click_action_init (ClutterClickAction *self)
670 self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, CLUTTER_TYPE_CLICK_ACTION,
671 ClutterClickActionPrivate);
673 self->priv->long_press_threshold = -1;
674 self->priv->long_press_duration = -1;
678 * clutter_click_action_new:
680 * Creates a new #ClutterClickAction instance
682 * Return value: the newly created #ClutterClickAction
687 clutter_click_action_new (void)
689 return g_object_new (CLUTTER_TYPE_CLICK_ACTION, NULL);
693 * clutter_click_action_release:
694 * @action: a #ClutterClickAction
696 * Emulates a release of the pointer button, which ungrabs the pointer
697 * and unsets the #ClutterClickAction:pressed state.
699 * This function will also cancel the long press gesture if one was
702 * This function is useful to break a grab, for instance after a certain
703 * amount of time has passed.
708 clutter_click_action_release (ClutterClickAction *action)
710 ClutterClickActionPrivate *priv;
712 g_return_if_fail (CLUTTER_IS_CLICK_ACTION (action));
719 /* disconnect the capture */
720 if (priv->capture_id != 0)
722 g_signal_handler_disconnect (priv->stage, priv->capture_id);
723 priv->capture_id = 0;
726 click_action_cancel_long_press (action);
727 click_action_set_held (action, FALSE);
728 click_action_set_pressed (action, FALSE);
732 * clutter_click_action_get_button:
733 * @action: a #ClutterClickAction
735 * Retrieves the button that was pressed.
737 * Return value: the button value
742 clutter_click_action_get_button (ClutterClickAction *action)
744 g_return_val_if_fail (CLUTTER_IS_CLICK_ACTION (action), 0);
746 return action->priv->press_button;
750 * clutter_click_action_get_state:
751 * @action: a #ClutterClickAction
753 * Retrieves the modifier state of the click action.
755 * Return value: the modifier state parameter, or 0
760 clutter_click_action_get_state (ClutterClickAction *action)
762 g_return_val_if_fail (CLUTTER_IS_CLICK_ACTION (action), 0);
764 return action->priv->modifier_state;
768 * clutter_click_action_get_coords:
769 * @action: a #ClutterClickAction
770 * @press_x: (out): return location for the X coordinate, or %NULL
771 * @press_y: (out): return location for the Y coordinate, or %NULL
773 * Retrieves the screen coordinates of the button press.
778 clutter_click_action_get_coords (ClutterClickAction *action,
782 g_return_if_fail (CLUTTER_IS_ACTION (action));
785 *press_x = action->priv->press_x;
788 *press_y = action->priv->press_y;