1 /* GTK - The GIMP Toolkit
2  * Copyright (C) 1995-1999 Peter Mattis, Spencer Kimball and Josh MacDonald
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library. If not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 /*
19  * Modified by the GTK+ Team and others 1997-2000.  See the AUTHORS
20  * file for a list of people on the GTK+ Team.  See the ChangeLog
21  * files for a list of changes.  These files are distributed with
22  * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
23  */
24 
25 #include "config.h"
26 
27 #include "gtkdragdest.h"
28 
29 #include "gtkdnd.h"
30 #include "gtkdndprivate.h"
31 #include "gtkselectionprivate.h"
32 #include "gtkintl.h"
33 
34 
35 static void
gtk_drag_dest_realized(GtkWidget * widget)36 gtk_drag_dest_realized (GtkWidget *widget)
37 {
38   GtkWidget *toplevel = gtk_widget_get_toplevel (widget);
39 
40   if (gtk_widget_is_toplevel (toplevel))
41     gdk_window_register_dnd (gtk_widget_get_window (toplevel));
42 }
43 
44 static void
gtk_drag_dest_hierarchy_changed(GtkWidget * widget,GtkWidget * previous_toplevel)45 gtk_drag_dest_hierarchy_changed (GtkWidget *widget,
46                                  GtkWidget *previous_toplevel)
47 {
48   GtkWidget *toplevel = gtk_widget_get_toplevel (widget);
49 
50   if (gtk_widget_is_toplevel (toplevel) && gtk_widget_get_realized (toplevel))
51     gdk_window_register_dnd (gtk_widget_get_window (toplevel));
52 }
53 
54 static void
gtk_drag_dest_site_destroy(gpointer data)55 gtk_drag_dest_site_destroy (gpointer data)
56 {
57   GtkDragDestSite *site = data;
58 
59   if (site->proxy_window)
60     g_object_unref (site->proxy_window);
61 
62   if (site->target_list)
63     gtk_target_list_unref (site->target_list);
64 
65   g_slice_free (GtkDragDestSite, site);
66 }
67 
68 static void
gtk_drag_dest_set_internal(GtkWidget * widget,GtkDragDestSite * site)69 gtk_drag_dest_set_internal (GtkWidget       *widget,
70                             GtkDragDestSite *site)
71 {
72   GtkDragDestSite *old_site;
73 
74   old_site = g_object_get_data (G_OBJECT (widget), I_("gtk-drag-dest"));
75   if (old_site)
76     {
77       g_signal_handlers_disconnect_by_func (widget,
78                                             gtk_drag_dest_realized,
79                                             old_site);
80       g_signal_handlers_disconnect_by_func (widget,
81                                             gtk_drag_dest_hierarchy_changed,
82                                             old_site);
83 
84       site->track_motion = old_site->track_motion;
85     }
86 
87   if (gtk_widget_get_realized (widget))
88     gtk_drag_dest_realized (widget);
89 
90   g_signal_connect (widget, "realize",
91                     G_CALLBACK (gtk_drag_dest_realized), site);
92   g_signal_connect (widget, "hierarchy-changed",
93                     G_CALLBACK (gtk_drag_dest_hierarchy_changed), site);
94 
95   g_object_set_data_full (G_OBJECT (widget), I_("gtk-drag-dest"),
96                           site, gtk_drag_dest_site_destroy);
97 }
98 
99 /**
100  * gtk_drag_dest_set: (method)
101  * @widget: a #GtkWidget
102  * @flags: which types of default drag behavior to use
103  * @targets: (allow-none) (array length=n_targets): a pointer to an array of
104  *     #GtkTargetEntrys indicating the drop types that this @widget will
105  *     accept, or %NULL. Later you can access the list with
106  *     gtk_drag_dest_get_target_list() and gtk_drag_dest_find_target().
107  * @n_targets: the number of entries in @targets
108  * @actions: a bitmask of possible actions for a drop onto this @widget.
109  *
110  * Sets a widget as a potential drop destination, and adds default behaviors.
111  *
112  * The default behaviors listed in @flags have an effect similar
113  * to installing default handlers for the widget’s drag-and-drop signals
114  * (#GtkWidget::drag-motion, #GtkWidget::drag-drop, ...). They all exist
115  * for convenience. When passing #GTK_DEST_DEFAULT_ALL for instance it is
116  * sufficient to connect to the widget’s #GtkWidget::drag-data-received
117  * signal to get primitive, but consistent drag-and-drop support.
118  *
119  * Things become more complicated when you try to preview the dragged data,
120  * as described in the documentation for #GtkWidget::drag-motion. The default
121  * behaviors described by @flags make some assumptions, that can conflict
122  * with your own signal handlers. For instance #GTK_DEST_DEFAULT_DROP causes
123  * invokations of gdk_drag_status() in the context of #GtkWidget::drag-motion,
124  * and invokations of gtk_drag_finish() in #GtkWidget::drag-data-received.
125  * Especially the later is dramatic, when your own #GtkWidget::drag-motion
126  * handler calls gtk_drag_get_data() to inspect the dragged data.
127  *
128  * There’s no way to set a default action here, you can use the
129  * #GtkWidget::drag-motion callback for that. Here’s an example which selects
130  * the action to use depending on whether the control key is pressed or not:
131  * |[<!-- language="C" -->
132  * static void
133  * drag_motion (GtkWidget *widget,
134  *              GdkDragContext *context,
135  *              gint x,
136  *              gint y,
137  *              guint time)
138  * {
139 *   GdkModifierType mask;
140  *
141  *   gdk_window_get_pointer (gtk_widget_get_window (widget),
142  *                           NULL, NULL, &mask);
143  *   if (mask & GDK_CONTROL_MASK)
144  *     gdk_drag_status (context, GDK_ACTION_COPY, time);
145  *   else
146  *     gdk_drag_status (context, GDK_ACTION_MOVE, time);
147  * }
148  * ]|
149  */
150 void
gtk_drag_dest_set(GtkWidget * widget,GtkDestDefaults flags,const GtkTargetEntry * targets,gint n_targets,GdkDragAction actions)151 gtk_drag_dest_set (GtkWidget            *widget,
152                    GtkDestDefaults       flags,
153                    const GtkTargetEntry *targets,
154                    gint                  n_targets,
155                    GdkDragAction         actions)
156 {
157   GtkDragDestSite *site;
158 
159   g_return_if_fail (GTK_IS_WIDGET (widget));
160 
161   site = g_slice_new0 (GtkDragDestSite);
162 
163   site->flags = flags;
164   site->have_drag = FALSE;
165   if (targets)
166     site->target_list = gtk_target_list_new (targets, n_targets);
167   else
168     site->target_list = NULL;
169   site->actions = actions;
170   site->do_proxy = FALSE;
171   site->proxy_window = NULL;
172   site->track_motion = FALSE;
173 
174   gtk_drag_dest_set_internal (widget, site);
175 }
176 
177 /**
178  * gtk_drag_dest_set_proxy: (method)
179  * @widget: a #GtkWidget
180  * @proxy_window: the window to which to forward drag events
181  * @protocol: the drag protocol which the @proxy_window accepts
182  *   (You can use gdk_drag_get_protocol() to determine this)
183  * @use_coordinates: If %TRUE, send the same coordinates to the
184  *   destination, because it is an embedded
185  *   subwindow.
186  *
187  * Sets this widget as a proxy for drops to another window.
188  *
189  * Deprecated: 3.22
190  */
191 void
gtk_drag_dest_set_proxy(GtkWidget * widget,GdkWindow * proxy_window,GdkDragProtocol protocol,gboolean use_coordinates)192 gtk_drag_dest_set_proxy (GtkWidget       *widget,
193                          GdkWindow       *proxy_window,
194                          GdkDragProtocol  protocol,
195                          gboolean         use_coordinates)
196 {
197   GtkDragDestSite *site;
198 
199   g_return_if_fail (GTK_IS_WIDGET (widget));
200   g_return_if_fail (!proxy_window || GDK_IS_WINDOW (proxy_window));
201 
202   site = g_slice_new (GtkDragDestSite);
203 
204   site->flags = 0;
205   site->have_drag = FALSE;
206   site->target_list = NULL;
207   site->actions = 0;
208   site->proxy_window = proxy_window;
209   if (proxy_window)
210     g_object_ref (proxy_window);
211   site->do_proxy = TRUE;
212   site->proxy_protocol = protocol;
213   site->proxy_coords = use_coordinates;
214   site->track_motion = FALSE;
215 
216   gtk_drag_dest_set_internal (widget, site);
217 }
218 
219 /**
220  * gtk_drag_dest_unset: (method)
221  * @widget: a #GtkWidget
222  *
223  * Clears information about a drop destination set with
224  * gtk_drag_dest_set(). The widget will no longer receive
225  * notification of drags.
226  */
227 void
gtk_drag_dest_unset(GtkWidget * widget)228 gtk_drag_dest_unset (GtkWidget *widget)
229 {
230   GtkDragDestSite *old_site;
231 
232   g_return_if_fail (GTK_IS_WIDGET (widget));
233 
234   old_site = g_object_get_data (G_OBJECT (widget), I_("gtk-drag-dest"));
235   if (old_site)
236     {
237       g_signal_handlers_disconnect_by_func (widget,
238                                             gtk_drag_dest_realized,
239                                             old_site);
240       g_signal_handlers_disconnect_by_func (widget,
241                                             gtk_drag_dest_hierarchy_changed,
242                                             old_site);
243     }
244 
245   g_object_set_data (G_OBJECT (widget), I_("gtk-drag-dest"), NULL);
246 }
247 
248 /**
249  * gtk_drag_dest_get_target_list: (method)
250  * @widget: a #GtkWidget
251  *
252  * Returns the list of targets this widget can accept from
253  * drag-and-drop.
254  *
255  * Returns: (nullable) (transfer none): the #GtkTargetList, or %NULL if none
256  */
257 GtkTargetList *
gtk_drag_dest_get_target_list(GtkWidget * widget)258 gtk_drag_dest_get_target_list (GtkWidget *widget)
259 {
260   GtkDragDestSite *site;
261 
262   g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL);
263 
264   site = g_object_get_data (G_OBJECT (widget), I_("gtk-drag-dest"));
265 
266   return site ? site->target_list : NULL;
267 }
268 
269 /**
270  * gtk_drag_dest_set_target_list: (method)
271  * @widget: a #GtkWidget that’s a drag destination
272  * @target_list: (allow-none): list of droppable targets, or %NULL for none
273  *
274  * Sets the target types that this widget can accept from drag-and-drop.
275  * The widget must first be made into a drag destination with
276  * gtk_drag_dest_set().
277  */
278 void
gtk_drag_dest_set_target_list(GtkWidget * widget,GtkTargetList * target_list)279 gtk_drag_dest_set_target_list (GtkWidget     *widget,
280                                GtkTargetList *target_list)
281 {
282   GtkDragDestSite *site;
283 
284   g_return_if_fail (GTK_IS_WIDGET (widget));
285 
286   site = g_object_get_data (G_OBJECT (widget), I_("gtk-drag-dest"));
287 
288   if (!site)
289     {
290       g_warning ("Can't set a target list on a widget until you've called gtk_drag_dest_set() "
291                  "to make the widget into a drag destination");
292       return;
293     }
294 
295   if (target_list)
296     gtk_target_list_ref (target_list);
297 
298   if (site->target_list)
299     gtk_target_list_unref (site->target_list);
300 
301   site->target_list = target_list;
302 }
303 
304 /**
305  * gtk_drag_dest_add_text_targets: (method)
306  * @widget: a #GtkWidget that’s a drag destination
307  *
308  * Add the text targets supported by #GtkSelectionData to
309  * the target list of the drag destination. The targets
310  * are added with @info = 0. If you need another value,
311  * use gtk_target_list_add_text_targets() and
312  * gtk_drag_dest_set_target_list().
313  *
314  * Since: 2.6
315  */
316 void
gtk_drag_dest_add_text_targets(GtkWidget * widget)317 gtk_drag_dest_add_text_targets (GtkWidget *widget)
318 {
319   GtkTargetList *target_list;
320 
321   target_list = gtk_drag_dest_get_target_list (widget);
322   if (target_list)
323     gtk_target_list_ref (target_list);
324   else
325     target_list = gtk_target_list_new (NULL, 0);
326   gtk_target_list_add_text_targets (target_list, 0);
327   gtk_drag_dest_set_target_list (widget, target_list);
328   gtk_target_list_unref (target_list);
329 }
330 
331 /**
332  * gtk_drag_dest_add_image_targets: (method)
333  * @widget: a #GtkWidget that’s a drag destination
334  *
335  * Add the image targets supported by #GtkSelectionData to
336  * the target list of the drag destination. The targets
337  * are added with @info = 0. If you need another value,
338  * use gtk_target_list_add_image_targets() and
339  * gtk_drag_dest_set_target_list().
340  *
341  * Since: 2.6
342  */
343 void
gtk_drag_dest_add_image_targets(GtkWidget * widget)344 gtk_drag_dest_add_image_targets (GtkWidget *widget)
345 {
346   GtkTargetList *target_list;
347 
348   target_list = gtk_drag_dest_get_target_list (widget);
349   if (target_list)
350     gtk_target_list_ref (target_list);
351   else
352     target_list = gtk_target_list_new (NULL, 0);
353   gtk_target_list_add_image_targets (target_list, 0, FALSE);
354   gtk_drag_dest_set_target_list (widget, target_list);
355   gtk_target_list_unref (target_list);
356 }
357 
358 /**
359  * gtk_drag_dest_add_uri_targets: (method)
360  * @widget: a #GtkWidget that’s a drag destination
361  *
362  * Add the URI targets supported by #GtkSelectionData to
363  * the target list of the drag destination. The targets
364  * are added with @info = 0. If you need another value,
365  * use gtk_target_list_add_uri_targets() and
366  * gtk_drag_dest_set_target_list().
367  *
368  * Since: 2.6
369  */
370 void
gtk_drag_dest_add_uri_targets(GtkWidget * widget)371 gtk_drag_dest_add_uri_targets (GtkWidget *widget)
372 {
373   GtkTargetList *target_list;
374 
375   target_list = gtk_drag_dest_get_target_list (widget);
376   if (target_list)
377     gtk_target_list_ref (target_list);
378   else
379     target_list = gtk_target_list_new (NULL, 0);
380   gtk_target_list_add_uri_targets (target_list, 0);
381   gtk_drag_dest_set_target_list (widget, target_list);
382   gtk_target_list_unref (target_list);
383 }
384 
385 /**
386  * gtk_drag_dest_set_track_motion: (method)
387  * @widget: a #GtkWidget that’s a drag destination
388  * @track_motion: whether to accept all targets
389  *
390  * Tells the widget to emit #GtkWidget::drag-motion and
391  * #GtkWidget::drag-leave events regardless of the targets and the
392  * %GTK_DEST_DEFAULT_MOTION flag.
393  *
394  * This may be used when a widget wants to do generic
395  * actions regardless of the targets that the source offers.
396  *
397  * Since: 2.10
398  */
399 void
gtk_drag_dest_set_track_motion(GtkWidget * widget,gboolean track_motion)400 gtk_drag_dest_set_track_motion (GtkWidget *widget,
401                                 gboolean   track_motion)
402 {
403   GtkDragDestSite *site;
404 
405   g_return_if_fail (GTK_IS_WIDGET (widget));
406 
407   site = g_object_get_data (G_OBJECT (widget), I_("gtk-drag-dest"));
408 
409   g_return_if_fail (site != NULL);
410 
411   site->track_motion = track_motion != FALSE;
412 }
413 
414 /**
415  * gtk_drag_dest_get_track_motion: (method)
416  * @widget: a #GtkWidget that’s a drag destination
417  *
418  * Returns whether the widget has been configured to always
419  * emit #GtkWidget::drag-motion signals.
420  *
421  * Returns: %TRUE if the widget always emits
422  *   #GtkWidget::drag-motion events
423  *
424  * Since: 2.10
425  */
426 gboolean
gtk_drag_dest_get_track_motion(GtkWidget * widget)427 gtk_drag_dest_get_track_motion (GtkWidget *widget)
428 {
429   GtkDragDestSite *site;
430 
431   g_return_val_if_fail (GTK_IS_WIDGET (widget), FALSE);
432 
433   site = g_object_get_data (G_OBJECT (widget), I_("gtk-drag-dest"));
434 
435   if (site)
436     return site->track_motion;
437 
438   return FALSE;
439 }
440 
441 /**
442  * gtk_drag_dest_find_target: (method)
443  * @widget: drag destination widget
444  * @context: drag context
445  * @target_list: (allow-none): list of droppable targets, or %NULL to use
446  *    gtk_drag_dest_get_target_list (@widget).
447  *
448  * Looks for a match between the supported targets of @context and the
449  * @dest_target_list, returning the first matching target, otherwise
450  * returning %GDK_NONE. @dest_target_list should usually be the return
451  * value from gtk_drag_dest_get_target_list(), but some widgets may
452  * have different valid targets for different parts of the widget; in
453  * that case, they will have to implement a drag_motion handler that
454  * passes the correct target list to this function.
455  *
456  * Returns: (transfer none): first target that the source offers
457  *     and the dest can accept, or %GDK_NONE
458  */
459 GdkAtom
gtk_drag_dest_find_target(GtkWidget * widget,GdkDragContext * context,GtkTargetList * target_list)460 gtk_drag_dest_find_target (GtkWidget      *widget,
461                            GdkDragContext *context,
462                            GtkTargetList  *target_list)
463 {
464   GList *tmp_target;
465   GList *tmp_source = NULL;
466   GtkWidget *source_widget;
467 
468   g_return_val_if_fail (GTK_IS_WIDGET (widget), GDK_NONE);
469   g_return_val_if_fail (GDK_IS_DRAG_CONTEXT (context), GDK_NONE);
470 
471   source_widget = gtk_drag_get_source_widget (context);
472   if (target_list == NULL)
473     target_list = gtk_drag_dest_get_target_list (widget);
474 
475   if (target_list == NULL)
476     return GDK_NONE;
477 
478   tmp_target = target_list->list;
479   while (tmp_target)
480     {
481       GtkTargetPair *pair = tmp_target->data;
482       tmp_source = gdk_drag_context_list_targets (context);
483       while (tmp_source)
484         {
485           if (tmp_source->data == GUINT_TO_POINTER (pair->target))
486             {
487               if ((!(pair->flags & GTK_TARGET_SAME_APP) || source_widget) &&
488                   (!(pair->flags & GTK_TARGET_SAME_WIDGET) || (source_widget == widget)) &&
489                   (!(pair->flags & GTK_TARGET_OTHER_APP) || !source_widget) &&
490                   (!(pair->flags & GTK_TARGET_OTHER_WIDGET) || (source_widget != widget)))
491                 return pair->target;
492               else
493                 break;
494             }
495           tmp_source = tmp_source->next;
496         }
497       tmp_target = tmp_target->next;
498     }
499 
500   return GDK_NONE;
501 }
502 
503