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