Release Clutter 1.11.4 (snapshot)
[profile/ivi/clutter.git] / clutter / clutter-gesture-action.c
1 /*
2  * Clutter.
3  *
4  * An OpenGL based 'interactive canvas' library.
5  *
6  * Copyright (C) 2010  Intel Corporation.
7  * Copyright (C) 2011  Robert Bosch Car Multimedia GmbH.
8  *
9  * This library is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Lesser General Public
11  * License as published by the Free Software Foundation; either
12  * version 2 of the License, or (at your option) any later version.
13  *
14  * This library is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  * Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public
20  * License along with this library. If not, see <http://www.gnu.org/licenses/>.
21  *
22  * Author:
23  *   Tomeu Vizoso <tomeu.vizoso@collabora.co.uk>
24  */
25
26 /**
27  * SECTION:clutter-gesture-action
28  * @Title: ClutterGestureAction
29  * @Short_Description: Action for gesture gestures
30  *
31  * #ClutterGestureAction is a sub-class of #ClutterAction that implements
32  * the logic for recognizing gesture gestures. It listens for low level events
33  * such as #ClutterButtonEvent and #ClutterMotionEvent on the stage to raise
34  * the #ClutterGestureAction::gesture-begin, #ClutterGestureAction::gesture-progress,
35  * and #ClutterGestureAction::gesture-end signals.
36  *
37  * To use #ClutterGestureAction you just need to apply it to a #ClutterActor
38  * using clutter_actor_add_action() and connect to the signals:
39  *
40  * |[
41  *   ClutterAction *action = clutter_gesture_action_new ();
42  *
43  *   clutter_actor_add_action (actor, action);
44  *
45  *   g_signal_connect (action, "gesture-begin", G_CALLBACK (on_gesture_begin), NULL);
46  *   g_signal_connect (action, "gesture-progress", G_CALLBACK (on_gesture_progress), NULL);
47  *   g_signal_connect (action, "gesture-end", G_CALLBACK (on_gesture_end), NULL);
48  * ]|
49  *
50  * Since: 1.8
51  */
52
53 #ifdef HAVE_CONFIG_H
54 #include "config.h"
55 #endif
56
57 #include "clutter-gesture-action.h"
58
59 #include "clutter-debug.h"
60 #include "clutter-enum-types.h"
61 #include "clutter-marshal.h"
62 #include "clutter-private.h"
63
64 struct _ClutterGestureActionPrivate
65 {
66   ClutterActor *stage;
67
68   guint actor_capture_id;
69   gulong stage_capture_id;
70
71   gfloat press_x, press_y;
72   gfloat last_motion_x, last_motion_y;
73   gfloat release_x, release_y;
74
75   guint in_drag : 1;
76 };
77
78 enum
79 {
80   GESTURE_BEGIN,
81   GESTURE_PROGRESS,
82   GESTURE_END,
83   GESTURE_CANCEL,
84
85   LAST_SIGNAL
86 };
87
88 static guint gesture_signals[LAST_SIGNAL] = { 0, };
89
90 G_DEFINE_TYPE (ClutterGestureAction, clutter_gesture_action, CLUTTER_TYPE_ACTION);
91
92 static gboolean
93 signal_accumulator (GSignalInvocationHint *ihint,
94                     GValue                *return_accu,
95                     const GValue          *handler_return,
96                     gpointer               user_data)
97 {
98   gboolean continue_emission;
99
100   continue_emission = g_value_get_boolean (handler_return);
101   g_value_set_boolean (return_accu, continue_emission);
102
103   return continue_emission;
104 }
105
106 static void
107 cancel_gesture (ClutterGestureAction *action)
108 {
109   ClutterGestureActionPrivate *priv = action->priv;
110   ClutterActor *actor;
111
112   priv->in_drag = FALSE;
113
114   g_signal_handler_disconnect (priv->stage, priv->stage_capture_id);
115   priv->stage_capture_id = 0;
116
117   actor = clutter_actor_meta_get_actor (CLUTTER_ACTOR_META (action));
118   g_signal_emit (action, gesture_signals[GESTURE_CANCEL], 0, actor);
119 }
120
121 static gboolean
122 stage_captured_event_cb (ClutterActor       *stage,
123                          ClutterEvent       *event,
124                          ClutterGestureAction *action)
125 {
126   ClutterGestureActionPrivate *priv = action->priv;
127   ClutterActor *actor;
128   gboolean return_value;
129
130   actor = clutter_actor_meta_get_actor (CLUTTER_ACTOR_META (action));
131
132   switch (clutter_event_type (event))
133     {
134     case CLUTTER_MOTION:
135       {
136         ClutterModifierType mods = clutter_event_get_state (event);
137
138         /* we might miss a button-release event in case of grabs,
139          * so we need to check whether the button is still down
140          * during a motion event
141          */
142         if (!(mods & CLUTTER_BUTTON1_MASK))
143           {
144             cancel_gesture (action);
145             return CLUTTER_EVENT_PROPAGATE;
146           }
147
148         clutter_event_get_coords (event, &priv->last_motion_x,
149                                          &priv->last_motion_y);
150
151         if (!clutter_actor_transform_stage_point (actor,
152                                                   priv->last_motion_x,
153                                                   priv->last_motion_y,
154                                                   NULL, NULL))
155           return CLUTTER_EVENT_PROPAGATE;
156
157         if (!priv->in_drag)
158           {
159             gint drag_threshold;
160             ClutterSettings *settings = clutter_settings_get_default ();
161
162             g_object_get (settings,
163                           "dnd-drag-threshold", &drag_threshold,
164                           NULL);
165
166             if ((ABS (priv->press_y - priv->last_motion_y) >= drag_threshold) ||
167                 (ABS (priv->press_x - priv->last_motion_x) >= drag_threshold))
168               {
169                 priv->in_drag = TRUE;
170
171                 g_signal_emit (action, gesture_signals[GESTURE_BEGIN], 0, actor,
172                                &return_value);
173                 if (!return_value)
174                   {
175                     cancel_gesture (action);
176                     return CLUTTER_EVENT_PROPAGATE;
177                   }
178               }
179             else
180               return CLUTTER_EVENT_PROPAGATE;
181           }
182
183           g_signal_emit (action, gesture_signals[GESTURE_PROGRESS], 0, actor,
184                          &return_value);
185           if (!return_value)
186             {
187               cancel_gesture (action);
188               return CLUTTER_EVENT_PROPAGATE;
189             }
190       }
191       break;
192
193     case CLUTTER_BUTTON_RELEASE:
194       {
195         clutter_event_get_coords (event, &priv->release_x, &priv->release_y);
196
197         g_signal_handler_disconnect (priv->stage, priv->stage_capture_id);
198         priv->stage_capture_id = 0;
199
200         if (priv->in_drag)
201           {
202             priv->in_drag = FALSE;
203             g_signal_emit (action, gesture_signals[GESTURE_END], 0, actor);
204           }
205       }
206       break;
207
208     default:
209       break;
210     }
211
212   return CLUTTER_EVENT_PROPAGATE;
213 }
214
215 static gboolean
216 actor_captured_event_cb (ClutterActor *actor,
217                          ClutterEvent *event,
218                          ClutterGestureAction *action)
219 {
220   ClutterGestureActionPrivate *priv = action->priv;
221
222   if (clutter_event_type (event) != CLUTTER_BUTTON_PRESS)
223     return CLUTTER_EVENT_PROPAGATE;
224
225   if (!clutter_actor_meta_get_enabled (CLUTTER_ACTOR_META (action)))
226     return CLUTTER_EVENT_PROPAGATE;
227
228   clutter_event_get_coords (event, &priv->press_x, &priv->press_y);
229
230   if (priv->stage == NULL)
231     priv->stage = clutter_actor_get_stage (actor);
232
233   priv->stage_capture_id =
234     g_signal_connect_after (priv->stage, "captured-event",
235                             G_CALLBACK (stage_captured_event_cb),
236                             action);
237
238   return CLUTTER_EVENT_PROPAGATE;
239 }
240
241 static void
242 clutter_gesture_action_set_actor (ClutterActorMeta *meta,
243                                   ClutterActor     *actor)
244 {
245   ClutterGestureActionPrivate *priv = CLUTTER_GESTURE_ACTION (meta)->priv;
246   ClutterActorMetaClass *meta_class =
247     CLUTTER_ACTOR_META_CLASS (clutter_gesture_action_parent_class);
248
249   if (priv->actor_capture_id != 0)
250     {
251       ClutterActor *old_actor = clutter_actor_meta_get_actor (meta);
252
253       if (old_actor != NULL)
254         g_signal_handler_disconnect (old_actor, priv->actor_capture_id);
255
256       priv->actor_capture_id = 0;
257     }
258
259   if (priv->stage_capture_id != 0)
260     {
261       if (priv->stage != NULL)
262         g_signal_handler_disconnect (priv->stage, priv->stage_capture_id);
263
264       priv->stage_capture_id = 0;
265       priv->stage = NULL;
266     }
267
268   if (actor != NULL)
269     {
270       priv->actor_capture_id =
271         g_signal_connect (actor, "captured-event",
272                           G_CALLBACK (actor_captured_event_cb),
273                           meta);
274     }
275
276   meta_class->set_actor (meta, actor);
277 }
278
279 static gboolean
280 default_event_handler (ClutterGestureAction *action,
281                        ClutterActor *actor)
282 {
283   return TRUE;
284 }
285
286 static void
287 clutter_gesture_action_class_init (ClutterGestureActionClass *klass)
288 {
289   ClutterActorMetaClass *meta_class = CLUTTER_ACTOR_META_CLASS (klass);
290
291   g_type_class_add_private (klass, sizeof (ClutterGestureActionPrivate));
292
293   meta_class->set_actor = clutter_gesture_action_set_actor;
294
295   klass->gesture_begin = default_event_handler;
296   klass->gesture_progress = default_event_handler;
297
298   /**
299    * ClutterGestureAction::gesture-begin:
300    * @action: the #ClutterGestureAction that emitted the signal
301    * @actor: the #ClutterActor attached to the @action
302    *
303    * The ::gesture_begin signal is emitted when the #ClutterActor to which
304    * a #ClutterGestureAction has been applied starts receiving a gesture.
305    *
306    * Return value: %TRUE if the gesture should start, and %FALSE if
307    *   the gesture should be ignored.
308    *
309    * Since: 1.8
310    */
311   gesture_signals[GESTURE_BEGIN] =
312     g_signal_new (I_("gesture-begin"),
313                   G_TYPE_FROM_CLASS (klass),
314                   G_SIGNAL_RUN_LAST,
315                   G_STRUCT_OFFSET (ClutterGestureActionClass, gesture_begin),
316                   signal_accumulator, NULL,
317                   _clutter_marshal_BOOLEAN__OBJECT,
318                   G_TYPE_BOOLEAN, 1,
319                   CLUTTER_TYPE_ACTOR);
320
321   /**
322    * ClutterGestureAction::gesture-progress:
323    * @action: the #ClutterGestureAction that emitted the signal
324    * @actor: the #ClutterActor attached to the @action
325    *
326    * The ::gesture-progress signal is emitted for each motion event after
327    * the #ClutterGestureAction::gesture-begin signal has been emitted.
328    *
329    * Return value: %TRUE if the gesture should continue, and %FALSE if
330    *   the gesture should be cancelled.
331    *
332    * Since: 1.8
333    */
334   gesture_signals[GESTURE_PROGRESS] =
335     g_signal_new (I_("gesture-progress"),
336                   G_TYPE_FROM_CLASS (klass),
337                   G_SIGNAL_RUN_LAST,
338                   G_STRUCT_OFFSET (ClutterGestureActionClass, gesture_progress),
339                   signal_accumulator, NULL,
340                   _clutter_marshal_BOOLEAN__OBJECT,
341                   G_TYPE_BOOLEAN, 1,
342                   CLUTTER_TYPE_ACTOR);
343
344   /**
345    * ClutterGestureAction::gesture-end:
346    * @action: the #ClutterGestureAction that emitted the signal
347    * @actor: the #ClutterActor attached to the @action
348    *
349    * The ::gesture-end signal is emitted at the end of the gesture gesture,
350    * when the pointer's button is released
351    *
352    * This signal is emitted if and only if the #ClutterGestureAction::gesture-begin
353    * signal has been emitted first.
354    *
355    * Since: 1.8
356    */
357   gesture_signals[GESTURE_END] =
358     g_signal_new (I_("gesture-end"),
359                   G_TYPE_FROM_CLASS (klass),
360                   G_SIGNAL_RUN_LAST,
361                   G_STRUCT_OFFSET (ClutterGestureActionClass, gesture_end),
362                   NULL, NULL,
363                   _clutter_marshal_VOID__OBJECT,
364                   G_TYPE_NONE, 1,
365                   CLUTTER_TYPE_ACTOR);
366
367   /**
368    * ClutterGestureAction::gesture-cancel:
369    * @action: the #ClutterGestureAction that emitted the signal
370    * @actor: the #ClutterActor attached to the @action
371    *
372    * The ::gesture-cancel signal is emitted when the ongoing gesture gets
373    * cancelled from the #ClutterGestureAction::gesture-progress signal handler.
374    *
375    * This signal is emitted if and only if the #ClutterGestureAction::gesture-begin
376    * signal has been emitted first.
377    *
378    * Since: 1.8
379    */
380   gesture_signals[GESTURE_CANCEL] =
381     g_signal_new (I_("gesture-cancel"),
382                   G_TYPE_FROM_CLASS (klass),
383                   G_SIGNAL_RUN_LAST,
384                   G_STRUCT_OFFSET (ClutterGestureActionClass, gesture_cancel),
385                   NULL, NULL,
386                   _clutter_marshal_VOID__OBJECT,
387                   G_TYPE_NONE, 1,
388                   CLUTTER_TYPE_ACTOR);
389 }
390
391 static void
392 clutter_gesture_action_init (ClutterGestureAction *self)
393 {
394   self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, CLUTTER_TYPE_GESTURE_ACTION,
395                                             ClutterGestureActionPrivate);
396
397   self->priv->press_x = self->priv->press_y = 0.f;
398   self->priv->last_motion_x = self->priv->last_motion_y = 0.f;
399   self->priv->release_x = self->priv->release_y = 0.f;
400 }
401
402 /**
403  * clutter_gesture_action_new:
404  *
405  * Creates a new #ClutterGestureAction instance.
406  *
407  * Return value: the newly created #ClutterGestureAction
408  *
409  * Since: 1.8
410  */
411 ClutterAction *
412 clutter_gesture_action_new (void)
413 {
414   return g_object_new (CLUTTER_TYPE_GESTURE_ACTION, NULL);
415 }
416
417 /**
418  * clutter_gesture_action_get_press_coords:
419  * @action: a #ClutterGestureAction
420  * @device: currently unused, set to 0
421  * @press_x: (out): return location for the press event's X coordinate
422  * @press_y: (out): return location for the press event's Y coordinate
423  *
424  * Retrieves the coordinates, in stage space, of the press event
425  * that started the dragging for an specific pointer device
426  *
427  * Since: 1.8
428  */
429 void
430 clutter_gesture_action_get_press_coords (ClutterGestureAction *action,
431                                          guint                 device,
432                                          gfloat               *press_x,
433                                          gfloat               *press_y)
434 {
435   g_return_if_fail (CLUTTER_IS_GESTURE_ACTION (action));
436
437   if (device != 0)
438     g_warning ("Multi-device support not yet implemented");
439
440   if (press_x)
441     *press_x = action->priv->press_x;
442
443   if (press_y)
444     *press_y = action->priv->press_y;
445 }
446
447 /**
448  * clutter_gesture_action_get_motion_coords:
449  * @action: a #ClutterGestureAction
450  * @device: currently unused, set to 0
451  * @motion_x: (out): return location for the latest motion
452  *   event's X coordinate
453  * @motion_y: (out): return location for the latest motion
454  *   event's Y coordinate
455  *
456  * Retrieves the coordinates, in stage space, of the latest motion
457  * event during the dragging
458  *
459  * Since: 1.8
460  */
461 void
462 clutter_gesture_action_get_motion_coords (ClutterGestureAction *action,
463                                           guint                 device,
464                                           gfloat               *motion_x,
465                                           gfloat               *motion_y)
466 {
467   g_return_if_fail (CLUTTER_IS_GESTURE_ACTION (action));
468
469   if (device != 0)
470     g_warning ("Multi-device support not yet implemented");
471
472   if (motion_x)
473     *motion_x = action->priv->last_motion_x;
474
475   if (motion_y)
476     *motion_y = action->priv->last_motion_y;
477 }
478
479 /**
480  * clutter_gesture_action_get_release_coords:
481  * @action: a #ClutterGestureAction
482  * @device: currently unused, set to 0
483  * @release_x: (out): return location for the X coordinate of the last release
484  * @release_y: (out): return location for the Y coordinate of the last release
485  *
486  * Retrieves the coordinates, in stage space, of the point where the pointer
487  * device was last released.
488  *
489  * Since: 1.8
490  */
491 void
492 clutter_gesture_action_get_release_coords (ClutterGestureAction *action,
493                                            guint                 device,
494                                            gfloat               *release_x,
495                                            gfloat               *release_y)
496 {
497   g_return_if_fail (CLUTTER_IS_GESTURE_ACTION (action));
498
499   if (device != 0)
500     g_warning ("Multi-device support not yet implemented");
501
502   if (release_x)
503     *release_x = action->priv->release_x;
504
505   if (release_y)
506     *release_y = action->priv->release_y;
507 }