Release Clutter 1.11.4 (snapshot)
[profile/ivi/clutter.git] / clutter / clutter-click-action.c
1 /*
2  * Clutter.
3  *
4  * An OpenGL based 'interactive canvas' library.
5  *
6  * Copyright (C) 2010  Intel Corporation.
7  *
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.
12  *
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.
17  *
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/>.
20  *
21  * Author:
22  *   Emmanuele Bassi <ebassi@linux.intel.com>
23  */
24
25 /**
26  * SECTION:clutter-click-action
27  * @Title: ClutterClickAction
28  * @Short_Description: Action for clickable actors
29  *
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.
35  *
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:
39  *
40  * |[
41  *   ClutterAction *action = clutter_click_action_new ();
42  *
43  *   clutter_actor_add_action (actor, action);
44  *
45  *   g_signal_connect (action, "clicked", G_CALLBACK (on_clicked), NULL);
46  * ]|
47  *
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:
57  *
58  * |[
59  *   static gboolean
60  *   on_long_press (ClutterClickAction    *action,
61  *                  ClutterActor          *actor,
62  *                  ClutterLongPressState  state)
63  *   {
64  *     switch (state)
65  *       {
66  *       case CLUTTER_LONG_PRESS_QUERY:
67  *         /&ast; return TRUE if the actor should support long press
68  *          &ast; gestures, and FALSE otherwise; this state will be
69  *          &ast; emitted on button presses
70  *          &ast;/
71  *         return TRUE;
72  *
73  *       case CLUTTER_LONG_PRESS_ACTIVATE:
74  *         /&ast; this state is emitted if the minimum duration has
75  *          &ast; been reached without the gesture being cancelled.
76  *          &ast; the return value is not used
77  *          &ast;/
78  *         return TRUE;
79  *
80  *       case CLUTTER_LONG_PRESS_CANCEL:
81  *         /&ast; this state is emitted if the long press was cancelled;
82  *          &ast; for instance, the pointer went outside the actor or the
83  *          &ast; allowed threshold, or the button was released before
84  *          &ast; the minimum duration was reached. the return value is
85  *          &ast; not used
86  *          &ast;/
87  *         return FALSE;
88  *       }
89  *   }
90  * ]|
91  *
92  * #ClutterClickAction is available since Clutter 1.4
93  */
94
95 #ifdef HAVE_CONFIG_H
96 #include "config.h"
97 #endif
98
99 #include "clutter-click-action.h"
100
101 #include "clutter-debug.h"
102 #include "clutter-enum-types.h"
103 #include "clutter-marshal.h"
104 #include "clutter-private.h"
105
106 struct _ClutterClickActionPrivate
107 {
108   ClutterActor *stage;
109
110   guint event_id;
111   guint capture_id;
112   guint long_press_id;
113
114   gint long_press_threshold;
115   gint long_press_duration;
116   gint drag_threshold;
117
118   guint press_button;
119   ClutterModifierType modifier_state;
120   gfloat press_x;
121   gfloat press_y;
122
123   guint is_held    : 1;
124   guint is_pressed : 1;
125 };
126
127 enum
128 {
129   PROP_0,
130
131   PROP_HELD,
132   PROP_PRESSED,
133   PROP_LONG_PRESS_THRESHOLD,
134   PROP_LONG_PRESS_DURATION,
135
136   PROP_LAST
137 };
138
139 static GParamSpec *obj_props[PROP_LAST] = { NULL, };
140
141 enum
142 {
143   CLICKED,
144   LONG_PRESS,
145
146   LAST_SIGNAL
147 };
148
149 static guint click_signals[LAST_SIGNAL] = { 0, };
150
151 G_DEFINE_TYPE (ClutterClickAction, clutter_click_action, CLUTTER_TYPE_ACTION);
152
153 /* forward declaration */
154 static gboolean on_captured_event (ClutterActor       *stage,
155                                    ClutterEvent       *event,
156                                    ClutterClickAction *action);
157
158 static inline void
159 click_action_set_pressed (ClutterClickAction *action,
160                           gboolean            is_pressed)
161 {
162   ClutterClickActionPrivate *priv = action->priv;
163
164   is_pressed = !!is_pressed;
165
166   if (priv->is_pressed == is_pressed)
167     return;
168
169   priv->is_pressed = is_pressed;
170   g_object_notify_by_pspec (G_OBJECT (action), obj_props[PROP_PRESSED]);
171 }
172
173 static inline void
174 click_action_set_held (ClutterClickAction *action,
175                        gboolean            is_held)
176 {
177   ClutterClickActionPrivate *priv = action->priv;
178
179   is_held = !!is_held;
180
181   if (priv->is_held == is_held)
182     return;
183
184   priv->is_held = is_held;
185   g_object_notify_by_pspec (G_OBJECT (action), obj_props[PROP_HELD]);
186 }
187
188 static gboolean
189 click_action_emit_long_press (gpointer data)
190 {
191   ClutterClickAction *action = data;
192   ClutterClickActionPrivate *priv = action->priv;
193   ClutterActor *actor;
194   gboolean result;
195
196   priv->long_press_id = 0;
197
198   actor = clutter_actor_meta_get_actor (data);
199
200   g_signal_emit (action, click_signals[LONG_PRESS], 0,
201                  actor,
202                  CLUTTER_LONG_PRESS_ACTIVATE,
203                  &result);
204
205   if (priv->capture_id != 0)
206     {
207       g_signal_handler_disconnect (priv->stage, priv->capture_id);
208       priv->capture_id = 0;
209     }
210
211   click_action_set_pressed (action, FALSE);
212   click_action_set_held (action, FALSE);
213
214   return FALSE;
215 }
216
217 static inline void
218 click_action_query_long_press (ClutterClickAction *action)
219 {
220   ClutterClickActionPrivate *priv = action->priv;
221   ClutterActor *actor;
222   gboolean result = FALSE;
223   gint timeout;
224
225   if (priv->long_press_duration < 0)
226     {
227       ClutterSettings *settings = clutter_settings_get_default ();
228
229       g_object_get (settings,
230                     "long-press-duration", &timeout,
231                     NULL);
232     }
233   else
234     timeout = priv->long_press_duration;
235
236   actor = clutter_actor_meta_get_actor (CLUTTER_ACTOR_META (action));
237
238   g_signal_emit (action, click_signals[LONG_PRESS], 0,
239                  actor,
240                  CLUTTER_LONG_PRESS_QUERY,
241                  &result);
242
243   if (result)
244     {
245       priv->long_press_id =
246         clutter_threads_add_timeout (timeout,
247                                      click_action_emit_long_press,
248                                      action);
249     }
250 }
251
252 static inline void
253 click_action_cancel_long_press (ClutterClickAction *action)
254 {
255   ClutterClickActionPrivate *priv = action->priv;
256
257   if (priv->long_press_id != 0)
258     {
259       ClutterActor *actor;
260       gboolean result;
261
262       actor = clutter_actor_meta_get_actor (CLUTTER_ACTOR_META (action));
263
264       g_source_remove (priv->long_press_id);
265       priv->long_press_id = 0;
266
267       g_signal_emit (action, click_signals[LONG_PRESS], 0,
268                      actor,
269                      CLUTTER_LONG_PRESS_CANCEL,
270                      &result);
271     }
272 }
273
274 static gboolean
275 on_event (ClutterActor       *actor,
276           ClutterEvent       *event,
277           ClutterClickAction *action)
278 {
279   ClutterClickActionPrivate *priv = action->priv;
280
281   if (!clutter_actor_meta_get_enabled (CLUTTER_ACTOR_META (action)))
282     return CLUTTER_EVENT_PROPAGATE;
283
284   switch (clutter_event_type (event))
285     {
286     case CLUTTER_BUTTON_PRESS:
287       if (clutter_event_get_click_count (event) != 1)
288         return CLUTTER_EVENT_PROPAGATE;
289
290       if (priv->is_held)
291         return CLUTTER_EVENT_STOP;
292
293       if (!clutter_actor_contains (actor, clutter_event_get_source (event)))
294         return CLUTTER_EVENT_PROPAGATE;
295
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);
299
300       if (priv->long_press_threshold < 0)
301         {
302           ClutterSettings *settings = clutter_settings_get_default ();
303
304           g_object_get (settings,
305                         "dnd-drag-threshold", &priv->drag_threshold,
306                         NULL);
307         }
308       else
309         priv->drag_threshold = priv->long_press_threshold;
310
311       if (priv->stage == NULL)
312         priv->stage = clutter_actor_get_stage (actor);
313
314       priv->capture_id = g_signal_connect_after (priv->stage, "captured-event",
315                                                  G_CALLBACK (on_captured_event),
316                                                  action);
317
318       click_action_set_pressed (action, TRUE);
319       click_action_set_held (action, TRUE);
320       click_action_query_long_press (action);
321       break;
322
323     case CLUTTER_ENTER:
324       click_action_set_pressed (action, priv->is_held);
325       break;
326
327     case CLUTTER_LEAVE:
328       click_action_set_pressed (action, priv->is_held);
329       click_action_cancel_long_press (action);
330       break;
331
332     default:
333       break;
334     }
335
336   return CLUTTER_EVENT_PROPAGATE;
337 }
338
339 static gboolean
340 on_captured_event (ClutterActor       *stage,
341                    ClutterEvent       *event,
342                    ClutterClickAction *action)
343 {
344   ClutterClickActionPrivate *priv = action->priv;
345   ClutterActor *actor;
346   ClutterModifierType modifier_state;
347
348   actor = clutter_actor_meta_get_actor (CLUTTER_ACTOR_META (action));
349
350   switch (clutter_event_type (event))
351     {
352     case CLUTTER_BUTTON_RELEASE:
353       if (!priv->is_held)
354         return CLUTTER_EVENT_STOP;
355
356       if (clutter_event_get_button (event) != priv->press_button ||
357           clutter_event_get_click_count (event) != 1)
358         return CLUTTER_EVENT_PROPAGATE;
359
360       click_action_set_held (action, FALSE);
361       click_action_cancel_long_press (action);
362
363       /* disconnect the capture */
364       if (priv->capture_id != 0)
365         {
366           g_signal_handler_disconnect (priv->stage, priv->capture_id);
367           priv->capture_id = 0;
368         }
369
370       if (priv->long_press_id != 0)
371         {
372           g_source_remove (priv->long_press_id);
373           priv->long_press_id = 0;
374         }
375
376       if (!clutter_actor_contains (actor, clutter_event_get_source (event)))
377         return CLUTTER_EVENT_PROPAGATE;
378
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);
387
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
391        * click */
392       if (modifier_state != priv->modifier_state)
393         priv->modifier_state = 0;
394
395       click_action_set_pressed (action, FALSE);
396       g_signal_emit (action, click_signals[CLICKED], 0, actor);
397       break;
398
399     case CLUTTER_MOTION:
400       {
401         gfloat motion_x, motion_y;
402         gfloat delta_x, delta_y;
403
404         if (!priv->is_held)
405           return CLUTTER_EVENT_PROPAGATE;
406
407         clutter_event_get_coords (event, &motion_x, &motion_y);
408
409         delta_x = ABS (motion_x - priv->press_x);
410         delta_y = ABS (motion_y - priv->press_y);
411
412         if (delta_x > priv->drag_threshold ||
413             delta_y > priv->drag_threshold)
414           click_action_cancel_long_press (action);
415       }
416       break;
417
418     default:
419       break;
420     }
421
422   return CLUTTER_EVENT_STOP;
423 }
424
425 static void
426 clutter_click_action_set_actor (ClutterActorMeta *meta,
427                                 ClutterActor     *actor)
428 {
429   ClutterClickAction *action = CLUTTER_CLICK_ACTION (meta);
430   ClutterClickActionPrivate *priv = action->priv;
431
432   if (priv->event_id != 0)
433     {
434       ClutterActor *old_actor = clutter_actor_meta_get_actor (meta);
435
436       if (old_actor != NULL)
437         g_signal_handler_disconnect (old_actor, priv->event_id);
438
439       priv->event_id = 0;
440     }
441
442   if (priv->capture_id != 0)
443     {
444       if (priv->stage != NULL)
445         g_signal_handler_disconnect (priv->stage, priv->capture_id);
446
447       priv->capture_id = 0;
448       priv->stage = NULL;
449     }
450
451   if (priv->long_press_id != 0)
452     {
453       g_source_remove (priv->long_press_id);
454       priv->long_press_id = 0;
455     }
456
457   click_action_set_pressed (action, FALSE);
458   click_action_set_held (action, FALSE);
459
460   if (actor != NULL)
461     priv->event_id = g_signal_connect (actor, "event",
462                                        G_CALLBACK (on_event),
463                                        action);
464
465   CLUTTER_ACTOR_META_CLASS (clutter_click_action_parent_class)->set_actor (meta, actor);
466 }
467
468 static void
469 clutter_click_action_set_property (GObject      *gobject,
470                                    guint         prop_id,
471                                    const GValue *value,
472                                    GParamSpec   *pspec)
473 {
474   ClutterClickActionPrivate *priv = CLUTTER_CLICK_ACTION (gobject)->priv;
475
476   switch (prop_id)
477     {
478     case PROP_LONG_PRESS_DURATION:
479       priv->long_press_duration = g_value_get_int (value);
480       break;
481
482     case PROP_LONG_PRESS_THRESHOLD:
483       priv->long_press_threshold = g_value_get_int (value);
484       break;
485
486     default:
487       G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
488       break;
489     }
490 }
491
492 static void
493 clutter_click_action_get_property (GObject    *gobject,
494                                    guint       prop_id,
495                                    GValue     *value,
496                                    GParamSpec *pspec)
497 {
498   ClutterClickActionPrivate *priv = CLUTTER_CLICK_ACTION (gobject)->priv;
499
500   switch (prop_id)
501     {
502     case PROP_HELD:
503       g_value_set_boolean (value, priv->is_held);
504       break;
505
506     case PROP_PRESSED:
507       g_value_set_boolean (value, priv->is_pressed);
508       break;
509
510     case PROP_LONG_PRESS_DURATION:
511       g_value_set_int (value, priv->long_press_duration);
512       break;
513
514     case PROP_LONG_PRESS_THRESHOLD:
515       g_value_set_int (value, priv->long_press_threshold);
516       break;
517
518     default:
519       G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
520       break;
521     }
522 }
523
524 static void
525 clutter_click_action_class_init (ClutterClickActionClass *klass)
526 {
527   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
528   ClutterActorMetaClass *meta_class = CLUTTER_ACTOR_META_CLASS (klass);
529
530   g_type_class_add_private (klass, sizeof (ClutterClickActionPrivate));
531
532   meta_class->set_actor = clutter_click_action_set_actor;
533
534   gobject_class->set_property = clutter_click_action_set_property;
535   gobject_class->get_property = clutter_click_action_get_property;
536
537   /**
538    * ClutterClickAction:pressed:
539    *
540    * Whether the clickable actor should be in "pressed" state
541    *
542    * Since: 1.4
543    */
544   obj_props[PROP_PRESSED] =
545     g_param_spec_boolean ("pressed",
546                           P_("Pressed"),
547                           P_("Whether the clickable should be in pressed state"),
548                           FALSE,
549                           CLUTTER_PARAM_READABLE);
550
551   /**
552    * ClutterClickAction:held:
553    *
554    * Whether the clickable actor has the pointer grabbed
555    *
556    * Since: 1.4
557    */
558   obj_props[PROP_HELD] =
559     g_param_spec_boolean ("held",
560                           P_("Held"),
561                           P_("Whether the clickable has a grab"),
562                           FALSE,
563                           CLUTTER_PARAM_READABLE);
564
565   /**
566    * ClutterClickAction:long-press-duration:
567    *
568    * The minimum duration of a press for it to be recognized as a long
569    * press gesture, in milliseconds.
570    *
571    * A value of -1 will make the #ClutterClickAction use the value of
572    * the #ClutterSettings:long-press-duration property.
573    *
574    * Since: 1.8
575    */
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"),
580                       -1, G_MAXINT,
581                       -1,
582                       CLUTTER_PARAM_READWRITE);
583
584   /**
585    * ClutterClickAction:long-press-threshold:
586    *
587    * The maximum allowed distance that can be covered (on both axes) before
588    * a long press gesture is cancelled, in pixels.
589    *
590    * A value of -1 will make the #ClutterClickAction use the value of
591    * the #ClutterSettings:dnd-drag-threshold property.
592    *
593    * Since: 1.8
594    */
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"),
599                       -1, G_MAXINT,
600                       -1,
601                       CLUTTER_PARAM_READWRITE);
602
603   g_object_class_install_properties (gobject_class,
604                                      PROP_LAST,
605                                      obj_props);
606
607   /**
608    * ClutterClickAction::clicked:
609    * @action: the #ClutterClickAction that emitted the signal
610    * @actor: the #ClutterActor attached to the @action
611    *
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
615    *
616    * Since: 1.4
617    */
618   click_signals[CLICKED] =
619     g_signal_new (I_("clicked"),
620                   G_TYPE_FROM_CLASS (klass),
621                   G_SIGNAL_RUN_LAST,
622                   G_STRUCT_OFFSET (ClutterClickActionClass, clicked),
623                   NULL, NULL,
624                   _clutter_marshal_VOID__OBJECT,
625                   G_TYPE_NONE, 1,
626                   CLUTTER_TYPE_ACTOR);
627
628   /**
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
633    *
634    * The ::long-press signal is emitted during the long press gesture
635    * handling.
636    *
637    * This signal can be emitted multiple times with different states.
638    *
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.
646    *
647    * It is possible to forcibly cancel a long press detection using
648    * clutter_click_action_release().
649    *
650    * Return value: Only the %CLUTTER_LONG_PRESS_QUERY state uses the
651    *   returned value of the handler; other states will ignore it
652    *
653    * Since: 1.8
654    */
655   click_signals[LONG_PRESS] =
656     g_signal_new (I_("long-press"),
657                   G_TYPE_FROM_CLASS (klass),
658                   G_SIGNAL_RUN_LAST,
659                   G_STRUCT_OFFSET (ClutterClickActionClass, long_press),
660                   NULL, NULL,
661                   _clutter_marshal_BOOLEAN__OBJECT_ENUM,
662                   G_TYPE_BOOLEAN, 2,
663                   CLUTTER_TYPE_ACTOR,
664                   CLUTTER_TYPE_LONG_PRESS_STATE);
665 }
666
667 static void
668 clutter_click_action_init (ClutterClickAction *self)
669 {
670   self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, CLUTTER_TYPE_CLICK_ACTION,
671                                             ClutterClickActionPrivate);
672
673   self->priv->long_press_threshold = -1;
674   self->priv->long_press_duration = -1;
675 }
676
677 /**
678  * clutter_click_action_new:
679  *
680  * Creates a new #ClutterClickAction instance
681  *
682  * Return value: the newly created #ClutterClickAction
683  *
684  * Since: 1.4
685  */
686 ClutterAction *
687 clutter_click_action_new (void)
688 {
689   return g_object_new (CLUTTER_TYPE_CLICK_ACTION, NULL);
690 }
691
692 /**
693  * clutter_click_action_release:
694  * @action: a #ClutterClickAction
695  *
696  * Emulates a release of the pointer button, which ungrabs the pointer
697  * and unsets the #ClutterClickAction:pressed state.
698  *
699  * This function will also cancel the long press gesture if one was
700  * initiated.
701  *
702  * This function is useful to break a grab, for instance after a certain
703  * amount of time has passed.
704  *
705  * Since: 1.4
706  */
707 void
708 clutter_click_action_release (ClutterClickAction *action)
709 {
710   ClutterClickActionPrivate *priv;
711
712   g_return_if_fail (CLUTTER_IS_CLICK_ACTION (action));
713
714   priv = action->priv;
715
716   if (!priv->is_held)
717     return;
718
719   /* disconnect the capture */
720   if (priv->capture_id != 0)
721     {
722       g_signal_handler_disconnect (priv->stage, priv->capture_id);
723       priv->capture_id = 0;
724     }
725
726   click_action_cancel_long_press (action);
727   click_action_set_held (action, FALSE);
728   click_action_set_pressed (action, FALSE);
729 }
730
731 /**
732  * clutter_click_action_get_button:
733  * @action: a #ClutterClickAction
734  *
735  * Retrieves the button that was pressed.
736  *
737  * Return value: the button value
738  *
739  * Since: 1.4
740  */
741 guint
742 clutter_click_action_get_button (ClutterClickAction *action)
743 {
744   g_return_val_if_fail (CLUTTER_IS_CLICK_ACTION (action), 0);
745
746   return action->priv->press_button;
747 }
748
749 /**
750  * clutter_click_action_get_state:
751  * @action: a #ClutterClickAction
752  *
753  * Retrieves the modifier state of the click action.
754  *
755  * Return value: the modifier state parameter, or 0
756  *
757  * Since: 1.6
758  */
759 ClutterModifierType
760 clutter_click_action_get_state (ClutterClickAction *action)
761 {
762   g_return_val_if_fail (CLUTTER_IS_CLICK_ACTION (action), 0);
763
764   return action->priv->modifier_state;
765 }
766
767 /**
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
772  *
773  * Retrieves the screen coordinates of the button press.
774  *
775  * Since: 1.8
776  */
777 void
778 clutter_click_action_get_coords (ClutterClickAction *action,
779                                  gfloat             *press_x,
780                                  gfloat             *press_y)
781 {
782   g_return_if_fail (CLUTTER_IS_ACTION (action));
783
784   if (press_x != NULL)
785     *press_x = action->priv->press_x;
786
787   if (press_y != NULL)
788     *press_y = action->priv->press_y;
789 }