Release Clutter 1.11.4 (snapshot)
[profile/ivi/clutter.git] / clutter / clutter-drop-action.c
1 /*
2  * Clutter.
3  *
4  * An OpenGL based 'interactive canvas' library.
5  *
6  * Copyright © 2011  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-drop-action
27  * @Title: ClutterDropAction
28  * @short_description: An action for drop targets
29  *
30  * #ClutterDropAction is a #ClutterAction that allows a #ClutterActor
31  * implementation to control what happens when an actor dragged using
32  * a #ClutterDragAction crosses the target area or when a dragged actor
33  * is released (or "dropped") on the target area.
34  *
35  * A trivial use of #ClutterDropAction consists in connecting to the
36  * #ClutterDropAction::drop signal and handling the drop from there,
37  * for instance:
38  *
39  * |[
40  *   ClutterAction *action = clutter_drop_action ();
41  *
42  *   g_signal_connect (action, "drop", G_CALLBACK (on_drop), NULL);
43  *   clutter_actor_add_action (an_actor, action);
44  * ]|
45  *
46  * The #ClutterDropAction::can-drop can be used to control whether the
47  * #ClutterDropAction::drop signal is going to be emitted; returning %FALSE
48  * from a handler connected to the #ClutterDropAction::can-drop signal will
49  * cause the #ClutterDropAction::drop signal to be skipped when the input
50  * device button is released.
51  *
52  * <example id="drop-action-example">
53  *   <title>Drop targets</title>
54  *   <programlisting>
55  * <xi:include xmlns:xi="http://www.w3.org/2001/XInclude" parse="text" href="../../../../examples/drop-action.c">
56  *   <xi:fallback>FIXME: MISSING XINCLUDE CONTENT</xi:fallback>
57  * </xi:include>
58  *   </programlisting>
59  * </example>
60  *
61  * It's important to note that #ClutterDropAction will only work with
62  * actors dragged using #ClutterDragAction.
63  *
64  * #ClutterDropAction is available since Clutter 1.8
65  */
66
67 #ifdef HAVE_CONFIG_H
68 #include "config.h"
69 #endif
70
71 #include "clutter-drop-action.h"
72
73 #include "clutter-actor-meta-private.h"
74 #include "clutter-actor-private.h"
75 #include "clutter-drag-action.h"
76 #include "clutter-main.h"
77 #include "clutter-marshal.h"
78 #include "clutter-stage-private.h"
79
80 struct _ClutterDropActionPrivate
81 {
82   ClutterActor *actor;
83   ClutterActor *stage;
84
85   gulong mapped_id;
86 };
87
88 typedef struct _DropTarget {
89   ClutterActor *stage;
90
91   gulong capture_id;
92
93   GHashTable *actions;
94
95   ClutterDropAction *last_action;
96 } DropTarget;
97
98 enum
99 {
100   CAN_DROP,
101   OVER_IN,
102   OVER_OUT,
103   DROP,
104   DROP_CANCEL,
105
106   LAST_SIGNAL
107 };
108
109 static guint drop_signals[LAST_SIGNAL] = { 0, };
110
111 G_DEFINE_TYPE (ClutterDropAction, clutter_drop_action, CLUTTER_TYPE_ACTION)
112
113 static void
114 drop_target_free (gpointer _data)
115 {
116   DropTarget *data = _data;
117
118   g_signal_handler_disconnect (data->stage, data->capture_id);
119   g_hash_table_destroy (data->actions);
120   g_free (data);
121 }
122
123 static gboolean
124 on_stage_capture (ClutterStage *stage,
125                   ClutterEvent *event,
126                   gpointer      user_data)
127 {
128   DropTarget *data = user_data;
129   gfloat event_x, event_y;
130   ClutterInputDevice *device;
131   ClutterActor *actor, *drag_actor;
132   ClutterDropAction *drop_action;
133   gboolean was_reactive;
134
135   if (!(clutter_event_type (event) == CLUTTER_MOTION ||
136         clutter_event_type (event) == CLUTTER_BUTTON_RELEASE))
137     return CLUTTER_EVENT_PROPAGATE;
138
139   if (!(clutter_event_get_state (event) & CLUTTER_BUTTON1_MASK))
140     return CLUTTER_EVENT_PROPAGATE;
141
142   clutter_event_get_coords (event, &event_x, &event_y);
143   device = clutter_event_get_device (event);
144
145   drag_actor = _clutter_stage_get_drag_actor (stage, device);
146   if (drag_actor == NULL)
147     return CLUTTER_EVENT_PROPAGATE;
148
149   /* get the actor under the cursor, excluding the dragged actor; we
150    * use reactivity because it won't cause any scene invalidation
151    */
152   was_reactive = clutter_actor_get_reactive (drag_actor);
153   clutter_actor_set_reactive (drag_actor, FALSE);
154
155   actor = clutter_stage_get_actor_at_pos (stage, CLUTTER_PICK_REACTIVE,
156                                           event_x,
157                                           event_y);
158   if (actor == NULL || actor == CLUTTER_ACTOR (stage))
159     {
160       if (data->last_action != NULL)
161         {
162           ClutterActorMeta *meta = CLUTTER_ACTOR_META (data->last_action);
163
164           g_signal_emit (data->last_action, drop_signals[OVER_OUT], 0,
165                          clutter_actor_meta_get_actor (meta));
166
167           data->last_action = NULL;
168         }
169
170       goto out;
171     }
172
173   drop_action = g_hash_table_lookup (data->actions, actor);
174
175   if (drop_action == NULL)
176     {
177       if (data->last_action != NULL)
178         {
179           ClutterActorMeta *meta = CLUTTER_ACTOR_META (data->last_action);
180
181           g_signal_emit (data->last_action, drop_signals[OVER_OUT], 0,
182                          clutter_actor_meta_get_actor (meta));
183
184           data->last_action = NULL;
185         }
186
187       goto out;
188     }
189   else
190     {
191       if (data->last_action != drop_action)
192         {
193           ClutterActorMeta *meta;
194
195           if (data->last_action != NULL)
196             {
197               meta = CLUTTER_ACTOR_META (data->last_action);
198
199               g_signal_emit (data->last_action, drop_signals[OVER_OUT], 0,
200                              clutter_actor_meta_get_actor (meta));
201             }
202
203           meta = CLUTTER_ACTOR_META (drop_action);
204
205           g_signal_emit (drop_action, drop_signals[OVER_IN], 0,
206                          clutter_actor_meta_get_actor (meta));
207         }
208
209       data->last_action = drop_action;
210     }
211
212 out:
213   if (clutter_event_type (event) == CLUTTER_BUTTON_RELEASE)
214     {
215       if (data->last_action != NULL)
216         {
217           ClutterActorMeta *meta = CLUTTER_ACTOR_META (data->last_action);
218           gboolean can_drop = FALSE;
219
220           g_signal_emit (data->last_action, drop_signals[CAN_DROP], 0,
221                          clutter_actor_meta_get_actor (meta),
222                          event_x, event_y,
223                          &can_drop);
224
225           if (can_drop)
226             {
227               g_signal_emit (data->last_action, drop_signals[DROP], 0,
228                              clutter_actor_meta_get_actor (meta),
229                              event_x, event_y);
230             }
231           else
232             {
233               g_signal_emit (data->last_action, drop_signals[DROP_CANCEL], 0,
234                              clutter_actor_meta_get_actor (meta),
235                              event_x, event_y);
236             }
237
238         }
239
240       data->last_action = NULL;
241     }
242
243   if (drag_actor != NULL)
244     clutter_actor_set_reactive (drag_actor, was_reactive);
245
246   return CLUTTER_EVENT_PROPAGATE;
247 }
248
249 static void
250 drop_action_register (ClutterDropAction *self)
251 {
252   ClutterDropActionPrivate *priv = self->priv;
253   DropTarget *data;
254
255   g_assert (priv->stage != NULL);
256
257   data = g_object_get_data (G_OBJECT (priv->stage), "__clutter_drop_targets");
258   if (data == NULL)
259     {
260       data = g_new0 (DropTarget, 1);
261
262       data->stage = priv->stage;
263       data->actions = g_hash_table_new (NULL, NULL);
264       data->capture_id = g_signal_connect (priv->stage, "captured-event",
265                                            G_CALLBACK (on_stage_capture),
266                                            data);
267       g_object_set_data_full (G_OBJECT (priv->stage), "__clutter_drop_targets",
268                               data,
269                               drop_target_free);
270     }
271
272   g_hash_table_replace (data->actions, priv->actor, self);
273 }
274
275 static void
276 drop_action_unregister (ClutterDropAction *self)
277 {
278   ClutterDropActionPrivate *priv = self->priv;
279   DropTarget *data = NULL;
280
281   if (priv->stage != NULL)
282     data = g_object_get_data (G_OBJECT (priv->stage), "__clutter_drop_targets");
283
284   if (data == NULL)
285     return;
286
287   g_hash_table_remove (data->actions, priv->actor);
288   if (g_hash_table_size (data->actions) == 0)
289     g_object_set_data (G_OBJECT (data->stage), "__clutter_drop_targets", NULL);
290 }
291
292 static void
293 on_actor_mapped (ClutterActor      *actor,
294                  GParamSpec        *pspec,
295                  ClutterDropAction *self)
296 {
297   if (CLUTTER_ACTOR_IS_MAPPED (actor))
298     {
299       if (self->priv->stage == NULL)
300         self->priv->stage = clutter_actor_get_stage (actor);
301
302       drop_action_register (self);
303     }
304   else
305     drop_action_unregister (self);
306 }
307
308 static void
309 clutter_drop_action_set_actor (ClutterActorMeta *meta,
310                                ClutterActor     *actor)
311 {
312   ClutterDropActionPrivate *priv = CLUTTER_DROP_ACTION (meta)->priv;
313
314   if (priv->actor != NULL)
315     {
316       drop_action_unregister (CLUTTER_DROP_ACTION (meta));
317
318       if (priv->mapped_id != 0)
319         g_signal_handler_disconnect (priv->actor, priv->mapped_id);
320
321       priv->stage = NULL;
322       priv->actor = NULL;
323       priv->mapped_id = 0;
324     }
325
326   priv->actor = actor;
327
328   if (priv->actor != NULL)
329     {
330       priv->stage = clutter_actor_get_stage (actor);
331       priv->mapped_id = g_signal_connect (actor, "notify::mapped",
332                                           G_CALLBACK (on_actor_mapped),
333                                           meta);
334
335       if (priv->stage != NULL)
336         drop_action_register (CLUTTER_DROP_ACTION (meta));
337     }
338
339   CLUTTER_ACTOR_META_CLASS (clutter_drop_action_parent_class)->set_actor (meta, actor);
340 }
341
342 static gboolean
343 signal_accumulator (GSignalInvocationHint *ihint,
344                     GValue                *return_accu,
345                     const GValue          *handler_return,
346                     gpointer               user_data)
347 {
348   gboolean continue_emission;
349
350   continue_emission = g_value_get_boolean (handler_return);
351   g_value_set_boolean (return_accu, continue_emission);
352
353   return continue_emission;
354 }
355
356 static gboolean
357 clutter_drop_action_real_can_drop (ClutterDropAction *action,
358                                    ClutterActor      *actor,
359                                    gfloat             event_x,
360                                    gfloat             event_y)
361 {
362   return TRUE;
363 }
364
365 static void
366 clutter_drop_action_class_init (ClutterDropActionClass *klass)
367 {
368   ClutterActorMetaClass *meta_class = CLUTTER_ACTOR_META_CLASS (klass);
369
370   g_type_class_add_private (klass, sizeof (ClutterDropActionPrivate));
371
372   meta_class->set_actor = clutter_drop_action_set_actor;
373
374   klass->can_drop = clutter_drop_action_real_can_drop;
375
376   /**
377    * ClutterDropAction::can-drop:
378    * @action: the #ClutterDropAction that emitted the signal
379    * @actor: the #ClutterActor attached to the @action
380    * @event_x: the X coordinate (in stage space) of the drop event
381    * @event_y: the Y coordinate (in stage space) of the drop event
382    *
383    * The ::can-drop signal is emitted when the dragged actor is dropped
384    * on @actor. The return value of the ::can-drop signal will determine
385    * whether or not the #ClutterDropAction::drop signal is going to be
386    * emitted on @action.
387    *
388    * The default implementation of #ClutterDropAction returns %TRUE for
389    * this signal.
390    *
391    * Return value: %TRUE if the drop is accepted, and %FALSE otherwise
392    *
393    * Since: 1.8
394    */
395   drop_signals[CAN_DROP] =
396     g_signal_new (I_("can-drop"),
397                   G_TYPE_FROM_CLASS (klass),
398                   G_SIGNAL_RUN_LAST,
399                   G_STRUCT_OFFSET (ClutterDropActionClass, can_drop),
400                   signal_accumulator, NULL,
401                   _clutter_marshal_BOOLEAN__OBJECT_FLOAT_FLOAT,
402                   G_TYPE_BOOLEAN, 3,
403                   CLUTTER_TYPE_ACTOR,
404                   G_TYPE_FLOAT,
405                   G_TYPE_FLOAT);
406
407   /**
408    * ClutterDropAction::over-in:
409    * @action: the #ClutterDropAction that emitted the signal
410    * @actor: the #ClutterActor attached to the @action
411    *
412    * The ::over-in signal is emitted when the dragged actor crosses
413    * into @actor.
414    *
415    * Since: 1.8
416    */
417   drop_signals[OVER_IN] =
418     g_signal_new (I_("over-in"),
419                   G_TYPE_FROM_CLASS (klass),
420                   G_SIGNAL_RUN_LAST,
421                   G_STRUCT_OFFSET (ClutterDropActionClass, over_in),
422                   NULL, NULL,
423                   _clutter_marshal_VOID__OBJECT,
424                   G_TYPE_NONE, 1,
425                   CLUTTER_TYPE_ACTOR);
426
427   /**
428    * ClutterDropAction::over-out:
429    * @action: the #ClutterDropAction that emitted the signal
430    * @actor: the #ClutterActor attached to the @action
431    *
432    * The ::over-out signal is emitted when the dragged actor crosses
433    * outside @actor.
434    *
435    * Since: 1.8
436    */
437   drop_signals[OVER_OUT] =
438     g_signal_new (I_("over-out"),
439                   G_TYPE_FROM_CLASS (klass),
440                   G_SIGNAL_RUN_LAST,
441                   G_STRUCT_OFFSET (ClutterDropActionClass, over_out),
442                   NULL, NULL,
443                   _clutter_marshal_VOID__OBJECT,
444                   G_TYPE_NONE, 1,
445                   CLUTTER_TYPE_ACTOR);
446
447   /**
448    * ClutterDropAction::drop:
449    * @action: the #ClutterDropAction that emitted the signal
450    * @actor: the #ClutterActor attached to the @action
451    * @event_x: the X coordinate (in stage space) of the drop event
452    * @event_y: the Y coordinate (in stage space) of the drop event
453    *
454    * The ::drop signal is emitted when the dragged actor is dropped
455    * on @actor. This signal is only emitted if at least an handler of
456    * #ClutterDropAction::can-drop returns %TRUE.
457    *
458    * Since: 1.8
459    */
460   drop_signals[DROP] =
461     g_signal_new (I_("drop"),
462                   G_TYPE_FROM_CLASS (klass),
463                   G_SIGNAL_RUN_LAST,
464                   G_STRUCT_OFFSET (ClutterDropActionClass, drop),
465                   NULL, NULL,
466                   _clutter_marshal_VOID__OBJECT_FLOAT_FLOAT,
467                   G_TYPE_NONE, 3,
468                   CLUTTER_TYPE_ACTOR,
469                   G_TYPE_FLOAT,
470                   G_TYPE_FLOAT);
471
472
473   /**
474    * ClutterDropAction::drop-cancel:
475    * @action: the #ClutterDropAction that emitted the signal
476    * @actor: the #ClutterActor attached to the @action
477    * @event_x: the X coordinate (in stage space) of the drop event
478    * @event_y: the Y coordinate (in stage space) of the drop event
479    *
480    * The ::drop-cancel signal is emitted when the drop is refused
481    * by an emission of the #ClutterDropAction::can-drop signal.
482    *
483    * After the ::drop-cancel signal is fired the active drag is
484    * terminated.
485    *
486    * Since: 1.12
487    */
488   drop_signals[DROP_CANCEL] =
489     g_signal_new (I_("drop-cancel"),
490                   G_TYPE_FROM_CLASS (klass),
491                   G_SIGNAL_RUN_LAST,
492                   G_STRUCT_OFFSET (ClutterDropActionClass, drop),
493                   NULL, NULL,
494                   _clutter_marshal_VOID__OBJECT_FLOAT_FLOAT,
495                   G_TYPE_NONE, 3,
496                   CLUTTER_TYPE_ACTOR,
497                   G_TYPE_FLOAT,
498                   G_TYPE_FLOAT);
499 }
500
501 static void
502 clutter_drop_action_init (ClutterDropAction *self)
503 {
504   self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, CLUTTER_TYPE_DROP_ACTION,
505                                             ClutterDropActionPrivate);
506 }
507
508 /**
509  * clutter_drop_action_new:
510  *
511  * Creates a new #ClutterDropAction.
512  *
513  * Use clutter_actor_add_action() to add the action to a #ClutterActor.
514  *
515  * Return value: the newly created #ClutterDropAction
516  *
517  * Since: 1.8
518  */
519 ClutterAction *
520 clutter_drop_action_new (void)
521 {
522   return g_object_new (CLUTTER_TYPE_DROP_ACTION, NULL);
523 }