update to 1.10.4
[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="../../../../tests/interactive/test-drop.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
105   LAST_SIGNAL
106 };
107
108 static guint drop_signals[LAST_SIGNAL] = { 0, };
109
110 G_DEFINE_TYPE (ClutterDropAction, clutter_drop_action, CLUTTER_TYPE_ACTION)
111
112 static void
113 drop_target_free (gpointer _data)
114 {
115   DropTarget *data = _data;
116
117   g_signal_handler_disconnect (data->stage, data->capture_id);
118   g_hash_table_destroy (data->actions);
119   g_free (data);
120 }
121
122 static gboolean
123 on_stage_capture (ClutterStage *stage,
124                   ClutterEvent *event,
125                   gpointer      user_data)
126 {
127   DropTarget *data = user_data;
128   gfloat event_x, event_y;
129   ClutterInputDevice *device;
130   ClutterActor *actor, *drag_actor;
131   ClutterDropAction *drop_action;
132   gboolean was_reactive;
133
134   if (!(clutter_event_type (event) == CLUTTER_MOTION ||
135         clutter_event_type (event) == CLUTTER_BUTTON_RELEASE))
136     return CLUTTER_EVENT_PROPAGATE;
137
138   if (!(clutter_event_get_state (event) & CLUTTER_BUTTON1_MASK))
139     return CLUTTER_EVENT_PROPAGATE;
140
141   clutter_event_get_coords (event, &event_x, &event_y);
142   device = clutter_event_get_device (event);
143
144   drag_actor = _clutter_stage_get_drag_actor (stage, device);
145   if (drag_actor == NULL)
146     return CLUTTER_EVENT_PROPAGATE;
147
148   /* get the actor under the cursor, excluding the dragged actor; we
149    * use reactivity because it won't cause any scene invalidation
150    */
151   was_reactive = clutter_actor_get_reactive (drag_actor);
152   clutter_actor_set_reactive (drag_actor, FALSE);
153
154   actor = clutter_stage_get_actor_at_pos (stage, CLUTTER_PICK_REACTIVE,
155                                           event_x,
156                                           event_y);
157   if (actor == NULL || actor == CLUTTER_ACTOR (stage))
158     {
159       if (data->last_action != NULL)
160         {
161           ClutterActorMeta *meta = CLUTTER_ACTOR_META (data->last_action);
162
163           g_signal_emit (data->last_action, drop_signals[OVER_OUT], 0,
164                          clutter_actor_meta_get_actor (meta));
165
166           data->last_action = NULL;
167         }
168
169       goto out;
170     }
171
172   drop_action = g_hash_table_lookup (data->actions, actor);
173
174   if (drop_action == NULL)
175     {
176       if (data->last_action != NULL)
177         {
178           ClutterActorMeta *meta = CLUTTER_ACTOR_META (data->last_action);
179
180           g_signal_emit (data->last_action, drop_signals[OVER_OUT], 0,
181                          clutter_actor_meta_get_actor (meta));
182
183           data->last_action = NULL;
184         }
185
186       goto out;
187     }
188   else
189     {
190       if (data->last_action != drop_action)
191         {
192           ClutterActorMeta *meta;
193
194           if (data->last_action != NULL)
195             {
196               meta = CLUTTER_ACTOR_META (data->last_action);
197
198               g_signal_emit (data->last_action, drop_signals[OVER_OUT], 0,
199                              clutter_actor_meta_get_actor (meta));
200             }
201
202           meta = CLUTTER_ACTOR_META (drop_action);
203
204           g_signal_emit (drop_action, drop_signals[OVER_IN], 0,
205                          clutter_actor_meta_get_actor (meta));
206         }
207
208       data->last_action = drop_action;
209     }
210
211 out:
212   if (clutter_event_type (event) == CLUTTER_BUTTON_RELEASE)
213     {
214       if (data->last_action != NULL)
215         {
216           ClutterActorMeta *meta = CLUTTER_ACTOR_META (data->last_action);
217           gboolean can_drop = FALSE;
218
219           g_signal_emit (data->last_action, drop_signals[CAN_DROP], 0,
220                          clutter_actor_meta_get_actor (meta),
221                          event_x, event_y,
222                          &can_drop);
223
224           if (can_drop)
225             {
226               g_signal_emit (data->last_action, drop_signals[DROP], 0,
227                              clutter_actor_meta_get_actor (meta),
228                              event_x, event_y);
229             }
230         }
231
232       data->last_action = NULL;
233     }
234
235   if (drag_actor != NULL)
236     clutter_actor_set_reactive (drag_actor, was_reactive);
237
238   return CLUTTER_EVENT_PROPAGATE;
239 }
240
241 static void
242 drop_action_register (ClutterDropAction *self)
243 {
244   ClutterDropActionPrivate *priv = self->priv;
245   DropTarget *data;
246
247   g_assert (priv->stage != NULL);
248
249   data = g_object_get_data (G_OBJECT (priv->stage), "__clutter_drop_targets");
250   if (data == NULL)
251     {
252       data = g_new0 (DropTarget, 1);
253
254       data->stage = priv->stage;
255       data->actions = g_hash_table_new (NULL, NULL);
256       data->capture_id = g_signal_connect (priv->stage, "captured-event",
257                                            G_CALLBACK (on_stage_capture),
258                                            data);
259       g_object_set_data_full (G_OBJECT (priv->stage), "__clutter_drop_targets",
260                               data,
261                               drop_target_free);
262     }
263
264   g_hash_table_replace (data->actions, priv->actor, self);
265 }
266
267 static void
268 drop_action_unregister (ClutterDropAction *self)
269 {
270   ClutterDropActionPrivate *priv = self->priv;
271   DropTarget *data;
272
273   data = g_object_get_data (G_OBJECT (priv->stage), "__clutter_drop_targets");
274   if (data == NULL)
275     return;
276
277   g_hash_table_remove (data->actions, priv->actor);
278   if (g_hash_table_size (data->actions) == 0)
279     g_object_set_data (G_OBJECT (data->stage), "__clutter_drop_targets", NULL);
280 }
281
282 static void
283 on_actor_mapped (ClutterActor      *actor,
284                  GParamSpec        *pspec,
285                  ClutterDropAction *self)
286 {
287   if (CLUTTER_ACTOR_IS_MAPPED (actor))
288     {
289       if (self->priv->stage == NULL)
290         self->priv->stage = clutter_actor_get_stage (actor);
291
292       drop_action_register (self);
293     }
294   else
295     drop_action_unregister (self);
296 }
297
298 static void
299 clutter_drop_action_set_actor (ClutterActorMeta *meta,
300                                ClutterActor     *actor)
301 {
302   ClutterDropActionPrivate *priv = CLUTTER_DROP_ACTION (meta)->priv;
303
304   if (priv->actor != NULL)
305     {
306       drop_action_unregister (CLUTTER_DROP_ACTION (meta));
307
308       if (priv->mapped_id != 0)
309         g_signal_handler_disconnect (priv->actor, priv->mapped_id);
310
311       priv->stage = NULL;
312       priv->actor = NULL;
313       priv->mapped_id = 0;
314     }
315
316   priv->actor = actor;
317
318   if (priv->actor != NULL)
319     {
320       priv->stage = clutter_actor_get_stage (actor);
321       priv->mapped_id = g_signal_connect (actor, "notify::mapped",
322                                           G_CALLBACK (on_actor_mapped),
323                                           meta);
324
325       if (priv->stage != NULL)
326         drop_action_register (CLUTTER_DROP_ACTION (meta));
327     }
328
329   CLUTTER_ACTOR_META_CLASS (clutter_drop_action_parent_class)->set_actor (meta, actor);
330 }
331
332 static gboolean
333 signal_accumulator (GSignalInvocationHint *ihint,
334                     GValue                *return_accu,
335                     const GValue          *handler_return,
336                     gpointer               user_data)
337 {
338   gboolean continue_emission;
339
340   continue_emission = g_value_get_boolean (handler_return);
341   g_value_set_boolean (return_accu, continue_emission);
342
343   return continue_emission;
344 }
345
346 static gboolean
347 clutter_drop_action_real_can_drop (ClutterDropAction *action,
348                                    ClutterActor      *actor,
349                                    gfloat             event_x,
350                                    gfloat             event_y)
351 {
352   return TRUE;
353 }
354
355 static void
356 clutter_drop_action_class_init (ClutterDropActionClass *klass)
357 {
358   ClutterActorMetaClass *meta_class = CLUTTER_ACTOR_META_CLASS (klass);
359
360   g_type_class_add_private (klass, sizeof (ClutterDropActionPrivate));
361
362   meta_class->set_actor = clutter_drop_action_set_actor;
363
364   klass->can_drop = clutter_drop_action_real_can_drop;
365
366   /**
367    * ClutterDropAction::can-drop:
368    * @action: the #ClutterDropAction that emitted the signal
369    * @actor: the #ClutterActor attached to the @action
370    * @event_x: the X coordinate (in stage space) of the drop event
371    * @event_y: the Y coordinate (in stage space) of the drop event
372    *
373    * The ::can-drop signal is emitted when the dragged actor is dropped
374    * on @actor. The return value of the ::can-drop signal will determine
375    * whether or not the #ClutterDropAction::drop signal is going to be
376    * emitted on @action.
377    *
378    * The default implementation of #ClutterDropAction returns %TRUE for
379    * this signal.
380    *
381    * Return value: %TRUE if the drop is accepted, and %FALSE otherwise
382    *
383    * Since: 1.8
384    */
385   drop_signals[CAN_DROP] =
386     g_signal_new (I_("can-drop"),
387                   G_TYPE_FROM_CLASS (klass),
388                   G_SIGNAL_RUN_LAST,
389                   G_STRUCT_OFFSET (ClutterDropActionClass, can_drop),
390                   signal_accumulator, NULL,
391                   _clutter_marshal_BOOLEAN__OBJECT_FLOAT_FLOAT,
392                   G_TYPE_BOOLEAN, 3,
393                   CLUTTER_TYPE_ACTOR,
394                   G_TYPE_FLOAT,
395                   G_TYPE_FLOAT);
396
397   /**
398    * ClutterDropAction::over-in:
399    * @action: the #ClutterDropAction that emitted the signal
400    * @actor: the #ClutterActor attached to the @action
401    *
402    * The ::over-in signal is emitted when the dragged actor crosses
403    * into @actor.
404    *
405    * Since: 1.8
406    */
407   drop_signals[OVER_IN] =
408     g_signal_new (I_("over-in"),
409                   G_TYPE_FROM_CLASS (klass),
410                   G_SIGNAL_RUN_LAST,
411                   G_STRUCT_OFFSET (ClutterDropActionClass, over_in),
412                   NULL, NULL,
413                   _clutter_marshal_VOID__OBJECT,
414                   G_TYPE_NONE, 1,
415                   CLUTTER_TYPE_ACTOR);
416
417   /**
418    * ClutterDropAction::over-out:
419    * @action: the #ClutterDropAction that emitted the signal
420    * @actor: the #ClutterActor attached to the @action
421    *
422    * The ::over-out signal is emitted when the dragged actor crosses
423    * outside @actor.
424    *
425    * Since: 1.8
426    */
427   drop_signals[OVER_OUT] =
428     g_signal_new (I_("over-out"),
429                   G_TYPE_FROM_CLASS (klass),
430                   G_SIGNAL_RUN_LAST,
431                   G_STRUCT_OFFSET (ClutterDropActionClass, over_out),
432                   NULL, NULL,
433                   _clutter_marshal_VOID__OBJECT,
434                   G_TYPE_NONE, 1,
435                   CLUTTER_TYPE_ACTOR);
436
437   /**
438    * ClutterDropAction::drop:
439    * @action: the #ClutterDropAction that emitted the signal
440    * @actor: the #ClutterActor attached to the @action
441    * @event_x: the X coordinate (in stage space) of the drop event
442    * @event_y: the Y coordinate (in stage space) of the drop event
443    *
444    * The ::drop signal is emitted when the dragged actor is dropped
445    * on @actor. This signal is only emitted if at least an handler of
446    * #ClutterDropAction::can-drop returns %TRUE.
447    *
448    * Since: 1.8
449    */
450   drop_signals[DROP] =
451     g_signal_new (I_("drop"),
452                   G_TYPE_FROM_CLASS (klass),
453                   G_SIGNAL_RUN_LAST,
454                   G_STRUCT_OFFSET (ClutterDropActionClass, drop),
455                   NULL, NULL,
456                   _clutter_marshal_VOID__OBJECT_FLOAT_FLOAT,
457                   G_TYPE_NONE, 3,
458                   CLUTTER_TYPE_ACTOR,
459                   G_TYPE_FLOAT,
460                   G_TYPE_FLOAT);
461 }
462
463 static void
464 clutter_drop_action_init (ClutterDropAction *self)
465 {
466   self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, CLUTTER_TYPE_DROP_ACTION,
467                                             ClutterDropActionPrivate);
468 }
469
470 /**
471  * clutter_drop_action_new:
472  *
473  * Creates a new #ClutterDropAction.
474  *
475  * Use clutter_actor_add_action() to add the action to a #ClutterActor.
476  *
477  * Return value: the newly created #ClutterDropAction
478  *
479  * Since: 1.8
480  */
481 ClutterAction *
482 clutter_drop_action_new (void)
483 {
484   return g_object_new (CLUTTER_TYPE_DROP_ACTION, NULL);
485 }