nautilus-canvas-dnd.c 51 KB
Newer Older
Ettore Perazzoli's avatar
Ettore Perazzoli committed
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.
Ettore Perazzoli's avatar
Ettore Perazzoli committed
4

Elliot Lee's avatar
Elliot Lee committed
5
   Copyright (C) 1999, 2000 Free Software Foundation
6 7
   Copyright (C) 2000 Eazel, Inc.
   
Ettore Perazzoli's avatar
Ettore Perazzoli committed
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
   The Gnome Library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Library General Public License as
   published by the Free Software Foundation; either version 2 of the
   License, or (at your option) any later version.

   The Gnome Library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Library General Public License for more details.

   You should have received a copy of the GNU Library General Public
   License along with the Gnome Library; see the file COPYING.LIB.  If not,
   write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
   Boston, MA 02111-1307, USA.

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 30
	    

   XDS support: Benedikt Meurer <benny@xfce.org> (adapted by Amos Brocco <amos.brocco@unifr.ch>)
				
Ettore Perazzoli's avatar
Ettore Perazzoli committed
31 32
*/

33

34
#include <config.h>
35
#include <math.h>
36
#include "nautilus-canvas-dnd.h"
Ettore Perazzoli's avatar
Ettore Perazzoli committed
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"
Ramiro Estrugo's avatar
Ramiro Estrugo committed
43 44 45 46 47 48
#include <eel/eel-glib-extensions.h>
#include <eel/eel-gnome-extensions.h>
#include <eel/eel-graphic-effects.h>
#include <eel/eel-gtk-extensions.h>
#include <eel/eel-stock-dialogs.h>
#include <eel/eel-string.h>
49
#include <eel/eel-vfs-extensions.h>
50 51
#include <gdk/gdkkeysyms.h>
#include <gdk/gdkx.h>
52
#include <gtk/gtk.h>
53
#include <glib/gi18n.h>
54

55
#include <libnautilus-private/nautilus-desktop-background.h>
Alexander Larsson's avatar
Alexander Larsson committed
56
#include <libnautilus-private/nautilus-file-utilities.h>
57
#include <libnautilus-private/nautilus-file-changes-queue.h>
58 59
#include <stdio.h>
#include <string.h>
60

61
#define DEBUG_FLAG NAUTILUS_DEBUG_CANVAS_CONTAINER
62 63
#include "nautilus-debug.h"

64
static const GtkTargetEntry drag_types [] = {
65 66
	{ NAUTILUS_ICON_DND_GNOME_ICON_LIST_TYPE, 0, NAUTILUS_ICON_DND_GNOME_ICON_LIST },
	{ NAUTILUS_ICON_DND_URI_LIST_TYPE, 0, NAUTILUS_ICON_DND_URI_LIST },
Ettore Perazzoli's avatar
Ettore Perazzoli committed
67 68
};

69
static const GtkTargetEntry drop_types [] = {
70
	{ NAUTILUS_ICON_DND_GNOME_ICON_LIST_TYPE, 0, NAUTILUS_ICON_DND_GNOME_ICON_LIST },
71 72
	/* prefer "_NETSCAPE_URL" over "text/uri-list" to satisfy web browsers. */
	{ 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 */
James Dietrich's avatar
James Dietrich committed
75
	{ NAUTILUS_ICON_DND_RAW_TYPE, 0, NAUTILUS_ICON_DND_RAW },
76 77
	/* Must be last: */
	{ NAUTILUS_ICON_DND_ROOTWINDOW_DROP_TYPE,  0, NAUTILUS_ICON_DND_ROOTWINDOW_DROP }
Ettore Perazzoli's avatar
Ettore Perazzoli committed
78
};
79 80
static void     stop_dnd_highlight         (GtkWidget      *widget);
static void     dnd_highlight_queue_redraw (GtkWidget      *widget);
Ettore Perazzoli's avatar
Ettore Perazzoli committed
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 88
							int x, int y, gboolean *icon_hit,
							gboolean rewrite_desktop);
89

90
static EelCanvasItem *
91
create_selection_shadow (NautilusCanvasContainer *container,
Ettore Perazzoli's avatar
Ettore Perazzoli committed
92 93
			 GList *list)
{
94 95
	EelCanvasGroup *group;
	EelCanvas *canvas;
96 97
	int max_x, max_y;
	int min_x, min_y;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
98
	GList *p;
99
	GtkAllocation allocation;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
100

101 102 103
	if (list == NULL) {
		return NULL;
	}
Ettore Perazzoli's avatar
Ettore Perazzoli committed
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);
Ettore Perazzoli's avatar
Ettore Perazzoli committed
112 113 114 115 116

	/* Creating a big set of rectangles in the canvas can be expensive, so
           we try to be smart and only create the maximum number of rectangles
           that we will need, in the vertical/horizontal directions.  */

117
	max_x = allocation.width;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
118 119
	min_x = -max_x;

120
	max_y = allocation.height;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
121 122 123 124
	min_y = -max_y;

	/* Create a group, so that it's easier to move all the items around at
           once.  */
