No issues found
1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
2
3 /* nautilus-canvas-dnd.c - Drag & drop handling for the canvas container widget.
4
5 Copyright (C) 1999, 2000 Free Software Foundation
6 Copyright (C) 2000 Eazel, Inc.
7
8 The Gnome Library is free software; you can redistribute it and/or
9 modify it under the terms of the GNU Library General Public License as
10 published by the Free Software Foundation; either version 2 of the
11 License, or (at your option) any later version.
12
13 The Gnome 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 Library General Public License for more details.
17
18 You should have received a copy of the GNU Library General Public
19 License along with the Gnome Library; see the file COPYING.LIB. If not,
20 write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
21 Boston, MA 02111-1307, USA.
22
23 Authors: Ettore Perazzoli <ettore@gnu.org>,
24 Darin Adler <darin@bentspoon.com>,
25 Andy Hertzfeld <andy@eazel.com>
26 Pavel Cisler <pavel@eazel.com>
27
28
29 XDS support: Benedikt Meurer <benny@xfce.org> (adapted by Amos Brocco <amos.brocco@unifr.ch>)
30
31 */
32
33
34 #include <config.h>
35 #include <math.h>
36 #include "nautilus-canvas-dnd.h"
37
38 #include "nautilus-file-dnd.h"
39 #include "nautilus-canvas-private.h"
40 #include "nautilus-link.h"
41 #include "nautilus-metadata.h"
42 #include "nautilus-selection-canvas-item.h"
43 #include <eel/eel-glib-extensions.h>
44 #include <eel/eel-gnome-extensions.h>
45 #include <eel/eel-graphic-effects.h>
46 #include <eel/eel-gtk-extensions.h>
47 #include <eel/eel-stock-dialogs.h>
48 #include <eel/eel-string.h>
49 #include <eel/eel-vfs-extensions.h>
50 #include <gdk/gdkkeysyms.h>
51 #include <gdk/gdkx.h>
52 #include <gtk/gtk.h>
53 #include <glib/gi18n.h>
54
55 #include <libnautilus-private/nautilus-desktop-background.h>
56 #include <libnautilus-private/nautilus-file-utilities.h>
57 #include <libnautilus-private/nautilus-file-changes-queue.h>
58 #include <stdio.h>
59 #include <string.h>
60
61 #define DEBUG_FLAG NAUTILUS_DEBUG_CANVAS_CONTAINER
62 #include "nautilus-debug.h"
63
64 static const GtkTargetEntry drag_types [] = {
65 { NAUTILUS_ICON_DND_GNOME_ICON_LIST_TYPE, 0, NAUTILUS_ICON_DND_GNOME_ICON_LIST },
66 { NAUTILUS_ICON_DND_URI_LIST_TYPE, 0, NAUTILUS_ICON_DND_URI_LIST },
67 };
68
69 static const GtkTargetEntry drop_types [] = {
70 { NAUTILUS_ICON_DND_GNOME_ICON_LIST_TYPE, 0, NAUTILUS_ICON_DND_GNOME_ICON_LIST },
71 /* prefer "_NETSCAPE_URL" over "text/uri-list" to satisfy web browsers. */
72 { NAUTILUS_ICON_DND_NETSCAPE_URL_TYPE, 0, NAUTILUS_ICON_DND_NETSCAPE_URL },
73 { NAUTILUS_ICON_DND_URI_LIST_TYPE, 0, NAUTILUS_ICON_DND_URI_LIST },
74 { NAUTILUS_ICON_DND_XDNDDIRECTSAVE_TYPE, 0, NAUTILUS_ICON_DND_XDNDDIRECTSAVE }, /* XDS Protocol Type */
75 { NAUTILUS_ICON_DND_RAW_TYPE, 0, NAUTILUS_ICON_DND_RAW },
76 /* Must be last: */
77 { NAUTILUS_ICON_DND_ROOTWINDOW_DROP_TYPE, 0, NAUTILUS_ICON_DND_ROOTWINDOW_DROP }
78 };
79 static void stop_dnd_highlight (GtkWidget *widget);
80 static void dnd_highlight_queue_redraw (GtkWidget *widget);
81
82 static GtkTargetList *drop_types_list = NULL;
83 static GtkTargetList *drop_types_list_root = NULL;
84
85 static char * nautilus_canvas_container_find_drop_target (NautilusCanvasContainer *container,
86 GdkDragContext *context,
87 int x, int y, gboolean *icon_hit,
88 gboolean rewrite_desktop);
89
90 static EelCanvasItem *
91 create_selection_shadow (NautilusCanvasContainer *container,
92 GList *list)
93 {
94 EelCanvasGroup *group;
95 EelCanvas *canvas;
96 int max_x, max_y;
97 int min_x, min_y;
98 GList *p;
99 GtkAllocation allocation;
100
101 if (list == NULL) {
102 return NULL;
103 }
104
105 /* if we're only dragging a single item, don't worry about the shadow */
106 if (list->next == NULL) {
107 return NULL;
108 }
109
110 canvas = EEL_CANVAS (container);
111 gtk_widget_get_allocation (GTK_WIDGET (container), &allocation);
112
113 /* Creating a big set of rectangles in the canvas can be expensive, so
114 we try to be smart and only create the maximum number of rectangles
115 that we will need, in the vertical/horizontal directions. */
116
117 max_x = allocation.width;
118 min_x = -max_x;
119
120 max_y = allocation.height;
121 min_y = -max_y;
122
123 /* Create a group, so that it's easier to move all the items around at
124 once. */
125 group = EEL_CANVAS_GROUP
126 (eel_canvas_item_new (EEL_CANVAS_GROUP (canvas->root),
127 eel_canvas_group_get_type (),
128 NULL));
129
130 for (p = list; p != NULL; p = p->next) {
131 NautilusDragSelectionItem *item;
132 int x1, y1, x2, y2;
133 GdkRGBA black = { 0, 0, 0, 1 };
134
135 item = p->data;
136
137 if (!item->got_icon_position) {
138 continue;
139 }
140
141 x1 = item->icon_x;
142 y1 = item->icon_y;
143 x2 = x1 + item->icon_width;
144 y2 = y1 + item->icon_height;
145
146 if (x2 >= min_x && x1 <= max_x && y2 >= min_y && y1 <= max_y)
147 eel_canvas_item_new
148 (group,
149 NAUTILUS_TYPE_SELECTION_CANVAS_ITEM,
150 "x1", (double) x1,
151 "y1", (double) y1,
152 "x2", (double) x2,
153 "y2", (double) y2,
154 "outline-color-rgba", &black,
155 "outline-stippling", TRUE,
156 "width_pixels", 1,
157 NULL);
158 }
159
160 return EEL_CANVAS_ITEM (group);
161 }
162
163 /* Set the affine instead of the x and y position.
164 * Simple, and setting x and y was broken at one point.
165 */
166 static void
167 set_shadow_position (EelCanvasItem *shadow,
168 double x, double y)
169 {
170 eel_canvas_item_set (shadow,
171 "x", x, "y", y,
172 NULL);
173 }
174
175
176 /* Source-side handling of the drag. */
177
178 /* iteration glue struct */
179 typedef struct {
180 gpointer iterator_context;
181 NautilusDragEachSelectedItemDataGet iteratee;
182 gpointer iteratee_data;
183 } CanvasGetDataBinderContext;
184
185 static void
186 canvas_rect_world_to_widget (EelCanvas *canvas,
187 EelDRect *world_rect,
188 EelIRect *widget_rect)
189 {
190 EelDRect window_rect;
191 GtkAdjustment *hadj, *vadj;
192
193 hadj = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (canvas));
194 vadj = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (canvas));
195
196 eel_canvas_world_to_window (canvas,
197 world_rect->x0, world_rect->y0,
198 &window_rect.x0, &window_rect.y0);
199 eel_canvas_world_to_window (canvas,
200 world_rect->x1, world_rect->y1,
201 &window_rect.x1, &window_rect.y1);
202 widget_rect->x0 = (int) window_rect.x0 - gtk_adjustment_get_value (hadj);
203 widget_rect->y0 = (int) window_rect.y0 - gtk_adjustment_get_value (vadj);
204 widget_rect->x1 = (int) window_rect.x1 - gtk_adjustment_get_value (hadj);
205 widget_rect->y1 = (int) window_rect.y1 - gtk_adjustment_get_value (vadj);
206 }
207
208 static void
209 canvas_widget_to_world (EelCanvas *canvas,
210 double widget_x, double widget_y,
211 double *world_x, double *world_y)
212 {
213 eel_canvas_window_to_world (canvas,
214 widget_x + gtk_adjustment_get_value (gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (canvas))),
215 widget_y + gtk_adjustment_get_value (gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (canvas))),
216 world_x, world_y);
217 }
218
219 static gboolean
220 icon_get_data_binder (NautilusCanvasIcon *icon, gpointer data)
221 {
222 CanvasGetDataBinderContext *context;
223 EelDRect world_rect;
224 EelIRect widget_rect;
225 char *uri;
226 NautilusCanvasContainer *container;
227
228 context = (CanvasGetDataBinderContext *)data;
229
230 g_assert (NAUTILUS_IS_CANVAS_CONTAINER (context->iterator_context));
231
232 container = NAUTILUS_CANVAS_CONTAINER (context->iterator_context);
233
234 world_rect = nautilus_canvas_item_get_icon_rectangle (icon->item);
235
236 canvas_rect_world_to_widget (EEL_CANVAS (container), &world_rect, &widget_rect);
237
238 uri = nautilus_canvas_container_get_icon_uri (container, icon);
239 if (uri == NULL) {
240 g_warning ("no URI for one of the iterated icons");
241 return TRUE;
242 }
243
244 widget_rect = eel_irect_offset_by (widget_rect,
245 - container->details->dnd_info->drag_info.start_x,
246 - container->details->dnd_info->drag_info.start_y);
247
248 widget_rect = eel_irect_scale_by (widget_rect,
249 1 / EEL_CANVAS (container)->pixels_per_unit);
250
251 /* pass the uri, mouse-relative x/y and icon width/height */
252 context->iteratee (uri,
253 (int) widget_rect.x0,
254 (int) widget_rect.y0,
255 widget_rect.x1 - widget_rect.x0,
256 widget_rect.y1 - widget_rect.y0,
257 context->iteratee_data);
258
259 g_free (uri);
260
261 return TRUE;
262 }
263
264 /* Iterate over each selected icon in a NautilusCanvasContainer,
265 * calling each_function on each.
266 */
267 static void
268 nautilus_canvas_container_each_selected_icon (NautilusCanvasContainer *container,
269 gboolean (*each_function) (NautilusCanvasIcon *, gpointer), gpointer data)
270 {
271 GList *p;
272 NautilusCanvasIcon *icon;
273
274 for (p = container->details->icons; p != NULL; p = p->next) {
275 icon = p->data;
276 if (!icon->is_selected) {
277 continue;
278 }
279 if (!each_function (icon, data)) {
280 return;
281 }
282 }
283 }
284
285 /* Adaptor function used with nautilus_canvas_container_each_selected_icon
286 * to help iterate over all selected items, passing uris, x, y, w and h
287 * values to the iteratee
288 */
289 static void
290 each_icon_get_data_binder (NautilusDragEachSelectedItemDataGet iteratee,
291 gpointer iterator_context, gpointer data)
292 {
293 CanvasGetDataBinderContext context;
294 NautilusCanvasContainer *container;
295
296 g_assert (NAUTILUS_IS_CANVAS_CONTAINER (iterator_context));
297 container = NAUTILUS_CANVAS_CONTAINER (iterator_context);
298
299 context.iterator_context = iterator_context;
300 context.iteratee = iteratee;
301 context.iteratee_data = data;
302 nautilus_canvas_container_each_selected_icon (container, icon_get_data_binder, &context);
303 }
304
305 /* Called when the data for drag&drop is needed */
306 static void
307 drag_data_get_callback (GtkWidget *widget,
308 GdkDragContext *context,
309 GtkSelectionData *selection_data,
310 guint info,
311 guint32 time,
312 gpointer data)
313 {
314 g_assert (widget != NULL);
315 g_assert (NAUTILUS_IS_CANVAS_CONTAINER (widget));
316 g_return_if_fail (context != NULL);
317
318 /* Call common function from nautilus-drag that set's up
319 * the selection data in the right format. Pass it means to
320 * iterate all the selected icons.
321 */
322 nautilus_drag_drag_data_get (widget, context, selection_data,
323 info, time, widget, each_icon_get_data_binder);
324 }
325
326
327 /* Target-side handling of the drag. */
328
329 static void
330 nautilus_canvas_container_position_shadow (NautilusCanvasContainer *container,
331 int x, int y)
332 {
333 EelCanvasItem *shadow;
334 double world_x, world_y;
335
336 shadow = container->details->dnd_info->shadow;
337 if (shadow == NULL) {
338 return;
339 }
340
341 canvas_widget_to_world (EEL_CANVAS (container), x, y,
342 &world_x, &world_y);
343
344 set_shadow_position (shadow, world_x, world_y);
345 eel_canvas_item_show (shadow);
346 }
347
348 static void
349 nautilus_canvas_container_dropped_canvas_feedback (GtkWidget *widget,
350 GtkSelectionData *data,
351 int x, int y)
352 {
353 NautilusCanvasContainer *container;
354 NautilusCanvasDndInfo *dnd_info;
355
356 container = NAUTILUS_CANVAS_CONTAINER (widget);
357 dnd_info = container->details->dnd_info;
358
359 /* Delete old selection list. */
360 nautilus_drag_destroy_selection_list (dnd_info->drag_info.selection_list);
361 dnd_info->drag_info.selection_list = NULL;
362
363 /* Delete old shadow if any. */
364 if (dnd_info->shadow != NULL) {
365 /* FIXME bugzilla.gnome.org 42484:
366 * Is a destroy really sufficient here? Who does the unref? */
367 eel_canvas_item_destroy (dnd_info->shadow);
368 }
369
370 /* Build the selection list and the shadow. */
371 dnd_info->drag_info.selection_list = nautilus_drag_build_selection_list (data);
372 dnd_info->shadow = create_selection_shadow (container, dnd_info->drag_info.selection_list);
373 nautilus_canvas_container_position_shadow (container, x, y);
374 }
375
376 static char *
377 get_direct_save_filename (GdkDragContext *context)
378 {
379 guchar *prop_text;
380 gint prop_len;
381
382 if (!gdk_property_get (gdk_drag_context_get_source_window (context), gdk_atom_intern (NAUTILUS_ICON_DND_XDNDDIRECTSAVE_TYPE, FALSE),
383 gdk_atom_intern ("text/plain", FALSE), 0, 1024, FALSE, NULL, NULL,
384 &prop_len, &prop_text)) {
385 return NULL;
386 }
387
388 /* Zero-terminate the string */
389 prop_text = g_realloc (prop_text, prop_len + 1);
390 prop_text[prop_len] = '\0';
391
392 /* Verify that the file name provided by the source is valid */
393 if (*prop_text == '\0' ||
394 strchr ((const gchar *) prop_text, G_DIR_SEPARATOR) != NULL) {
395 DEBUG ("Invalid filename provided by XDS drag site");
396 g_free (prop_text);
397 return NULL;
398 }
399
400 return (gchar *) prop_text;
401 }
402
403 static void
404 set_direct_save_uri (GtkWidget *widget, GdkDragContext *context, NautilusDragInfo *drag_info, int x, int y)
405 {
406 GFile *base, *child;
407 char *filename, *drop_target;
408 gchar *uri;
409
410 drag_info->got_drop_data_type = TRUE;
411 drag_info->data_type = NAUTILUS_ICON_DND_XDNDDIRECTSAVE;
412
413 uri = NULL;
414
415 filename = get_direct_save_filename (context);
416 drop_target = nautilus_canvas_container_find_drop_target (NAUTILUS_CANVAS_CONTAINER (widget),
417 context, x, y, NULL, TRUE);
418
419 if (drop_target && eel_uri_is_trash (drop_target)) {
420 g_free (drop_target);
421 drop_target = NULL; /* Cannot save to trash ...*/
422 }
423
424 if (filename != NULL && drop_target != NULL) {
425 /* Resolve relative path */
426 base = g_file_new_for_uri (drop_target);
427 child = g_file_get_child (base, filename);
428 uri = g_file_get_uri (child);
429 g_object_unref (base);
430 g_object_unref (child);
431
432 /* Change the uri property */
433 gdk_property_change (gdk_drag_context_get_source_window (context),
434 gdk_atom_intern (NAUTILUS_ICON_DND_XDNDDIRECTSAVE_TYPE, FALSE),
435 gdk_atom_intern ("text/plain", FALSE), 8,
436 GDK_PROP_MODE_REPLACE, (const guchar *) uri,
437 strlen (uri));
438
439 drag_info->direct_save_uri = uri;
440 }
441
442 g_free (filename);
443 g_free (drop_target);
444 }
445
446 /* FIXME bugzilla.gnome.org 47445: Needs to become a shared function */
447 static void
448 get_data_on_first_target_we_support (GtkWidget *widget, GdkDragContext *context, guint32 time, int x, int y)
449 {
450 GtkTargetList *list;
451 GdkAtom target;
452
453 if (drop_types_list == NULL) {
454 drop_types_list = gtk_target_list_new (drop_types,
455 G_N_ELEMENTS (drop_types) - 1);
456 gtk_target_list_add_text_targets (drop_types_list, NAUTILUS_ICON_DND_TEXT);
457 }
458 if (drop_types_list_root == NULL) {
459 drop_types_list_root = gtk_target_list_new (drop_types,
460 G_N_ELEMENTS (drop_types));
461 gtk_target_list_add_text_targets (drop_types_list_root, NAUTILUS_ICON_DND_TEXT);
462 }
463
464 if (nautilus_canvas_container_get_is_desktop (NAUTILUS_CANVAS_CONTAINER (widget))) {
465 list = drop_types_list_root;
466 } else {
467 list = drop_types_list;
468 }
469
470 target = gtk_drag_dest_find_target (widget, context, list);
471 if (target != GDK_NONE) {
472 guint info;
473 NautilusDragInfo *drag_info;
474 gboolean found;
475
476 drag_info = &(NAUTILUS_CANVAS_CONTAINER (widget)->details->dnd_info->drag_info);
477
478 found = gtk_target_list_find (list, target, &info);
479 g_assert (found);
480
481 /* Don't get_data for destructive ops */
482 if ((info == NAUTILUS_ICON_DND_ROOTWINDOW_DROP ||
483 info == NAUTILUS_ICON_DND_XDNDDIRECTSAVE) &&
484 !drag_info->drop_occured) {
485 /* We can't call get_data here, because that would
486 make the source execute the rootwin action or the direct save */
487 drag_info->got_drop_data_type = TRUE;
488 drag_info->data_type = info;
489 } else {
490 if (info == NAUTILUS_ICON_DND_XDNDDIRECTSAVE) {
491 set_direct_save_uri (widget, context, drag_info, x, y);
492 }
493 gtk_drag_get_data (GTK_WIDGET (widget), context,
494 target, time);
495 }
496 }
497 }
498
499 static void
500 nautilus_canvas_container_ensure_drag_data (NautilusCanvasContainer *container,
501 GdkDragContext *context,
502 guint32 time)
503 {
504 NautilusCanvasDndInfo *dnd_info;
505
506 dnd_info = container->details->dnd_info;
507
508 if (!dnd_info->drag_info.got_drop_data_type) {
509 get_data_on_first_target_we_support (GTK_WIDGET (container), context, time, 0, 0);
510 }
511 }
512
513 static void
514 drag_end_callback (GtkWidget *widget,
515 GdkDragContext *context,
516 gpointer data)
517 {
518 NautilusCanvasContainer *container;
519 NautilusCanvasDndInfo *dnd_info;
520
521 container = NAUTILUS_CANVAS_CONTAINER (widget);
522 dnd_info = container->details->dnd_info;
523
524 nautilus_drag_destroy_selection_list (dnd_info->drag_info.selection_list);
525 dnd_info->drag_info.selection_list = NULL;
526 }
527
528 static NautilusCanvasIcon *
529 nautilus_canvas_container_item_at (NautilusCanvasContainer *container,
530 int x, int y)
531 {
532 GList *p;
533 int size;
534 EelDRect point;
535 EelIRect canvas_point;
536
537 /* build the hit-test rectangle. Base the size on the scale factor to ensure that it is
538 * non-empty even at the smallest scale factor
539 */
540
541 size = MAX (1, 1 + (1 / EEL_CANVAS (container)->pixels_per_unit));
542 point.x0 = x;
543 point.y0 = y;
544 point.x1 = x + size;
545 point.y1 = y + size;
546
547 for (p = container->details->icons; p != NULL; p = p->next) {
548 NautilusCanvasIcon *icon;
549 icon = p->data;
550
551 eel_canvas_w2c (EEL_CANVAS (container),
552 point.x0,
553 point.y0,
554 &canvas_point.x0,
555 &canvas_point.y0);
556 eel_canvas_w2c (EEL_CANVAS (container),
557 point.x1,
558 point.y1,
559 &canvas_point.x1,
560 &canvas_point.y1);
561 if (nautilus_canvas_item_hit_test_rectangle (icon->item, canvas_point)) {
562 return icon;
563 }
564 }
565
566 return NULL;
567 }
568
569 static char *
570 get_container_uri (NautilusCanvasContainer *container)
571 {
572 char *uri;
573
574 /* get the URI associated with the container */
575 uri = NULL;
576 g_signal_emit_by_name (container, "get_container_uri", &uri);
577 return uri;
578 }
579
580 static gboolean
581 nautilus_canvas_container_selection_items_local (NautilusCanvasContainer *container,
582 GList *items)
583 {
584 char *container_uri_string;
585 gboolean result;
586
587 /* must have at least one item */
588 g_assert (items);
589
590 /* get the URI associated with the container */
591 container_uri_string = get_container_uri (container);
592
593 if (eel_uri_is_desktop (container_uri_string)) {
594 result = nautilus_drag_items_on_desktop (items);
595 } else {
596 result = nautilus_drag_items_local (container_uri_string, items);
597 }
598 g_free (container_uri_string);
599
600 return result;
601 }
602
603 /* handle dropped url */
604 static void
605 receive_dropped_netscape_url (NautilusCanvasContainer *container, const char *encoded_url, GdkDragContext *context, int x, int y)
606 {
607 char *drop_target;
608
609 if (encoded_url == NULL) {
610 return;
611 }
612
613 drop_target = nautilus_canvas_container_find_drop_target (container, context, x, y, NULL, TRUE);
614
615 g_signal_emit_by_name (container, "handle_netscape_url",
616 encoded_url,
617 drop_target,
618 gdk_drag_context_get_selected_action (context),
619 x, y);
620
621 g_free (drop_target);
622 }
623
624 /* handle dropped uri list */
625 static void
626 receive_dropped_uri_list (NautilusCanvasContainer *container, const char *uri_list, GdkDragContext *context, int x, int y)
627 {
628 char *drop_target;
629
630 if (uri_list == NULL) {
631 return;
632 }
633
634 drop_target = nautilus_canvas_container_find_drop_target (container, context, x, y, NULL, TRUE);
635
636 g_signal_emit_by_name (container, "handle_uri_list",
637 uri_list,
638 drop_target,
639 gdk_drag_context_get_selected_action (context),
640 x, y);
641
642 g_free (drop_target);
643 }
644
645 /* handle dropped text */
646 static void
647 receive_dropped_text (NautilusCanvasContainer *container, const char *text, GdkDragContext *context, int x, int y)
648 {
649 char *drop_target;
650
651 if (text == NULL) {
652 return;
653 }
654
655 drop_target = nautilus_canvas_container_find_drop_target (container, context, x, y, NULL, TRUE);
656
657 g_signal_emit_by_name (container, "handle_text",
658 text,
659 drop_target,
660 gdk_drag_context_get_selected_action (context),
661 x, y);
662
663 g_free (drop_target);
664 }
665
666 /* handle dropped raw data */
667 static void
668 receive_dropped_raw (NautilusCanvasContainer *container, const char *raw_data, int length, const char *direct_save_uri, GdkDragContext *context, int x, int y)
669 {
670 char *drop_target;
671
672 if (raw_data == NULL) {
673 return;
674 }
675
676 drop_target = nautilus_canvas_container_find_drop_target (container, context, x, y, NULL, TRUE);
677
678 g_signal_emit_by_name (container, "handle_raw",
679 raw_data,
680 length,
681 drop_target,
682 direct_save_uri,
683 gdk_drag_context_get_selected_action (context),
684 x, y);
685
686 g_free (drop_target);
687 }
688
689 static int
690 auto_scroll_timeout_callback (gpointer data)
691 {
692 NautilusCanvasContainer *container;
693 GtkWidget *widget;
694 float x_scroll_delta, y_scroll_delta;
695 GdkRectangle exposed_area;
696 GtkAllocation allocation;
697
698 g_assert (NAUTILUS_IS_CANVAS_CONTAINER (data));
699 widget = GTK_WIDGET (data);
700 container = NAUTILUS_CANVAS_CONTAINER (widget);
701
702 if (container->details->dnd_info->drag_info.waiting_to_autoscroll
703 && container->details->dnd_info->drag_info.start_auto_scroll_in > g_get_monotonic_time ()) {
704 /* not yet */
705 return TRUE;
706 }
707
708 container->details->dnd_info->drag_info.waiting_to_autoscroll = FALSE;
709
710 nautilus_drag_autoscroll_calculate_delta (widget, &x_scroll_delta, &y_scroll_delta);
711 if (x_scroll_delta == 0 && y_scroll_delta == 0) {
712 /* no work */
713 return TRUE;
714 }
715
716 /* Clear the old dnd highlight frame */
717 dnd_highlight_queue_redraw (widget);
718
719 if (!nautilus_canvas_container_scroll (container, (int)x_scroll_delta, (int)y_scroll_delta)) {
720 /* the scroll value got pinned to a min or max adjustment value,
721 * we ended up not scrolling
722 */
723 return TRUE;
724 }
725
726 /* Make sure the dnd highlight frame is redrawn */
727 dnd_highlight_queue_redraw (widget);
728
729 /* update cached drag start offsets */
730 container->details->dnd_info->drag_info.start_x -= x_scroll_delta;
731 container->details->dnd_info->drag_info.start_y -= y_scroll_delta;
732
733 /* Due to a glitch in GtkLayout, whe need to do an explicit draw of the exposed
734 * area.
735 * Calculate the size of the area we need to draw
736 */
737 gtk_widget_get_allocation (widget, &allocation);
738 exposed_area.x = allocation.x;
739 exposed_area.y = allocation.y;
740 exposed_area.width = allocation.width;
741 exposed_area.height = allocation.height;
742
743 if (x_scroll_delta > 0) {
744 exposed_area.x = exposed_area.width - x_scroll_delta;
745 } else if (x_scroll_delta < 0) {
746 exposed_area.width = -x_scroll_delta;
747 }
748
749 if (y_scroll_delta > 0) {
750 exposed_area.y = exposed_area.height - y_scroll_delta;
751 } else if (y_scroll_delta < 0) {
752 exposed_area.height = -y_scroll_delta;
753 }
754
755 /* offset it to 0, 0 */
756 exposed_area.x -= allocation.x;
757 exposed_area.y -= allocation.y;
758
759 gtk_widget_queue_draw_area (widget,
760 exposed_area.x,
761 exposed_area.y,
762 exposed_area.width,
763 exposed_area.height);
764
765 return TRUE;
766 }
767
768 static void
769 set_up_auto_scroll_if_needed (NautilusCanvasContainer *container)
770 {
771 nautilus_drag_autoscroll_start (&container->details->dnd_info->drag_info,
772 GTK_WIDGET (container),
773 auto_scroll_timeout_callback,
774 container);
775 }
776
777 static void
778 stop_auto_scroll (NautilusCanvasContainer *container)
779 {
780 nautilus_drag_autoscroll_stop (&container->details->dnd_info->drag_info);
781 }
782
783 static void
784 handle_local_move (NautilusCanvasContainer *container,
785 double world_x, double world_y)
786 {
787 GList *moved_icons, *p;
788 NautilusDragSelectionItem *item;
789 NautilusCanvasIcon *icon;
790 NautilusFile *file;
791 char screen_string[32];
792 GdkScreen *screen;
793 time_t now;
794
795 if (container->details->auto_layout) {
796 return;
797 }
798
799 time (&now);
800
801 /* Move and select the icons. */
802 moved_icons = NULL;
803 for (p = container->details->dnd_info->drag_info.selection_list; p != NULL; p = p->next) {
804 item = p->data;
805
806 icon = nautilus_canvas_container_get_icon_by_uri
807 (container, item->uri);
808
809 if (icon == NULL) {
810 /* probably dragged from another screen. Add it to
811 * this screen
812 */
813
814 file = nautilus_file_get_by_uri (item->uri);
815
816 screen = gtk_widget_get_screen (GTK_WIDGET (container));
817 g_snprintf (screen_string, sizeof (screen_string), "%d",
818 gdk_screen_get_number (screen));
819 nautilus_file_set_metadata (file,
820 NAUTILUS_METADATA_KEY_SCREEN,
821 NULL, screen_string);
822 nautilus_file_set_time_metadata (file,
823 NAUTILUS_METADATA_KEY_ICON_POSITION_TIMESTAMP, now);
824
825 nautilus_canvas_container_add (container, NAUTILUS_CANVAS_ICON_DATA (file));
826
827 icon = nautilus_canvas_container_get_icon_by_uri
828 (container, item->uri);
829 }
830
831 if (item->got_icon_position) {
832 nautilus_canvas_container_move_icon
833 (container, icon,
834 world_x + item->icon_x, world_y + item->icon_y,
835 icon->scale,
836 TRUE, TRUE, TRUE);
837 }
838 moved_icons = g_list_prepend (moved_icons, icon);
839 }
840 nautilus_canvas_container_select_list_unselect_others
841 (container, moved_icons);
842 /* Might have been moved in a way that requires adjusting scroll region. */
843 nautilus_canvas_container_update_scroll_region (container);
844 g_list_free (moved_icons);
845 }
846
847 static void
848 handle_nonlocal_move (NautilusCanvasContainer *container,
849 GdkDragAction action,
850 int x, int y,
851 const char *target_uri,
852 gboolean icon_hit)
853 {
854 GList *source_uris, *p;
855 GArray *source_item_locations;
856 gboolean free_target_uri, is_rtl;
857 int index, item_x;
858 GtkAllocation allocation;
859
860 if (container->details->dnd_info->drag_info.selection_list == NULL) {
861 return;
862 }
863
864 source_uris = NULL;
865 for (p = container->details->dnd_info->drag_info.selection_list; p != NULL; p = p->next) {
866 /* do a shallow copy of all the uri strings of the copied files */
867 source_uris = g_list_prepend (source_uris, ((NautilusDragSelectionItem *)p->data)->uri);
868 }
869 source_uris = g_list_reverse (source_uris);
870
871 is_rtl = nautilus_canvas_container_is_layout_rtl (container);
872
873 source_item_locations = g_array_new (FALSE, TRUE, sizeof (GdkPoint));
874 if (!icon_hit) {
875 /* Drop onto a container. Pass along the item points to allow placing
876 * the items in their same relative positions in the new container.
877 */
878 source_item_locations = g_array_set_size (source_item_locations,
879 g_list_length (container->details->dnd_info->drag_info.selection_list));
880
881 for (index = 0, p = container->details->dnd_info->drag_info.selection_list;
882 p != NULL; index++, p = p->next) {
883 item_x = ((NautilusDragSelectionItem *)p->data)->icon_x;
884 if (is_rtl)
885 item_x = -item_x - ((NautilusDragSelectionItem *)p->data)->icon_width;
886 g_array_index (source_item_locations, GdkPoint, index).x = item_x;
887 g_array_index (source_item_locations, GdkPoint, index).y =
888 ((NautilusDragSelectionItem *)p->data)->icon_y;
889 }
890 }
891
892 free_target_uri = FALSE;
893 /* Rewrite internal desktop URIs to the normal target uri */
894 if (eel_uri_is_desktop (target_uri)) {
895 target_uri = nautilus_get_desktop_directory_uri ();
896 free_target_uri = TRUE;
897 }
898
899 if (is_rtl) {
900 gtk_widget_get_allocation (GTK_WIDGET (container), &allocation);
901 x = CANVAS_WIDTH (container, allocation) - x;
902 }
903
904 /* start the copy */
905 g_signal_emit_by_name (container, "move_copy_items",
906 source_uris,
907 source_item_locations,
908 target_uri,
909 action,
910 x, y);
911
912 if (free_target_uri) {
913 g_free ((char *)target_uri);
914 }
915
916 g_list_free (source_uris);
917 g_array_free (source_item_locations, TRUE);
918 }
919
920 static char *
921 nautilus_canvas_container_find_drop_target (NautilusCanvasContainer *container,
922 GdkDragContext *context,
923 int x, int y,
924 gboolean *icon_hit,
925 gboolean rewrite_desktop)
926 {
927 NautilusCanvasIcon *drop_target_icon;
928 double world_x, world_y;
929 NautilusFile *file;
930 char *icon_uri;
931 char *container_uri;
932
933 if (icon_hit) {
934 *icon_hit = FALSE;
935 }
936
937 if (!container->details->dnd_info->drag_info.got_drop_data_type) {
938 return NULL;
939 }
940
941 canvas_widget_to_world (EEL_CANVAS (container), x, y, &world_x, &world_y);
942
943 /* FIXME bugzilla.gnome.org 42485:
944 * These "can_accept_items" tests need to be done by
945 * the canvas view, not here. This file is not supposed to know
946 * that the target is a file.
947 */
948
949 /* Find the item we hit with our drop, if any */
950 drop_target_icon = nautilus_canvas_container_item_at (container, world_x, world_y);
951 if (drop_target_icon != NULL) {
952 icon_uri = nautilus_canvas_container_get_icon_uri (container, drop_target_icon);
953 if (icon_uri != NULL) {
954 file = nautilus_file_get_by_uri (icon_uri);
955
956 if (!nautilus_drag_can_accept_info (file,
957 container->details->dnd_info->drag_info.data_type,
958 container->details->dnd_info->drag_info.selection_list)) {
959 /* the item we dropped our selection on cannot accept the items,
960 * do the same thing as if we just dropped the items on the canvas
961 */
962 drop_target_icon = NULL;
963 }
964
965 g_free (icon_uri);
966 nautilus_file_unref (file);
967 }
968 }
969
970 if (drop_target_icon == NULL) {
971 if (icon_hit) {
972 *icon_hit = FALSE;
973 }
974
975 container_uri = get_container_uri (container);
976
977 if (container_uri != NULL) {
978 if (rewrite_desktop && eel_uri_is_desktop (container_uri)) {
979 g_free (container_uri);
980 container_uri = nautilus_get_desktop_directory_uri ();
981 } else {
982 gboolean can;
983 file = nautilus_file_get_by_uri (container_uri);
984 can = nautilus_drag_can_accept_info (file,
985 container->details->dnd_info->drag_info.data_type,
986 container->details->dnd_info->drag_info.selection_list);
987 g_object_unref (file);
988 if (!can) {
989 g_free (container_uri);
990 container_uri = NULL;
991 }
992 }
993 }
994
995 return container_uri;
996 }
997
998 if (icon_hit) {
999 *icon_hit = TRUE;
1000 }
1001 return nautilus_canvas_container_get_icon_drop_target_uri (container, drop_target_icon);
1002 }
1003
1004 static gboolean
1005 selection_is_image_file (GList *selection_list)
1006 {
1007 const char *mime_type;
1008 NautilusDragSelectionItem *selected_item;
1009 gboolean result;
1010 GFile *location;
1011 GFileInfo *info;
1012
1013 /* Make sure only one item is selected */
1014 if (selection_list == NULL ||
1015 selection_list->next != NULL) {
1016 return FALSE;
1017 }
1018
1019 selected_item = selection_list->data;
1020
1021 mime_type = NULL;
1022
1023 location = g_file_new_for_uri (selected_item->uri);
1024 info = g_file_query_info (location,
1025 G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE,
1026 0, NULL, NULL);
1027 if (info) {
1028 mime_type = g_file_info_get_content_type (info);
1029 }
1030
1031 result = g_str_has_prefix (mime_type, "image/");
1032
1033 if (info) {
1034 g_object_unref (info);
1035 }
1036 g_object_unref (location);
1037
1038 return result;
1039 }
1040
1041
1042 static void
1043 nautilus_canvas_container_receive_dropped_icons (NautilusCanvasContainer *container,
1044 GdkDragContext *context,
1045 int x, int y)
1046 {
1047 char *drop_target, *container_uri;
1048 gboolean local_move_only;
1049 double world_x, world_y;
1050 gboolean icon_hit;
1051 GdkDragAction action, real_action;
1052 NautilusDragSelectionItem *selected_item;
1053
1054 drop_target = NULL;
1055
1056 if (container->details->dnd_info->drag_info.selection_list == NULL) {
1057 return;
1058 }
1059
1060 real_action = gdk_drag_context_get_selected_action (context);
1061
1062 if (real_action == GDK_ACTION_ASK) {
1063 /* FIXME bugzilla.gnome.org 42485: This belongs in FMDirectoryView, not here. */
1064 /* Check for special case items in selection list */
1065 if (nautilus_drag_selection_includes_special_link (container->details->dnd_info->drag_info.selection_list)) {
1066 /* We only want to move the trash */
1067 action = GDK_ACTION_MOVE;
1068 } else {
1069 action = GDK_ACTION_MOVE | GDK_ACTION_COPY | GDK_ACTION_LINK;
1070 container_uri = get_container_uri (container);
1071
1072 if (eel_uri_is_desktop (container_uri) &&
1073 selection_is_image_file (container->details->dnd_info->drag_info.selection_list)) {
1074 action |= NAUTILUS_DND_ACTION_SET_AS_BACKGROUND;
1075 }
1076
1077 g_free (container_uri);
1078 }
1079 real_action = nautilus_drag_drop_action_ask
1080 (GTK_WIDGET (container), action);
1081 }
1082
1083 if (real_action == (GdkDragAction) NAUTILUS_DND_ACTION_SET_AS_BACKGROUND) {
1084 NautilusDesktopBackground *background;
1085
1086 background = nautilus_desktop_background_new (container);
1087 selected_item = container->details->dnd_info->drag_info.selection_list->data;
1088
1089 nautilus_desktop_background_receive_dropped_background_image (background,
1090 selected_item->uri);
1091
1092 g_object_unref (background);
1093
1094 return;
1095 }
1096
1097 if (real_action > 0) {
1098 eel_canvas_window_to_world (EEL_CANVAS (container),
1099 x + gtk_adjustment_get_value (gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (container))),
1100 y + gtk_adjustment_get_value (gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (container))),
1101 &world_x, &world_y);
1102
1103 drop_target = nautilus_canvas_container_find_drop_target (container,
1104 context, x, y, &icon_hit, FALSE);
1105
1106 local_move_only = FALSE;
1107 if (!icon_hit && real_action == GDK_ACTION_MOVE) {
1108 /* we can just move the canvas positions if the move ended up in
1109 * the item's parent container
1110 */
1111 local_move_only = nautilus_canvas_container_selection_items_local
1112 (container, container->details->dnd_info->drag_info.selection_list);
1113 }
1114
1115 if (local_move_only) {
1116 handle_local_move (container, world_x, world_y);
1117 } else {
1118 handle_nonlocal_move (container, real_action, world_x, world_y, drop_target, icon_hit);
1119 }
1120 }
1121
1122 g_free (drop_target);
1123 nautilus_drag_destroy_selection_list (container->details->dnd_info->drag_info.selection_list);
1124 container->details->dnd_info->drag_info.selection_list = NULL;
1125 }
1126
1127 static void
1128 nautilus_canvas_container_get_drop_action (NautilusCanvasContainer *container,
1129 GdkDragContext *context,
1130 int x, int y,
1131 int *action)
1132 {
1133 char *drop_target;
1134 gboolean icon_hit;
1135 double world_x, world_y;
1136
1137 icon_hit = FALSE;
1138 if (!container->details->dnd_info->drag_info.got_drop_data_type) {
1139 /* drag_data_received_callback didn't get called yet */
1140 return;
1141 }
1142
1143 /* find out if we're over an canvas */
1144 canvas_widget_to_world (EEL_CANVAS (container), x, y, &world_x, &world_y);
1145 *action = 0;
1146
1147 drop_target = nautilus_canvas_container_find_drop_target (container,
1148 context, x, y, &icon_hit, FALSE);
1149 if (drop_target == NULL) {
1150 return;
1151 }
1152
1153 /* case out on the type of object being dragged */
1154 switch (container->details->dnd_info->drag_info.data_type) {
1155 case NAUTILUS_ICON_DND_GNOME_ICON_LIST:
1156 if (container->details->dnd_info->drag_info.selection_list != NULL) {
1157 nautilus_drag_default_drop_action_for_icons (context, drop_target,
1158 container->details->dnd_info->drag_info.selection_list,
1159 action);
1160 }
1161 break;
1162 case NAUTILUS_ICON_DND_URI_LIST:
1163 *action = nautilus_drag_default_drop_action_for_uri_list (context, drop_target);
1164 break;
1165
1166 case NAUTILUS_ICON_DND_NETSCAPE_URL:
1167 *action = nautilus_drag_default_drop_action_for_netscape_url (context);
1168 break;
1169
1170 case NAUTILUS_ICON_DND_ROOTWINDOW_DROP:
1171 *action = gdk_drag_context_get_suggested_action (context);
1172 break;
1173
1174 case NAUTILUS_ICON_DND_TEXT:
1175 case NAUTILUS_ICON_DND_XDNDDIRECTSAVE:
1176 case NAUTILUS_ICON_DND_RAW:
1177 *action = GDK_ACTION_COPY;
1178 break;
1179 }
1180
1181 g_free (drop_target);
1182 }
1183
1184 static void
1185 set_drop_target (NautilusCanvasContainer *container,
1186 NautilusCanvasIcon *icon)
1187 {
1188 NautilusCanvasIcon *old_icon;
1189
1190 /* Check if current drop target changed, update icon drop
1191 * higlight if needed.
1192 */
1193 old_icon = container->details->drop_target;
1194 if (icon == old_icon) {
1195 return;
1196 }
1197
1198 /* Remember the new drop target for the next round. */
1199 container->details->drop_target = icon;
1200 nautilus_canvas_container_update_icon (container, old_icon);
1201 nautilus_canvas_container_update_icon (container, icon);
1202 }
1203
1204 static void
1205 nautilus_canvas_dnd_update_drop_target (NautilusCanvasContainer *container,
1206 GdkDragContext *context,
1207 int x, int y)
1208 {
1209 NautilusCanvasIcon *icon;
1210 NautilusFile *file;
1211 double world_x, world_y;
1212 char *uri;
1213
1214 g_assert (NAUTILUS_IS_CANVAS_CONTAINER (container));
1215
1216 canvas_widget_to_world (EEL_CANVAS (container), x, y, &world_x, &world_y);
1217
1218 /* Find the item we hit with our drop, if any. */
1219 icon = nautilus_canvas_container_item_at (container, world_x, world_y);
1220
1221 /* FIXME bugzilla.gnome.org 42485:
1222 * These "can_accept_items" tests need to be done by
1223 * the canvas view, not here. This file is not supposed to know
1224 * that the target is a file.
1225 */
1226
1227 /* Find if target canvas accepts our drop. */
1228 if (icon != NULL) {
1229 uri = nautilus_canvas_container_get_icon_uri (container, icon);
1230 file = nautilus_file_get_by_uri (uri);
1231 g_free (uri);
1232
1233 if (!nautilus_drag_can_accept_info (file,
1234 container->details->dnd_info->drag_info.data_type,
1235 container->details->dnd_info->drag_info.selection_list)) {
1236 icon = NULL;
1237 }
1238
1239 nautilus_file_unref (file);
1240 }
1241
1242 set_drop_target (container, icon);
1243 }
1244
1245 static void
1246 nautilus_canvas_container_free_drag_data (NautilusCanvasContainer *container)
1247 {
1248 NautilusCanvasDndInfo *dnd_info;
1249
1250 dnd_info = container->details->dnd_info;
1251
1252 dnd_info->drag_info.got_drop_data_type = FALSE;
1253
1254 if (dnd_info->shadow != NULL) {
1255 eel_canvas_item_destroy (dnd_info->shadow);
1256 dnd_info->shadow = NULL;
1257 }
1258
1259 if (dnd_info->drag_info.selection_data != NULL) {
1260 gtk_selection_data_free (dnd_info->drag_info.selection_data);
1261 dnd_info->drag_info.selection_data = NULL;
1262 }
1263
1264 if (dnd_info->drag_info.direct_save_uri != NULL) {
1265 g_free (dnd_info->drag_info.direct_save_uri);
1266 dnd_info->drag_info.direct_save_uri = NULL;
1267 }
1268 }
1269
1270 static void
1271 drag_leave_callback (GtkWidget *widget,
1272 GdkDragContext *context,
1273 guint32 time,
1274 gpointer data)
1275 {
1276 NautilusCanvasDndInfo *dnd_info;
1277
1278 dnd_info = NAUTILUS_CANVAS_CONTAINER (widget)->details->dnd_info;
1279
1280 if (dnd_info->shadow != NULL)
1281 eel_canvas_item_hide (dnd_info->shadow);
1282
1283 stop_dnd_highlight (widget);
1284
1285 set_drop_target (NAUTILUS_CANVAS_CONTAINER (widget), NULL);
1286 stop_auto_scroll (NAUTILUS_CANVAS_CONTAINER (widget));
1287 nautilus_canvas_container_free_drag_data(NAUTILUS_CANVAS_CONTAINER (widget));
1288 }
1289
1290 static void
1291 drag_begin_callback (GtkWidget *widget,
1292 GdkDragContext *context,
1293 gpointer data)
1294 {
1295 NautilusCanvasContainer *container;
1296 cairo_surface_t *surface;
1297 double x1, y1, x2, y2, winx, winy;
1298 int x_offset, y_offset;
1299 int start_x, start_y;
1300
1301 container = NAUTILUS_CANVAS_CONTAINER (widget);
1302
1303 start_x = container->details->dnd_info->drag_info.start_x +
1304 gtk_adjustment_get_value (gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (container)));
1305 start_y = container->details->dnd_info->drag_info.start_y +
1306 gtk_adjustment_get_value (gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (container)));
1307
1308 /* create a pixmap and mask to drag with */
1309 surface = nautilus_canvas_item_get_drag_surface (container->details->drag_icon->item);
1310
1311 /* compute the image's offset */
1312 eel_canvas_item_get_bounds (EEL_CANVAS_ITEM (container->details->drag_icon->item),
1313 &x1, &y1, &x2, &y2);
1314 eel_canvas_world_to_window (EEL_CANVAS (container),
1315 x1, y1, &winx, &winy);
1316 x_offset = start_x - winx;
1317 y_offset = start_y - winy;
1318
1319 cairo_surface_set_device_offset (surface, -x_offset, -y_offset);
1320 gtk_drag_set_icon_surface (context, surface);
1321 cairo_surface_destroy (surface);
1322 }
1323
1324 void
1325 nautilus_canvas_dnd_begin_drag (NautilusCanvasContainer *container,
1326 GdkDragAction actions,
1327 int button,
1328 GdkEventMotion *event,
1329 int start_x,
1330 int start_y)
1331 {
1332 NautilusCanvasDndInfo *dnd_info;
1333
1334 g_return_if_fail (NAUTILUS_IS_CANVAS_CONTAINER (container));
1335 g_return_if_fail (event != NULL);
1336
1337 dnd_info = container->details->dnd_info;
1338 g_return_if_fail (dnd_info != NULL);
1339
1340 /* Notice that the event is in bin_window coordinates, because of
1341 the way the canvas handles events.
1342 */
1343 dnd_info->drag_info.start_x = start_x -
1344 gtk_adjustment_get_value (gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (container)));
1345 dnd_info->drag_info.start_y = start_y -
1346 gtk_adjustment_get_value (gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (container)));
1347
1348 /* start the drag */
1349 gtk_drag_begin (GTK_WIDGET (container),
1350 dnd_info->drag_info.target_list,
1351 actions,
1352 button,
1353 (GdkEvent *) event);
1354 }
1355
1356 static gboolean
1357 drag_highlight_draw (GtkWidget *widget,
1358 cairo_t *cr,
1359 gpointer user_data)
1360 {
1361 gint width, height;
1362 GdkWindow *window;
1363 GtkStyleContext *style;
1364
1365 window = gtk_widget_get_window (widget);
1366 width = gdk_window_get_width (window);
1367 height = gdk_window_get_height (window);
1368
1369 style = gtk_widget_get_style_context (widget);
1370
1371 gtk_style_context_save (style);
1372 gtk_style_context_add_class (style, GTK_STYLE_CLASS_DND);
1373 gtk_style_context_set_state (style, GTK_STATE_FLAG_FOCUSED);
1374
1375 gtk_render_frame (style,
1376 cr,
1377 0, 0, width, height);
1378
1379 gtk_style_context_restore (style);
1380
1381 return FALSE;
1382 }
1383
1384 /* Queue a redraw of the dnd highlight rect */
1385 static void
1386 dnd_highlight_queue_redraw (GtkWidget *widget)
1387 {
1388 NautilusCanvasDndInfo *dnd_info;
1389 int width, height;
1390 GtkAllocation allocation;
1391
1392 dnd_info = NAUTILUS_CANVAS_CONTAINER (widget)->details->dnd_info;
1393
1394 if (!dnd_info->highlighted) {
1395 return;
1396 }
1397
1398 gtk_widget_get_allocation (widget, &allocation);
1399 width = allocation.width;
1400 height = allocation.height;
1401
1402 /* we don't know how wide the shadow is exactly,
1403 * so we expose a 10-pixel wide border
1404 */
1405 gtk_widget_queue_draw_area (widget,
1406 0, 0,
1407 width, 10);
1408 gtk_widget_queue_draw_area (widget,
1409 0, 0,
1410 10, height);
1411 gtk_widget_queue_draw_area (widget,
1412 0, height - 10,
1413 width, 10);
1414 gtk_widget_queue_draw_area (widget,
1415 width - 10, 0,
1416 10, height);
1417 }
1418
1419 static void
1420 start_dnd_highlight (GtkWidget *widget)
1421 {
1422 NautilusCanvasDndInfo *dnd_info;
1423 GtkWidget *toplevel;
1424
1425 dnd_info = NAUTILUS_CANVAS_CONTAINER (widget)->details->dnd_info;
1426
1427 toplevel = gtk_widget_get_toplevel (widget);
1428 if (toplevel != NULL &&
1429 g_object_get_data (G_OBJECT (toplevel), "is_desktop_window")) {
1430 return;
1431 }
1432
1433 if (!dnd_info->highlighted) {
1434 dnd_info->highlighted = TRUE;
1435 g_signal_connect_after (widget, "draw",
1436 G_CALLBACK (drag_highlight_draw),
1437 NULL);
1438 dnd_highlight_queue_redraw (widget);
1439 }
1440 }
1441
1442 static void
1443 stop_dnd_highlight (GtkWidget *widget)
1444 {
1445 NautilusCanvasDndInfo *dnd_info;
1446
1447 dnd_info = NAUTILUS_CANVAS_CONTAINER (widget)->details->dnd_info;
1448
1449 if (dnd_info->highlighted) {
1450 g_signal_handlers_disconnect_by_func (widget,
1451 drag_highlight_draw,
1452 NULL);
1453 dnd_highlight_queue_redraw (widget);
1454 dnd_info->highlighted = FALSE;
1455 }
1456 }
1457
1458 static gboolean
1459 drag_motion_callback (GtkWidget *widget,
1460 GdkDragContext *context,
1461 int x, int y,
1462 guint32 time)
1463 {
1464 int action;
1465
1466 nautilus_canvas_container_ensure_drag_data (NAUTILUS_CANVAS_CONTAINER (widget), context, time);
1467 nautilus_canvas_container_position_shadow (NAUTILUS_CANVAS_CONTAINER (widget), x, y);
1468 nautilus_canvas_dnd_update_drop_target (NAUTILUS_CANVAS_CONTAINER (widget), context, x, y);
1469 set_up_auto_scroll_if_needed (NAUTILUS_CANVAS_CONTAINER (widget));
1470 /* Find out what the drop actions are based on our drag selection and
1471 * the drop target.
1472 */
1473 action = 0;
1474 nautilus_canvas_container_get_drop_action (NAUTILUS_CANVAS_CONTAINER (widget), context, x, y,
1475 &action);
1476 if (action != 0) {
1477 start_dnd_highlight (widget);
1478 }
1479
1480 gdk_drag_status (context, action, time);
1481
1482 return TRUE;
1483 }
1484
1485 static gboolean
1486 drag_drop_callback (GtkWidget *widget,
1487 GdkDragContext *context,
1488 int x,
1489 int y,
1490 guint32 time,
1491 gpointer data)
1492 {
1493 NautilusCanvasDndInfo *dnd_info;
1494
1495 dnd_info = NAUTILUS_CANVAS_CONTAINER (widget)->details->dnd_info;
1496
1497 /* tell the drag_data_received callback that
1498 the drop occured and that it can actually
1499 process the actions.
1500 make sure it is going to be called at least once.
1501 */
1502 dnd_info->drag_info.drop_occured = TRUE;
1503
1504 get_data_on_first_target_we_support (widget, context, time, x, y);
1505
1506 return TRUE;
1507 }
1508
1509 void
1510 nautilus_canvas_dnd_end_drag (NautilusCanvasContainer *container)
1511 {
1512 NautilusCanvasDndInfo *dnd_info;
1513
1514 g_return_if_fail (NAUTILUS_IS_CANVAS_CONTAINER (container));
1515
1516 dnd_info = container->details->dnd_info;
1517 g_return_if_fail (dnd_info != NULL);
1518 stop_auto_scroll (container);
1519 /* Do nothing.
1520 * Can that possibly be right?
1521 */
1522 }
1523
1524 /** this callback is called in 2 cases.
1525 It is called upon drag_motion events to get the actual data
1526 In that case, it just makes sure it gets the data.
1527 It is called upon drop_drop events to execute the actual
1528 actions on the received action. In that case, it actually first makes sure
1529 that we have got the data then processes it.
1530 */
1531
1532 static void
1533 drag_data_received_callback (GtkWidget *widget,
1534 GdkDragContext *context,
1535 int x,
1536 int y,
1537 GtkSelectionData *data,
1538 guint info,
1539 guint32 time,
1540 gpointer user_data)
1541 {
1542 NautilusDragInfo *drag_info;
1543 guchar *tmp;
1544 const guchar *tmp_raw;
1545 int length;
1546 gboolean success;
1547
1548 drag_info = &(NAUTILUS_CANVAS_CONTAINER (widget)->details->dnd_info->drag_info);
1549
1550 drag_info->got_drop_data_type = TRUE;
1551 drag_info->data_type = info;
1552
1553 switch (info) {
1554 case NAUTILUS_ICON_DND_GNOME_ICON_LIST:
1555 nautilus_canvas_container_dropped_canvas_feedback (widget, data, x, y);
1556 break;
1557 case NAUTILUS_ICON_DND_URI_LIST:
1558 case NAUTILUS_ICON_DND_TEXT:
1559 case NAUTILUS_ICON_DND_XDNDDIRECTSAVE:
1560 case NAUTILUS_ICON_DND_RAW:
1561 /* Save the data so we can do the actual work on drop. */
1562 if (drag_info->selection_data != NULL) {
1563 gtk_selection_data_free (drag_info->selection_data);
1564 }
1565 drag_info->selection_data = gtk_selection_data_copy (data);
1566 break;
1567
1568 /* Netscape keeps sending us the data, even though we accept the first drag */
1569 case NAUTILUS_ICON_DND_NETSCAPE_URL:
1570 if (drag_info->selection_data != NULL) {
1571 gtk_selection_data_free (drag_info->selection_data);
1572 drag_info->selection_data = gtk_selection_data_copy (data);
1573 }
1574 break;
1575 case NAUTILUS_ICON_DND_ROOTWINDOW_DROP:
1576 /* Do nothing, this won't even happen, since we don't want to call get_data twice */
1577 break;
1578 }
1579
1580 /* this is the second use case of this callback.
1581 * we have to do the actual work for the drop.
1582 */
1583 if (drag_info->drop_occured) {
1584
1585 success = FALSE;
1586 switch (info) {
1587 case NAUTILUS_ICON_DND_GNOME_ICON_LIST:
1588 nautilus_canvas_container_receive_dropped_icons
1589 (NAUTILUS_CANVAS_CONTAINER (widget),
1590 context, x, y);
1591 break;
1592 case NAUTILUS_ICON_DND_NETSCAPE_URL:
1593 receive_dropped_netscape_url
1594 (NAUTILUS_CANVAS_CONTAINER (widget),
1595 (char *) gtk_selection_data_get_data (data), context, x, y);
1596 success = TRUE;
1597 break;
1598 case NAUTILUS_ICON_DND_URI_LIST:
1599 receive_dropped_uri_list
1600 (NAUTILUS_CANVAS_CONTAINER (widget),
1601 (char *) gtk_selection_data_get_data (data), context, x, y);
1602 success = TRUE;
1603 break;
1604 case NAUTILUS_ICON_DND_TEXT:
1605 tmp = gtk_selection_data_get_text (data);
1606 receive_dropped_text
1607 (NAUTILUS_CANVAS_CONTAINER (widget),
1608 (char *) tmp, context, x, y);
1609 success = TRUE;
1610 g_free (tmp);
1611 break;
1612 case NAUTILUS_ICON_DND_RAW:
1613 length = gtk_selection_data_get_length (data);
1614 tmp_raw = gtk_selection_data_get_data (data);
1615 receive_dropped_raw
1616 (NAUTILUS_CANVAS_CONTAINER (widget),
1617 (const gchar *) tmp_raw, length, drag_info->direct_save_uri,
1618 context, x, y);
1619 success = TRUE;
1620 break;
1621 case NAUTILUS_ICON_DND_ROOTWINDOW_DROP:
1622 /* Do nothing, everything is done by the sender */
1623 break;
1624 case NAUTILUS_ICON_DND_XDNDDIRECTSAVE:
1625 {
1626 const guchar *selection_data;
1627 gint selection_length;
1628 gint selection_format;
1629
1630 selection_data = gtk_selection_data_get_data (drag_info->selection_data);
1631 selection_length = gtk_selection_data_get_length (drag_info->selection_data);
1632 selection_format = gtk_selection_data_get_format (drag_info->selection_data);
1633
1634 if (selection_format == 8 &&
1635 selection_length == 1 &&
1636 selection_data[0] == 'F') {
1637 gtk_drag_get_data (widget, context,
1638 gdk_atom_intern (NAUTILUS_ICON_DND_RAW_TYPE,
1639 FALSE),
1640 time);
1641 return;
1642 } else if (selection_format == 8 &&
1643 selection_length == 1 &&
1644 selection_data[0] == 'F' &&
1645 drag_info->direct_save_uri != NULL) {
1646 GdkPoint p;
1647 GFile *location;
1648
1649 location = g_file_new_for_uri (drag_info->direct_save_uri);
1650
1651 nautilus_file_changes_queue_file_added (location);
1652 p.x = x; p.y = y;
1653 nautilus_file_changes_queue_schedule_position_set (
1654 location,
1655 p,
1656 gdk_screen_get_number (
1657 gtk_widget_get_screen (widget)));
1658 g_object_unref (location);
1659 nautilus_file_changes_consume_changes (TRUE);
1660 success = TRUE;
1661 }
1662 break;
1663 } /* NAUTILUS_ICON_DND_XDNDDIRECTSAVE */
1664 }
1665 gtk_drag_finish (context, success, FALSE, time);
1666
1667 nautilus_canvas_container_free_drag_data (NAUTILUS_CANVAS_CONTAINER (widget));
1668
1669 set_drop_target (NAUTILUS_CANVAS_CONTAINER (widget), NULL);
1670
1671 /* reinitialise it for the next dnd */
1672 drag_info->drop_occured = FALSE;
1673 }
1674
1675 }
1676
1677 void
1678 nautilus_canvas_dnd_init (NautilusCanvasContainer *container)
1679 {
1680 GtkTargetList *targets;
1681 int n_elements;
1682
1683 g_return_if_fail (container != NULL);
1684 g_return_if_fail (NAUTILUS_IS_CANVAS_CONTAINER (container));
1685
1686
1687 container->details->dnd_info = g_new0 (NautilusCanvasDndInfo, 1);
1688 nautilus_drag_init (&container->details->dnd_info->drag_info,
1689 drag_types, G_N_ELEMENTS (drag_types), TRUE);
1690
1691 /* Set up the widget as a drag destination.
1692 * (But not a source, as drags starting from this widget will be
1693 * implemented by dealing with events manually.)
1694 */
1695 n_elements = G_N_ELEMENTS (drop_types);
1696 if (!nautilus_canvas_container_get_is_desktop (container)) {
1697 /* Don't set up rootwindow drop */
1698 n_elements -= 1;
1699 }
1700 gtk_drag_dest_set (GTK_WIDGET (container),
1701 0,
1702 drop_types, n_elements,
1703 GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK | GDK_ACTION_ASK);
1704
1705 targets = gtk_drag_dest_get_target_list (GTK_WIDGET (container));
1706 gtk_target_list_add_text_targets (targets, NAUTILUS_ICON_DND_TEXT);
1707
1708
1709 /* Messages for outgoing drag. */
1710 g_signal_connect (container, "drag_begin",
1711 G_CALLBACK (drag_begin_callback), NULL);
1712 g_signal_connect (container, "drag_data_get",
1713 G_CALLBACK (drag_data_get_callback), NULL);
1714 g_signal_connect (container, "drag_end",
1715 G_CALLBACK (drag_end_callback), NULL);
1716
1717 /* Messages for incoming drag. */
1718 g_signal_connect (container, "drag_data_received",
1719 G_CALLBACK (drag_data_received_callback), NULL);
1720 g_signal_connect (container, "drag_motion",
1721 G_CALLBACK (drag_motion_callback), NULL);
1722 g_signal_connect (container, "drag_drop",
1723 G_CALLBACK (drag_drop_callback), NULL);
1724 g_signal_connect (container, "drag_leave",
1725 G_CALLBACK (drag_leave_callback), NULL);
1726 }
1727
1728 void
1729 nautilus_canvas_dnd_fini (NautilusCanvasContainer *container)
1730 {
1731 g_return_if_fail (NAUTILUS_IS_CANVAS_CONTAINER (container));
1732
1733 if (container->details->dnd_info != NULL) {
1734 stop_auto_scroll (container);
1735
1736 nautilus_drag_finalize (&container->details->dnd_info->drag_info);
1737 container->details->dnd_info = NULL;
1738 }
1739 }