125 126 127
	group = EEL_CANVAS_GROUP
		(eel_canvas_item_new (EEL_CANVAS_GROUP (canvas->root),
					eel_canvas_group_get_type (),
Ettore Perazzoli's avatar
Ettore Perazzoli committed
128 129 130
					NULL));
	
	for (p = list; p != NULL; p = p->next) {
131
		NautilusDragSelectionItem *item;
132
		int x1, y1, x2, y2;
133
		GdkRGBA black = { 0, 0, 0, 1 };
Ettore Perazzoli's avatar
Ettore Perazzoli committed
134 135 136

		item = p->data;

137
		if (!item->got_icon_position) {
138
			continue;
139
		}
140 141 142 143 144

		x1 = item->icon_x;
		y1 = item->icon_y;
		x2 = x1 + item->icon_width;
		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
Ettore Perazzoli's avatar
Ettore Perazzoli committed
148
				(group,
149
				 NAUTILUS_TYPE_SELECTION_CANVAS_ITEM,
150 151 152 153
				 "x1", (double) x1,
				 "y1", (double) y1,
				 "x2", (double) x2,
				 "y2", (double) y2,
154
				 "outline-color-rgba", &black,
155
				 "outline-stippling", TRUE,
Ettore Perazzoli's avatar
Ettore Perazzoli committed
156 157 158 159
				 "width_pixels", 1,
				 NULL);
	}

160
	return EEL_CANVAS_ITEM (group);
Ettore Perazzoli's avatar
Ettore Perazzoli committed
161 162
}

163 164 165
/* Set the affine instead of the x and y position.
 * Simple, and setting x and y was broken at one point.
 */
Ettore Perazzoli's avatar
Ettore Perazzoli committed
166
static void
167
set_shadow_position (EelCanvasItem *shadow,
168
		     double x, double y)
Ettore Perazzoli's avatar
Ettore Perazzoli committed
169
{
170 171 172
	eel_canvas_item_set (shadow,
			     "x", x, "y", y,
			     NULL);
Ettore Perazzoli's avatar
Ettore Perazzoli committed
173 174
}

175

176
/* Source-side handling of the drag. */
Ettore Perazzoli's avatar
Ettore Perazzoli committed
177

178 179 180
/* iteration glue struct */
typedef struct {
	gpointer iterator_context;
181
	NautilusDragEachSelectedItemDataGet iteratee;
182
	gpointer iteratee_data;
183
} CanvasGetDataBinderContext;
184

185 186
static void
canvas_rect_world_to_widget (EelCanvas *canvas,
Alexander Larsson's avatar
Alexander Larsson committed
187 188
			     EelDRect *world_rect,
			     EelIRect *widget_rect)
189
{
Alexander Larsson's avatar
Alexander Larsson committed
190
	EelDRect window_rect;
191 192 193 194
	GtkAdjustment *hadj, *vadj;

	hadj = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (canvas));
	vadj = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (canvas));
195 196 197 198 199 200 201
	
	eel_canvas_world_to_window (canvas,
				    world_rect->x0, world_rect->y0,
				    &window_rect.x0, &window_rect.y0);
	eel_canvas_world_to_window (canvas,
				    world_rect->x1, world_rect->y1,
				    &window_rect.x1, &window_rect.y1);
202 203 204 205
	widget_rect->x0 = (int) window_rect.x0 - gtk_adjustment_get_value (hadj);
	widget_rect->y0 = (int) window_rect.y0 - gtk_adjustment_get_value (vadj);
	widget_rect->x1 = (int) window_rect.x1 - gtk_adjustment_get_value (hadj);
	widget_rect->y1 = (int) window_rect.y1 - gtk_adjustment_get_value (vadj);
206 207 208 209 210 211 212 213
}

static void
canvas_widget_to_world (EelCanvas *canvas,
			double widget_x, double widget_y,
			double *world_x, double *world_y)
{
	eel_canvas_window_to_world (canvas,
214 215
				    widget_x + gtk_adjustment_get_value (gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (canvas))),
				    widget_y + gtk_adjustment_get_value (gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (canvas))),
216 217 218
				    world_x, world_y);
}

219
static gboolean
220
icon_get_data_binder (NautilusCanvasIcon *icon, gpointer data)
Ettore Perazzoli's avatar
Ettore Perazzoli committed
221
{
222
	CanvasGetDataBinderContext *context;
Alexander Larsson's avatar
Alexander Larsson committed
223 224
	EelDRect world_rect;
	EelIRect widget_rect;
225
	char *uri;
226
	NautilusCanvasContainer *container;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
227

228
	context = (CanvasGetDataBinderContext *)data;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
229

230
	g_assert (NAUTILUS_IS_CANVAS_CONTAINER (context->iterator_context));
Ettore Perazzoli's avatar
Ettore Perazzoli committed
231

232
	container = NAUTILUS_CANVAS_CONTAINER (context->iterator_context);
Ettore Perazzoli's avatar
Ettore Perazzoli committed
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 240 241
	if (uri == NULL) {
		g_warning ("no URI for one of the iterated icons");
		return TRUE;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
242 243
	}

Alexander Larsson's avatar
Alexander Larsson committed
244
	widget_rect = eel_irect_offset_by (widget_rect, 
245 246 247
		- container->details->dnd_info->drag_info.start_x,
		- container->details->dnd_info->drag_info.start_y);

Alexander Larsson's avatar
Alexander Larsson committed
248
	widget_rect = eel_irect_scale_by (widget_rect, 
249
		1 / EEL_CANVAS (container)->pixels_per_unit);
250
	
251 252
	/* pass the uri, mouse-relative x/y and icon width/height */
	context->iteratee (uri, 
Darin Adler's avatar
Darin Adler committed
253 254 255 256
			   (int) widget_rect.x0,
			   (int) widget_rect.y0,
			   widget_rect.x1 - widget_rect.x0,
			   widget_rect.y1 - widget_rect.y0,
257
			   context->iteratee_data);
Ettore Perazzoli's avatar
Ettore Perazzoli committed
258

259 260 261
	g_free (uri);

	return TRUE;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
262 263
}

264
/* Iterate over each selected icon in a NautilusCanvasContainer,
265 266
 * calling each_function on each.
 */
Ettore Perazzoli's avatar
Ettore Perazzoli committed
267
static void
268 269
nautilus_canvas_container_each_selected_icon (NautilusCanvasContainer *container,
	gboolean (*each_function) (NautilusCanvasIcon *, gpointer), gpointer data)
Ettore Perazzoli's avatar
Ettore Perazzoli committed
270 271
{
	GList *p;
272
	NautilusCanvasIcon *icon;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
273

274
	for (p = container->details->icons; p != NULL; p = p->next) {
Ettore Perazzoli's avatar
Ettore Perazzoli committed
275
		icon = p->data;
276
		if (!icon->is_selected) {
Ettore Perazzoli's avatar
Ettore Perazzoli committed
277
			continue;
278 279 280 281
		}
		if (!each_function (icon, data)) {
			return;
		}
Ettore Perazzoli's avatar
Ettore Perazzoli committed
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 288 289
 * values to the iteratee
 */
static void
290
each_icon_get_data_binder (NautilusDragEachSelectedItemDataGet iteratee, 
291 292
	gpointer iterator_context, gpointer data)
{
293 294
	CanvasGetDataBinderContext context;
	NautilusCanvasContainer *container;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
295

296 297
	g_assert (NAUTILUS_IS_CANVAS_CONTAINER (iterator_context));
	container = NAUTILUS_CANVAS_CONTAINER (iterator_context);
Ettore Perazzoli's avatar
Ettore Perazzoli committed
298

299 300 301
	context.iterator_context = iterator_context;
	context.iteratee = iteratee;
	context.iteratee_data = data;
302
	nautilus_canvas_container_each_selected_icon (container, icon_get_data_binder, &context);
Ettore Perazzoli's avatar
Ettore Perazzoli committed
303 304
}

305
/* Called when the data for drag&drop is needed */
Ettore Perazzoli's avatar
Ettore Perazzoli committed
306
static void
307 308 309 310 311 312
drag_data_get_callback (GtkWidget *widget,
			GdkDragContext *context,
			GtkSelectionData *selection_data,
			guint info,
			guint32 time,
			gpointer data)
Ettore Perazzoli's avatar
Ettore Perazzoli committed
313
{
314 315
	NautilusDragInfo *drag_info;

316
	g_assert (widget != NULL);
317
	g_assert (NAUTILUS_IS_CANVAS_CONTAINER (widget));
Ettore Perazzoli's avatar
Ettore Perazzoli committed
318 319
	g_return_if_fail (context != NULL);

320 321 322 323
	/* Call common function from nautilus-drag that set's up
	 * the selection data in the right format. Pass it means to
	 * iterate all the selected icons.
	 */
324 325
	drag_info = &(NAUTILUS_CANVAS_CONTAINER (widget)->details->dnd_info->drag_info);
	nautilus_drag_drag_data_get_from_cache (drag_info->selection_cache, context, selection_data, info, time);
Ettore Perazzoli's avatar
Ettore Perazzoli committed
326 327
}

328

Ettore Perazzoli's avatar
Ettore Perazzoli committed
329 330 331
/* Target-side handling of the drag.  */

static void
332
nautilus_canvas_container_position_shadow (NautilusCanvasContainer *container,
333
					 int x, int y)
334
{
335
	EelCanvasItem *shadow;
336 337
	double world_x, world_y;

338
	shadow = container->details->dnd_info->shadow;
339
	if (shadow == NULL) {
340
		return;
341
	}
342 343 344

	canvas_widget_to_world (EEL_CANVAS (container), x, y,
				&world_x, &world_y);
345

346
	set_shadow_position (shadow, world_x, world_y);
347
	eel_canvas_item_show (shadow);
348 349 350
}

static void
351
nautilus_canvas_container_dropped_canvas_feedback (GtkWidget *widget,
352 353
					       GtkSelectionData *data,
					       int x, int y)
Ettore Perazzoli's avatar
Ettore Perazzoli committed
354
{
355 356
	NautilusCanvasContainer *container;
	NautilusCanvasDndInfo *dnd_info;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
357

358
	container = NAUTILUS_CANVAS_CONTAINER (widget);
359
	dnd_info = container->details->dnd_info;
360
	
361
	/* Delete old selection list. */
362
	nautilus_drag_destroy_selection_list (dnd_info->drag_info.selection_list);
363
	dnd_info->drag_info.selection_list = NULL;
364

365
	/* Delete old shadow if any. */
366
	if (dnd_info->shadow != NULL) {
367
		/* FIXME bugzilla.gnome.org 42484: 
368
		 * Is a destroy really sufficient here? Who does the unref? */
369
		eel_canvas_item_destroy (dnd_info->shadow);
370
	}
371 372

	/* Build the selection list and the shadow. */
373
	dnd_info->drag_info.selection_list = nautilus_drag_build_selection_list (data);
374
	dnd_info->shadow = create_selection_shadow (container, dnd_info->drag_info.selection_list);
375
	nautilus_canvas_container_position_shadow (container, x, y);
Ettore Perazzoli's avatar
Ettore Perazzoli committed
376 377
}

378 379 380 381 382
static char *
get_direct_save_filename (GdkDragContext *context)
{
	guchar *prop_text;
	gint prop_len;
383 384

	if (!gdk_property_get (gdk_drag_context_get_source_window (context), gdk_atom_intern (NAUTILUS_ICON_DND_XDNDDIRECTSAVE_TYPE, FALSE),
385
			       gdk_atom_intern ("text/plain", FALSE), 0, 1024, FALSE, NULL, NULL,
386
			       &prop_len, &prop_text)) {
387 388
		return NULL;
	}
James Dietrich's avatar
James Dietrich committed
389

390 391 392 393 394 395 396
	/* Zero-terminate the string */
	prop_text = g_realloc (prop_text, prop_len + 1);
	prop_text[prop_len] = '\0';
	
	/* Verify that the file name provided by the source is valid */
	if (*prop_text == '\0' ||
	    strchr ((const gchar *) prop_text, G_DIR_SEPARATOR) != NULL) {
397
		DEBUG ("Invalid filename provided by XDS drag site");
398 399 400 401
		g_free (prop_text);
		return NULL;
	}
	
402
	return (gchar *) prop_text;
403 404 405 406 407
}

static void
set_direct_save_uri (GtkWidget *widget, GdkDragContext *context, NautilusDragInfo *drag_info, int x, int y)
{
Alexander Larsson's avatar
Alexander Larsson committed
408
	GFile *base, *child;
409 410 411 412 413 414 415 416 417
	char *filename, *drop_target;
	gchar *uri;
	
	drag_info->got_drop_data_type = TRUE;
	drag_info->data_type = NAUTILUS_ICON_DND_XDNDDIRECTSAVE;
	
	uri = NULL;
	
	filename = get_direct_save_filename (context);
418
	drop_target = nautilus_canvas_container_find_drop_target (NAUTILUS_CANVAS_CONTAINER (widget), 
419
								context, x, y, NULL, TRUE);
420 421 422 423 424 425 426 427
	
	if (drop_target && eel_uri_is_trash (drop_target)) {
		g_free (drop_target);
		drop_target = NULL; /* Cannot save to trash ...*/
	}
	
	if (filename != NULL && drop_target != NULL) {
		/* Resolve relative path */
Alexander Larsson's avatar
Alexander Larsson committed
428 429 430 431 432
		base = g_file_new_for_uri (drop_target);
		child = g_file_get_child (base, filename);
		uri = g_file_get_uri (child);
		g_object_unref (base);
		g_object_unref (child);
433 434
		
		/* Change the uri property */
435
		gdk_property_change (gdk_drag_context_get_source_window (context),
436 437 438 439 440 441 442 443 444 445 446 447
				     gdk_atom_intern (NAUTILUS_ICON_DND_XDNDDIRECTSAVE_TYPE, FALSE),
				     gdk_atom_intern ("text/plain", FALSE), 8,
				     GDK_PROP_MODE_REPLACE, (const guchar *) uri,
				     strlen (uri));
		
		drag_info->direct_save_uri = uri;
	} 
	
	g_free (filename);
	g_free (drop_target);
}

448
/* FIXME bugzilla.gnome.org 47445: Needs to become a shared function */
449
static void
450
get_data_on_first_target_we_support (GtkWidget *widget, GdkDragContext *context, guint32 time, int x, int y)
451
{
452
	GtkTargetList *list;
453
	GdkAtom target;
454

455
	if (drop_types_list == NULL) {
456
		drop_types_list = gtk_target_list_new (drop_types,
457
						       G_N_ELEMENTS (drop_types) - 1);
458 459 460
		gtk_target_list_add_text_targets (drop_types_list, NAUTILUS_ICON_DND_TEXT);
	}
	if (drop_types_list_root == NULL) {
461 462
		drop_types_list_root = gtk_target_list_new (drop_types,
							    G_N_ELEMENTS (drop_types));
463 464
		gtk_target_list_add_text_targets (drop_types_list_root, NAUTILUS_ICON_DND_TEXT);
	}
465
	
466
	if (nautilus_canvas_container_get_is_desktop (NAUTILUS_CANVAS_CONTAINER (widget))) {
467 468 469 470
		list = drop_types_list_root;
	} else {
		list = drop_types_list;
	}
471 472 473

	target = gtk_drag_dest_find_target (widget, context, list);
	if (target != GDK_NONE) {
474 475
		guint info;
		NautilusDragInfo *drag_info;
476
		gboolean found;
477

478
		drag_info = &(NAUTILUS_CANVAS_CONTAINER (widget)->details->dnd_info->drag_info);
479

480 481 482
		found = gtk_target_list_find (list, target, &info);
		g_assert (found);

483 484 485
		/* Don't get_data for destructive ops */
		if ((info == NAUTILUS_ICON_DND_ROOTWINDOW_DROP ||
		     info == NAUTILUS_ICON_DND_XDNDDIRECTSAVE) &&
486 487
		    !drag_info->drop_occured) {
			/* We can't call get_data here, because that would
488
			   make the source execute the rootwin action or the direct save */
489
			drag_info->got_drop_data_type = TRUE;
490
			drag_info->data_type = info;
491
		} else {
492 493 494
			if (info == NAUTILUS_ICON_DND_XDNDDIRECTSAVE) {
				set_direct_save_uri (widget, context, drag_info, x, y);
			}
495 496
			gtk_drag_get_data (GTK_WIDGET (widget), context,
					   target, time);
497 498 499 500
		}
	}
}

501
static void
502
nautilus_canvas_container_ensure_drag_data (NautilusCanvasContainer *container,
503 504
					  GdkDragContext *context,
					  guint32 time)
505
{
506
	NautilusCanvasDndInfo *dnd_info;
507

508
	dnd_info = container->details->dnd_info;
509

510
	if (!dnd_info->drag_info.got_drop_data_type) {
511
		get_data_on_first_target_we_support (GTK_WIDGET (container), context, time, 0, 0);
512
	}
513 514
}

Ettore Perazzoli's avatar
Ettore Perazzoli committed
515
static void
516 517 518
drag_end_callback (GtkWidget *widget,
		   GdkDragContext *context,
		   gpointer data)
Ettore Perazzoli's avatar
Ettore Perazzoli committed
519
{
520 521
	NautilusCanvasContainer *container;
	NautilusCanvasDndInfo *dnd_info;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
522

523
	container = NAUTILUS_CANVAS_CONTAINER (widget);
524
	dnd_info = container->details->dnd_info;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
525

526
	nautilus_drag_destroy_selection_list (dnd_info->drag_info.selection_list);
527
	dnd_info->drag_info.selection_list = NULL;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
528 529
}

530 531
static NautilusCanvasIcon *
nautilus_canvas_container_item_at (NautilusCanvasContainer *container,
532 533 534
				 int x, int y)
{
	GList *p;
535
	int size;
Alexander Larsson's avatar
Alexander Larsson committed
536 537
	EelDRect point;
	EelIRect canvas_point;
538

539 540 541 542
	/* build the hit-test rectangle. Base the size on the scale factor to ensure that it is
	 * non-empty even at the smallest scale factor
	 */
	
543
	size = MAX (1, 1 + (1 / EEL_CANVAS (container)->pixels_per_unit));
544 545
	point.x0 = x;
	point.y0 = y;
546 547
	point.x1 = x + size;
	point.y1 = y + size;
548 549

	for (p = container->details->icons; p != NULL; p = p->next) {
550
		NautilusCanvasIcon *icon;
551
		icon = p->data;
552
		
553 554 555 556 557 558 559 560 561 562
		eel_canvas_w2c (EEL_CANVAS (container),
				point.x0,
				point.y0,
				&canvas_point.x0,
				&canvas_point.y0);
		eel_canvas_w2c (EEL_CANVAS (container),
				point.x1,
				point.y1,
				&canvas_point.x1,
				&canvas_point.y1);
563
		if (nautilus_canvas_item_hit_test_rectangle (icon->item, canvas_point)) {
564 565 566
			return icon;
		}
	}
567
	
568 569 570 571
	return NULL;
}

static char *
572
get_container_uri (NautilusCanvasContainer *container)
573 574 575 576 577
{
	char *uri;

	/* get the URI associated with the container */
	uri = NULL;
578
	g_signal_emit_by_name (container, "get-container-uri", &uri);
579 580 581 582
	return uri;
}

static gboolean
583
nautilus_canvas_container_selection_items_local (NautilusCanvasContainer *container,
584
					       GList *items)
585 586 587 588 589 590 591 592 593
{
	char *container_uri_string;
	gboolean result;

	/* must have at least one item */
	g_assert (items);

	/* get the URI associated with the container */
	container_uri_string = get_container_uri (container);
594
	
Alexander Larsson's avatar
Alexander Larsson committed
595
	if (eel_uri_is_desktop (container_uri_string)) {
Alexander Larsson's avatar
Alexander Larsson committed
596
		result = nautilus_drag_items_on_desktop (items);
597
	} else {
598
		result = nautilus_drag_items_local (container_uri_string, items);
599
	}
600 601 602 603 604
	g_free (container_uri_string);
	
	return result;
}

605 606
/* handle dropped url */
static void
607
receive_dropped_netscape_url (NautilusCanvasContainer *container, const char *encoded_url, GdkDragContext *context, int x, int y)
608
{
609 610
	char *drop_target;

611 612 613 614
	if (encoded_url == NULL) {
		return;
	}

615
	drop_target = nautilus_canvas_container_find_drop_target (container, context, x, y, NULL, TRUE);
616

617
	g_signal_emit_by_name (container, "handle-netscape-url",
618
			       encoded_url,
619
			       drop_target,
620
			       gdk_drag_context_get_selected_action (context),
621
			       x, y);
622 623

	g_free (drop_target);
624 625
}

626 627
/* handle dropped uri list */
static void
628
receive_dropped_uri_list (NautilusCanvasContainer *container, const char *uri_list, GdkDragContext *context, int x, int y)
629
{	
630 631
	char *drop_target;

632 633 634
	if (uri_list == NULL) {
		return;
	}
635

636
	drop_target = nautilus_canvas_container_find_drop_target (container, context, x, y, NULL, TRUE);
637

638
	g_signal_emit_by_name (container, "handle-uri-list",
639
				 uri_list,
640
				 drop_target,
641
				 gdk_drag_context_get_selected_action (context),
642
				 x, y);
643 644

	g_free (drop_target);
645 646
}

647 648
/* handle dropped text */
static void
649
receive_dropped_text (NautilusCanvasContainer *container, const char *text, GdkDragContext *context, int x, int y)
650
{	
651 652
	char *drop_target;

653 654 655
	if (text == NULL) {
		return;
	}
656

657
	drop_target = nautilus_canvas_container_find_drop_target (container, context, x, y, NULL, TRUE);
658
	
659
	g_signal_emit_by_name (container, "handle-text",
660
			       text,
661
			       drop_target,
662
			       gdk_drag_context_get_selected_action (context),
663
			       x, y);
664 665

	g_free (drop_target);
666 667
}

James Dietrich's avatar
James Dietrich committed
668 669
/* handle dropped raw data */
static void
670
receive_dropped_raw (NautilusCanvasContainer *container, const char *raw_data, int length, const char *direct_save_uri, GdkDragContext *context, int x, int y)
James Dietrich's avatar
James Dietrich committed
671 672 673 674 675 676 677
{
	char *drop_target;

	if (raw_data == NULL) {
		return;
	}

678
	drop_target = nautilus_canvas_container_find_drop_target (container, context, x, y, NULL, TRUE);
James Dietrich's avatar
James Dietrich committed
679

680
	g_signal_emit_by_name (container, "handle-raw",
James Dietrich's avatar
James Dietrich committed
681 682 683 684
			       raw_data,
			       length,
			       drop_target,
			       direct_save_uri,
685
			       gdk_drag_context_get_selected_action (context),
James Dietrich's avatar
James Dietrich committed
686 687 688 689 690
			       x, y);

	g_free (drop_target);
}

Pavel Cisler's avatar
Pavel Cisler committed
691 692 693
static int
auto_scroll_timeout_callback (gpointer data)
{
694
	NautilusCanvasContainer *container;
Pavel Cisler's avatar
Pavel Cisler committed
695 696 697
	GtkWidget *widget;
	float x_scroll_delta, y_scroll_delta;
	GdkRectangle exposed_area;
698
	GtkAllocation allocation;
Pavel Cisler's avatar
Pavel Cisler committed
699

700
	g_assert (NAUTILUS_IS_CANVAS_CONTAINER (data));
Pavel Cisler's avatar
Pavel Cisler committed
701
	widget = GTK_WIDGET (data);
702
	container = NAUTILUS_CANVAS_CONTAINER (widget);
Pavel Cisler's avatar
Pavel Cisler committed
703

704
	if (container->details->dnd_info->drag_info.waiting_to_autoscroll
705
	    && container->details->dnd_info->drag_info.start_auto_scroll_in > g_get_monotonic_time ()) {
Pavel Cisler's avatar
Pavel Cisler committed
706 707 708 709
		/* not yet */
		return TRUE;
	}

710
	container->details->dnd_info->drag_info.waiting_to_autoscroll = FALSE;
Pavel Cisler's avatar
Pavel Cisler committed
711

712
	nautilus_drag_autoscroll_calculate_delta (widget, &x_scroll_delta, &y_scroll_delta);
713 714 715 716
	if (x_scroll_delta == 0 && y_scroll_delta == 0) {
		/* no work */
		return TRUE;
	}
717

718 719 720
	/* Clear the old dnd highlight frame */
	dnd_highlight_queue_redraw (widget);
	
721
	if (!nautilus_canvas_container_scroll (container, (int)x_scroll_delta, (int)y_scroll_delta)) {
722 723 724 725 726
		/* the scroll value got pinned to a min or max adjustment value,
		 * we ended up not scrolling
		 */
		return TRUE;
	}
Pavel Cisler's avatar
Pavel Cisler committed
727

728 729 730
	/* Make sure the dnd highlight frame is redrawn */
	dnd_highlight_queue_redraw (widget);
	
Pavel Cisler's avatar
Pavel Cisler committed
731 732 733 734 735 736 737 738
	/* update cached drag start offsets */
	container->details->dnd_info->drag_info.start_x -= x_scroll_delta;
	container->details->dnd_info->drag_info.start_y -= y_scroll_delta;

	/* Due to a glitch in GtkLayout, whe need to do an explicit draw of the exposed
	 * area. 
	 * Calculate the size of the area we need to draw
	 */
739 740 741 742 743
	gtk_widget_get_allocation (widget, &allocation);
	exposed_area.x = allocation.x;
	exposed_area.y = allocation.y;
	exposed_area.width = allocation.width;
	exposed_area.height = allocation.height;
Pavel Cisler's avatar
Pavel Cisler committed
744 745 746 747 748 749 750 751 752 753 754 755 756 757

	if (x_scroll_delta > 0) {
		exposed_area.x = exposed_area.width - x_scroll_delta;
	} else if (x_scroll_delta < 0) {
		exposed_area.width = -x_scroll_delta;
	}

	if (y_scroll_delta > 0) {
		exposed_area.y = exposed_area.height - y_scroll_delta;
	} else if (y_scroll_delta < 0) {
		exposed_area.height = -y_scroll_delta;
	}

	/* offset it to 0, 0 */
758 759
	exposed_area.x -= allocation.x;
	exposed_area.y -= allocation.y;
Pavel Cisler's avatar
Pavel Cisler committed
760

761 762 763 764 765
	gtk_widget_queue_draw_area (widget,
				    exposed_area.x,
				    exposed_area.y,
				    exposed_area.width,
				    exposed_area.height);
Pavel Cisler's avatar
Pavel Cisler committed
766 767 768 769 770

	return TRUE;
}

static void
771
set_up_auto_scroll_if_needed (NautilusCanvasContainer *container)
Pavel Cisler's avatar
Pavel Cisler committed
772
{
773
	nautilus_drag_autoscroll_start (&container->details->dnd_info->drag_info,
774 775 776
					GTK_WIDGET (container),
					auto_scroll_timeout_callback,
					container);
Pavel Cisler's avatar
Pavel Cisler committed
777 778 779
}

static void
780
stop_auto_scroll (NautilusCanvasContainer *container)
Pavel Cisler's avatar
Pavel Cisler committed
781
{
782
	nautilus_drag_autoscroll_stop (&container->details->dnd_info->drag_info);
Pavel Cisler's avatar
Pavel Cisler committed
783 784
}

785
static void
786
handle_local_move (NautilusCanvasContainer *container,
787 788 789
		   double world_x, double world_y)
{
	GList *moved_icons, *p;
790
	NautilusDragSelectionItem *item;
791
	NautilusCanvasIcon *icon;
792
	NautilusFile *file;
793
	char screen_string[32];
794
	GdkScreen *screen;
795
	time_t now;
796 797

	if (container->details->auto_layout) {
798
		return;
799 800
	}

801
	time (&now);
802

803
	/* Move and select the icons. */
804
	moved_icons = NULL;
805
	for (p = container->details->dnd_info->drag_info.selection_list; p != NULL; p = p->next) {
806 807
		item = p->data;
		
808
		icon = nautilus_canvas_container_get_icon_by_uri
809
			(container, item->uri);
810

811 812 813 814 815
		if (icon == NULL) {
			/* probably dragged from another screen.  Add it to
			 * this screen
			 */

Alexander Larsson's avatar
Alexander Larsson committed
816
			file = nautilus_file_get_by_uri (item->uri);
817 818

			screen = gtk_widget_get_screen (GTK_WIDGET (container));
819 820
			g_snprintf (screen_string, sizeof (screen_string), "%d",
				    gdk_screen_get_number (screen));
821 822 823
			nautilus_file_set_metadata (file,
					NAUTILUS_METADATA_KEY_SCREEN,
					NULL, screen_string);
824 825
			nautilus_file_set_time_metadata (file,
							 NAUTILUS_METADATA_KEY_ICON_POSITION_TIMESTAMP, now);
826

827
			nautilus_canvas_container_add (container, NAUTILUS_CANVAS_ICON_DATA (file));
828
			
829
			icon = nautilus_canvas_container_get_icon_by_uri
830 831 832
				(container, item->uri);
		}

833
		if (item->got_icon_position) {
834
			nautilus_canvas_container_move_icon
835 836
				(container, icon,
				 world_x + item->icon_x, world_y + item->icon_y,
837
				 icon->scale,
838
				 TRUE, TRUE, TRUE);
839 840 841
		}
		moved_icons = g_list_prepend (moved_icons, icon);
	}		
842
	nautilus_canvas_container_select_list_unselect_others
843
		(container, moved_icons);
844
	/* Might have been moved in a way that requires adjusting scroll region. */
845
	nautilus_canvas_container_update_scroll_region (container);
846 847 848 849
	g_list_free (moved_icons);
}

static void
850
handle_nonlocal_move (NautilusCanvasContainer *container,
851
		      GdkDragAction action,
852
		      int x, int y,
853 854
		      const char *target_uri,
		      gboolean icon_hit)
855 856
{
	GList *source_uris, *p;
857
	GArray *source_item_locations;
858 859
	gboolean free_target_uri, is_rtl;
	int index, item_x;
860
	GtkAllocation allocation;
861

862
	if (container->details->dnd_info->drag_info.selection_list == NULL) {
863 864
		return;
	}
865

866
	source_uris = NULL;
867
	for (p = container->details->dnd_info->drag_info.selection_list; p != NULL; p = p->next) {
868
		/* do a shallow copy of all the uri strings of the copied files */
869
		source_uris = g_list_prepend (source_uris, ((NautilusDragSelectionItem *)p->data)->uri);
870 871 872
	}
	source_uris = g_list_reverse (source_uris);
	
873
	is_rtl = nautilus_canvas_container_is_layout_rtl (container);
874

875
	source_item_locations = g_array_new (FALSE, TRUE, sizeof (GdkPoint));
876
	if (!icon_hit) {
877 878 879
		/* Drop onto a container. Pass along the item points to allow placing
		 * the items in their same relative positions in the new container.
		 */
880 881 882 883 884
		source_item_locations = g_array_set_size (source_item_locations,
			g_list_length (container->details->dnd_info->drag_info.selection_list));
			
		for (index = 0, p = container->details->dnd_info->drag_info.selection_list;
			p != NULL; index++, p = p->next) {
885 886 887 888
		    	item_x = ((NautilusDragSelectionItem *)p->data)->icon_x;
			if (is_rtl)
				item_x = -item_x - ((NautilusDragSelectionItem *)p->data)->icon_width;
		     	g_array_index (source_item_locations, GdkPoint, index).x = item_x;
889
		     	g_array_index (source_item_locations, GdkPoint, index).y =
890
				((NautilusDragSelectionItem *)p->data)->icon_y;
891 892
		}
	}
Alexander Larsson's avatar
Alexander Larsson committed
893 894 895 896 897 898 899

	free_target_uri = FALSE;
 	/* Rewrite internal desktop URIs to the normal target uri */
	if (eel_uri_is_desktop (target_uri)) {
		target_uri = nautilus_get_desktop_directory_uri ();
		free_target_uri = TRUE;
	}
900

901
	if (is_rtl) {
902 903
		gtk_widget_get_allocation (GTK_WIDGET (container), &allocation);
		x = CANVAS_WIDTH (container, allocation) - x;
904
	}
905

906
	/* start the copy */
907
	g_signal_emit_by_name (container, "move-copy-items",
908 909 910 911 912
			       source_uris,
			       source_item_locations,
			       target_uri,
			       action,
			       x, y);
913

Alexander Larsson's avatar
Alexander Larsson committed
914 915 916 917
	if (free_target_uri) {
		g_free ((char *)target_uri);
	}

918
	g_list_free (source_uris);
919
	g_array_free (source_item_locations, TRUE);
920 921
}

922
static char *
923
nautilus_canvas_container_find_drop_target (NautilusCanvasContainer *container,
924 925
					  GdkDragContext *context,
					  int x, int y,
926 927
					  gboolean *icon_hit,
					  gboolean rewrite_desktop)
Ettore Perazzoli's avatar
Ettore Perazzoli committed
928
{
929
	NautilusCanvasIcon *drop_target_icon;
930
	double world_x, world_y;
931 932
	NautilusFile *file;
	char *icon_uri;
933 934
	char *container_uri;
	
935 936 937 938 939
	if (icon_hit) {
		*icon_hit = FALSE;
	}

	if (!container->details->dnd_info->drag_info.got_drop_data_type) {
940
		return NULL;
941
	}
942

943
	canvas_widget_to_world (EEL_CANVAS (container), x, y, &world_x, &world_y);
944
	
945
	/* FIXME bugzilla.gnome.org 42485: 
946
	 * These "can_accept_items" tests need to be done by
947
	 * the canvas view, not here. This file is not supposed to know
948 949 950
	 * that the target is a file.
	 */

951
	/* Find the item we hit with our drop, if any */	
952
	drop_target_icon = nautilus_canvas_container_item_at (container, world_x, world_y);
953
	if (drop_target_icon != NULL) {
954
		icon_uri = nautilus_canvas_container_get_icon_uri (container, drop_target_icon);
955
		if (icon_uri != NULL) {
Alexander Larsson's avatar
Alexander Larsson committed
956
			file = nautilus_file_get_by_uri (icon_uri);
957

958 959 960
			if (!nautilus_drag_can_accept_info (file,
							    container->details->dnd_info->drag_info.data_type,
							    container->details->dnd_info->drag_info.selection_list)) {
961 962 963 964 965 966 967 968 969
			 	/* the item we dropped our selection on cannot accept the items,
			 	 * do the same thing as if we just dropped the items on the canvas
				 */
				drop_target_icon = NULL;
			}
			
			g_free (icon_uri);
			nautilus_file_unref (file);
		}
970 971
	}

972
	if (drop_target_icon == NULL) {
973 974 975 976
		if (icon_hit) {
			*icon_hit = FALSE;
		}

977 978
		container_uri = get_container_uri (container);

979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994
		if (container_uri != NULL) {
			if (rewrite_desktop && eel_uri_is_desktop (container_uri)) {
				g_free (container_uri);
				container_uri = nautilus_get_desktop_directory_uri ();
			} else {
				gboolean can;
				file = nautilus_file_get_by_uri (container_uri);
				can = nautilus_drag_can_accept_info (file,
								     container->details->dnd_info->drag_info.data_type,
								     container->details->dnd_info->drag_info.selection_list);
				g_object_unref (file);
				if (!can) {
					g_free (container_uri);
					container_uri = NULL;
				}
			}
995 996 997
		}
		
		return container_uri;
998 999
	}
	
1000 1001 1002
	if (icon_hit) {
		*icon_hit = TRUE;
	}
1003
	return nautilus_canvas_container_get_icon_drop_target_uri (container, drop_target_icon);
1004 1005
}

1006 1007 1008
static gboolean
selection_is_image_file (GList *selection_list)
{
Alexander Larsson's avatar
Alexander Larsson committed
1009
	const char *mime_type;
1010 1011
	NautilusDragSelectionItem *selected_item;
	gboolean result;
Alexander Larsson's avatar
Alexander Larsson committed
1012 1013
	GFile *location;
	GFileInfo *info;
1014 1015 1016 1017 1018 1019 1020 1021 1022

	/* Make sure only one item is selected */
	if (selection_list == NULL ||
	    selection_list->next != NULL) {
		return FALSE;
	}

	selected_item = selection_list->data;

Alexander Larsson's avatar
Alexander Larsson committed
1023 1024 1025 1026
	mime_type = NULL;
	
	location = g_file_new_for_uri (selected_item->uri);
	info = g_file_query_info (location,
1027
				  G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE,
Alexander Larsson's avatar
Alexander Larsson committed
1028 1029 1030 1031
				  0, NULL, NULL);
	if (info) {
		mime_type = g_file_info_get_content_type (info);
	}
1032

1033
	result = g_str_has_prefix (mime_type, "image/");
Alexander Larsson's avatar
Alexander Larsson committed
1034 1035 1036 1037 1038

	if (info) {
		g_object_unref (info);
	}
	g_object_unref (location);
1039 1040 1041 1042 1043
	
	return result;
}


1044
static void
1045
nautilus_canvas_container_receive_dropped_icons (NautilusCanvasContainer *container,
1046 1047 1048
					       GdkDragContext *context,
					       int x, int y)
{
1049
	char *drop_target, *container_uri;
1050 1051 1052
	gboolean local_move_only;
	double world_x, world_y;
	gboolean icon_hit;
1053
	GdkDragAction action, real_action;
1054
	NautilusDragSelectionItem *selected_item;
1055 1056 1057

	drop_target = NULL;

1058 1059 1060 1061
	if (container->details->dnd_info->drag_info.selection_list == NULL) {
		return;
	}

1062 1063 1064
	real_action = gdk_drag_context_get_selected_action (context);

	if (real_action == GDK_ACTION_ASK) {
1065
		/* FIXME bugzilla.gnome.org 42485: This belongs in FMDirectoryView, not here. */
1066
		/* Check for special case items in selection list */
1067
		if (nautilus_drag_selection_includes_special_link (container->details->dnd_info->drag_info.selection_list)) {
1068 1069 1070 1071
			/* We only want to move the trash */
			action = GDK_ACTION_MOVE;
		} else {
			action = GDK_ACTION_MOVE | GDK_ACTION_COPY | GDK_ACTION_LINK;
1072
			container_uri = get_container_uri (container);
1073
			
1074 1075
			if (eel_uri_is_desktop (container_uri) &&
			    selection_is_image_file (container->details->dnd_info->drag_info.selection_list)) {
1076 1077
				action |= NAUTILUS_DND_ACTION_SET_AS_BACKGROUND;
			}
1078 1079

			g_free (container_uri);
1080
		}
1081
		real_action = nautilus_drag_drop_action_ask
1082
			(GTK_WIDGET (container), action);
1083
	}
1084
	
1085
	if (real_action == (GdkDragAction) NAUTILUS_DND_ACTION_SET_AS_BACKGROUND) {
1086 1087
		NautilusDesktopBackground *background;

1088
		background = nautilus_desktop_background_new (container);
1089
		selected_item = container->details->dnd_info->drag_info.selection_list->data;
1090 1091

		nautilus_desktop_background_receive_dropped_background_image (background,
1092
									      selected_item->uri);