nautilus-icon-dnd.c 39.5 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-icon-dnd.c - Drag & drop handling for the icon 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
24
25
   Authors: Ettore Perazzoli <ettore@gnu.org>,
            Darin Adler <darin@eazel.com>,
	    Andy Hertzfeld <andy@eazel.com>
26
	    Pavel Cisler <pavel@eazel.com>
Ettore Perazzoli's avatar
Ettore Perazzoli committed
27
28
*/

29

30
#include <config.h>
31
#include "nautilus-icon-dnd.h"
Ettore Perazzoli's avatar
Ettore Perazzoli committed
32

33
#include "nautilus-background.h"
34
35
#include "nautilus-file-utilities.h"
#include "nautilus-gdk-pixbuf-extensions.h"
36
37
#include "nautilus-glib-extensions.h"
#include "nautilus-gnome-extensions.h"
38
#include "nautilus-graphic-effects.h"
39
40
41
42
#include "nautilus-gtk-extensions.h"
#include "nautilus-gtk-macros.h"
#include "nautilus-icon-private.h"
#include "nautilus-link.h"
43
#include "nautilus-stock-dialogs.h"
44
#include "nautilus-string.h"
45
46
47
48
49
#include <gdk/gdkkeysyms.h>
#include <gdk/gdkx.h>
#include <gtk/gtkmain.h>
#include <gtk/gtksignal.h>
#include <libgnome/gnome-i18n.h>
50
#include <libgnome/gnome-mime.h>
51
52
#include <libgnomeui/gnome-canvas-rect-ellipse.h>
#include <libgnomeui/gnome-stock.h>
53
#include <libgnomeui/gnome-uidefs.h>
54
55
56
57
58
#include <libgnomevfs/gnome-vfs-uri.h>
#include <libgnomevfs/gnome-vfs-utils.h>
#include <math.h>
#include <stdio.h>
#include <string.h>
59

60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
static gboolean drag_drop_callback                   (GtkWidget             *widget,
						      GdkDragContext        *context,
						      int                    x,
						      int                    y,
						      guint32                time,
						      gpointer               data);
static void     nautilus_icon_dnd_update_drop_target (NautilusIconContainer *container,
						      GdkDragContext        *context,
						      int                    x,
						      int                    y);
static gboolean drag_motion_callback                 (GtkWidget             *widget,
						      GdkDragContext        *context,
						      int                    x,
						      int                    y,
						      guint32                time);
75

76
77
78
79
static void     nautilus_icon_container_receive_dropped_icons    (NautilusIconContainer *container,
								  GdkDragContext *context,
								  int x, int y);
static void     receive_dropped_tile_image                       (NautilusIconContainer *container, 
80
								  GtkSelectionData *data);
81
82
83
84
static void     receive_dropped_keyword                          (NautilusIconContainer *container, 
								  char* keyword, 
								  int x, 
								  int y);
85
86
87
88
static void     receive_dropped_uri_list                          (NautilusIconContainer *container, 
								  char* keyword, 
								  int x, 
								  int y);
89
90
91
92
93
static void     nautilus_icon_container_free_drag_data           (NautilusIconContainer *container);
static void     set_drop_target                                  (NautilusIconContainer *container,
								  NautilusIcon *icon);


Ettore Perazzoli's avatar
Ettore Perazzoli committed
94
static GtkTargetEntry drag_types [] = {
95
	{ NAUTILUS_ICON_DND_GNOME_ICON_LIST_TYPE, 0, NAUTILUS_ICON_DND_GNOME_ICON_LIST },
96
	{ NAUTILUS_ICON_DND_URI_LIST_TYPE, 0, NAUTILUS_ICON_DND_URI_LIST },
97
98
	{ NAUTILUS_ICON_DND_URL_TYPE, 0, NAUTILUS_ICON_DND_URL },
	{ NAUTILUS_ICON_DND_TEXT_TYPE, 0, NAUTILUS_ICON_DND_TEXT }
Ettore Perazzoli's avatar
Ettore Perazzoli committed
99
100
101
};

static GtkTargetEntry drop_types [] = {
102
	{ NAUTILUS_ICON_DND_GNOME_ICON_LIST_TYPE, 0, NAUTILUS_ICON_DND_GNOME_ICON_LIST },
103
104
	{ NAUTILUS_ICON_DND_URI_LIST_TYPE, 0, NAUTILUS_ICON_DND_URI_LIST },
	{ NAUTILUS_ICON_DND_URL_TYPE, 0, NAUTILUS_ICON_DND_URL },
105
	{ NAUTILUS_ICON_DND_COLOR_TYPE, 0, NAUTILUS_ICON_DND_COLOR },
106
107
	{ NAUTILUS_ICON_DND_BGIMAGE_TYPE, 0, NAUTILUS_ICON_DND_BGIMAGE },
	{ NAUTILUS_ICON_DND_KEYWORD_TYPE, 0, NAUTILUS_ICON_DND_KEYWORD }
Ettore Perazzoli's avatar
Ettore Perazzoli committed
108
109
};

110

Ettore Perazzoli's avatar
Ettore Perazzoli committed
111
static GnomeCanvasItem *
112
create_selection_shadow (NautilusIconContainer *container,
Ettore Perazzoli's avatar
Ettore Perazzoli committed
113
114
115
116
117
			 GList *list)
{
	GnomeCanvasGroup *group;
	GnomeCanvas *canvas;
	GdkBitmap *stipple;
118
119
	int max_x, max_y;
	int min_x, min_y;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
120
	GList *p;
121
	double pixels_per_unit;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
122

123
124
125
	if (list == NULL) {
		return NULL;
	}
Ettore Perazzoli's avatar
Ettore Perazzoli committed
126

127
	/* if we're only dragging a single item, don't worry about the shadow */
128
	if (list->next == NULL) {
129
		return NULL;
130
	}
131
		
132
	stipple = container->details->dnd_info->drag_info.stipple;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
	g_return_val_if_fail (stipple != NULL, NULL);

	canvas = GNOME_CANVAS (container);

	/* 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.  */

	max_x = GTK_WIDGET (container)->allocation.width;
	min_x = -max_x;

	max_y = GTK_WIDGET (container)->allocation.height;
	min_y = -max_y;

	/* Create a group, so that it's easier to move all the items around at
           once.  */
	group = GNOME_CANVAS_GROUP
		(gnome_canvas_item_new (GNOME_CANVAS_GROUP (canvas->root),
					gnome_canvas_group_get_type (),
					NULL));
	
154
	pixels_per_unit = canvas->pixels_per_unit;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
155
	for (p = list; p != NULL; p = p->next) {
156
		DragSelectionItem *item;
157
		int x1, y1, x2, y2;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
158
159
160

		item = p->data;

161
		if (!item->got_icon_position) {
162
			continue;
163
		}
164
165
166
167
168

		x1 = item->icon_x;
		y1 = item->icon_y;
		x2 = x1 + item->icon_width;
		y2 = y1 + item->icon_height;
169
			
170
171
		if (x2 >= min_x && x1 <= max_x && y2 >= min_y && y1 <= max_y)
			gnome_canvas_item_new
Ettore Perazzoli's avatar
Ettore Perazzoli committed
172
173
				(group,
				 gnome_canvas_rect_get_type (),
174
175
176
177
				 "x1", (double) x1 / pixels_per_unit,
				 "y1", (double) y1 / pixels_per_unit,
				 "x2", (double) x2 / pixels_per_unit,
				 "y2", (double) y2 / pixels_per_unit,
Ettore Perazzoli's avatar
Ettore Perazzoli committed
178
179
180
181
182
183
184
185
186
				 "outline_color", "black",
				 "outline_stipple", stipple,
				 "width_pixels", 1,
				 NULL);
	}

	return GNOME_CANVAS_ITEM (group);
}

187
188
189
/* 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
190
191
static void
set_shadow_position (GnomeCanvasItem *shadow,
192
		     double x, double y)
Ettore Perazzoli's avatar
Ettore Perazzoli committed
193
194
195
196
197
198
199
200
201
202
203
204
205
{
	double affine[6];

	affine[0] = 1.0;
	affine[1] = 0.0;
	affine[2] = 0.0;
	affine[3] = 1.0;
	affine[4] = x;
	affine[5] = y;

	gnome_canvas_item_affine_absolute (shadow, affine);
}

206

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

209
210
211
212
213
214
215
216
217
/* iteration glue struct */
typedef struct {
	gpointer iterator_context;
	NautilusDragEachSelectedItemDataGet iteratee;
	gpointer iteratee_data;
} IconGetDataBinderContext;

static gboolean
icon_get_data_binder (NautilusIcon *icon, gpointer data)
Ettore Perazzoli's avatar
Ettore Perazzoli committed
218
{
219
220
221
222
223
	IconGetDataBinderContext *context;
	ArtDRect world_rect;
	ArtIRect window_rect;
	char *uri;
	NautilusIconContainer *container;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
224

225
	context = (IconGetDataBinderContext *)data;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
226

227
	g_assert (NAUTILUS_IS_ICON_CONTAINER (context->iterator_context));
Ettore Perazzoli's avatar
Ettore Perazzoli committed
228

229
	container = NAUTILUS_ICON_CONTAINER (context->iterator_context);
Ettore Perazzoli's avatar
Ettore Perazzoli committed
230

231
232
233
234
235
236
237
238
239
	nautilus_icon_canvas_item_get_icon_rectangle
		(icon->item, &world_rect);
	nautilus_gnome_canvas_world_to_window_rectangle
		(GNOME_CANVAS (container), &world_rect, &window_rect);

	uri = nautilus_icon_container_get_icon_uri (container, icon);
	if (uri == NULL) {
		g_warning ("no URI for one of the iterated icons");
		return TRUE;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
240
241
	}

242
243
244
245
246
247
248
	/* pass the uri, mouse-relative x/y and icon width/height */
	context->iteratee (uri, 
			   (int) (window_rect.x0 - container->details->dnd_info->drag_info.start_x),
			   (int) (window_rect.y0 - container->details->dnd_info->drag_info.start_y),
			   window_rect.x1 - window_rect.x0,
			   window_rect.y1 - window_rect.y0,
			   context->iteratee_data);
Ettore Perazzoli's avatar
Ettore Perazzoli committed
249

250
251
252
	g_free (uri);

	return TRUE;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
253
254
}

255
256
257
/* Iterate over each selected icon in a NautilusIconContainer,
 * calling each_function on each.
 */
Ettore Perazzoli's avatar
Ettore Perazzoli committed
258
static void
259
260
nautilus_icon_container_each_selected_icon (NautilusIconContainer *container,
	gboolean (*each_function) (NautilusIcon *, gpointer), gpointer data)
Ettore Perazzoli's avatar
Ettore Perazzoli committed
261
262
{
	GList *p;
263
	NautilusIcon *icon;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
264

265
	for (p = container->details->icons; p != NULL; p = p->next) {
Ettore Perazzoli's avatar
Ettore Perazzoli committed
266
		icon = p->data;
267
		if (!icon->is_selected) {
Ettore Perazzoli's avatar
Ettore Perazzoli committed
268
			continue;
269
270
271
272
		}
		if (!each_function (icon, data)) {
			return;
		}
Ettore Perazzoli's avatar
Ettore Perazzoli committed
273
	}
274
275
276
}

/* Adaptor function used with nautilus_icon_container_each_selected_icon
277
 * to help iterate over all selected items, passing uris, x, y, w and h
278
279
280
281
282
283
284
285
 * values to the iteratee
 */
static void
each_icon_get_data_binder (NautilusDragEachSelectedItemDataGet iteratee, 
	gpointer iterator_context, gpointer data)
{
	IconGetDataBinderContext context;
	NautilusIconContainer *container;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
286

287
288
	g_assert (NAUTILUS_IS_ICON_CONTAINER (iterator_context));
	container = NAUTILUS_ICON_CONTAINER (iterator_context);
Ettore Perazzoli's avatar
Ettore Perazzoli committed
289

290
291
292
293
	context.iterator_context = iterator_context;
	context.iteratee = iteratee;
	context.iteratee_data = data;
	nautilus_icon_container_each_selected_icon (container, icon_get_data_binder, &context);
Ettore Perazzoli's avatar
Ettore Perazzoli committed
294
295
}

296
/* Called when the data for drag&drop is needed */
Ettore Perazzoli's avatar
Ettore Perazzoli committed
297
static void
298
299
300
301
302
303
drag_data_get_callback (GtkWidget *widget,
			GdkDragContext *context,
			GtkSelectionData *selection_data,
			guint info,
			guint32 time,
			gpointer data)
Ettore Perazzoli's avatar
Ettore Perazzoli committed
304
{
305
306
	g_assert (widget != NULL);
	g_assert (NAUTILUS_IS_ICON_CONTAINER (widget));
Ettore Perazzoli's avatar
Ettore Perazzoli committed
307
308
	g_return_if_fail (context != NULL);

309
310
311
312
313
314
	/* 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.
	 */
	nautilus_drag_drag_data_get (widget, context, selection_data,
		info, time, widget, each_icon_get_data_binder);
Ettore Perazzoli's avatar
Ettore Perazzoli committed
315
316
}

317

Ettore Perazzoli's avatar
Ettore Perazzoli committed
318
319
320
/* Target-side handling of the drag.  */

static void
321
322
nautilus_icon_container_position_shadow (NautilusIconContainer *container,
					 int x, int y)
323
324
325
326
{
	GnomeCanvasItem *shadow;
	double world_x, world_y;

327
	shadow = container->details->dnd_info->shadow;
328
	if (shadow == NULL) {
329
		return;
330
	}
331
332
	gnome_canvas_window_to_world (GNOME_CANVAS (container),
				      x, y, &world_x, &world_y);
333

334
	set_shadow_position (shadow, world_x, world_y);
335
	gnome_canvas_item_show (shadow);
336
337
338
}

static void
339
340
341
nautilus_icon_container_dropped_icon_feedback (GtkWidget *widget,
					       GtkSelectionData *data,
					       int x, int y)
Ettore Perazzoli's avatar
Ettore Perazzoli committed
342
{
343
344
	NautilusIconContainer *container;
	NautilusIconDndInfo *dnd_info;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
345

346
	container = NAUTILUS_ICON_CONTAINER (widget);
347
	dnd_info = container->details->dnd_info;
348
	
349
	/* Delete old selection list. */
350
351
	nautilus_drag_destroy_selection_list (dnd_info->drag_info.selection_list);
	dnd_info->drag_info.selection_list = NULL;
352

353
	/* Delete old shadow if any. */
354
	if (dnd_info->shadow != NULL) {
355
356
		/* FIXME bugzilla.eazel.com 2484: 
		 * Is a destroy really sufficient here? Who does the unref? */
Ettore Perazzoli's avatar
Ettore Perazzoli committed
357
		gtk_object_destroy (GTK_OBJECT (dnd_info->shadow));
358
	}
359
360

	/* Build the selection list and the shadow. */
361
362
	dnd_info->drag_info.selection_list = nautilus_drag_build_selection_list (data);
	dnd_info->shadow = create_selection_shadow (container, dnd_info->drag_info.selection_list);
363
	nautilus_icon_container_position_shadow (container, x, y);
Ettore Perazzoli's avatar
Ettore Perazzoli committed
364
365
}

366
367
368
369
370
371
372
373
/** this callback is called in 2 cases.
    It is called upon drag_motion events to get the actual data 
    In that case, it just makes sure it gets the data.
    It is called upon drop_drop events to execute the actual 
    actions on the received action. In that case, it actually fist makes sure
    that we have got the data then processes it.
*/

374
static void
375
376
377
378
379
380
381
382
drag_data_received_callback (GtkWidget *widget,
			     GdkDragContext *context,
			     int x,
			     int y,
			     GtkSelectionData *data,
			     guint info,
			     guint32 time,
			     gpointer user_data)
Ettore Perazzoli's avatar
Ettore Perazzoli committed
383
{
384
    	NautilusDragInfo *drag_info;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
385

386
	drag_info = &(NAUTILUS_ICON_CONTAINER (widget)->details->dnd_info->drag_info);
Ettore Perazzoli's avatar
Ettore Perazzoli committed
387

388
389
	drag_info->got_drop_data_type = TRUE;
	drag_info->data_type = info;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
390

391
	switch (info) {
392
	case NAUTILUS_ICON_DND_GNOME_ICON_LIST:
393
		nautilus_icon_container_dropped_icon_feedback (widget, data, x, y);
394
		break;
395
	case NAUTILUS_ICON_DND_COLOR:
396
	case NAUTILUS_ICON_DND_BGIMAGE:	
397
	case NAUTILUS_ICON_DND_KEYWORD:	
398
	case NAUTILUS_ICON_DND_URI_LIST:
399
		/* Save the data so we can do the actual work on drop. */
400
401
		g_assert (drag_info->selection_data == NULL);
		drag_info->selection_data = nautilus_gtk_selection_data_copy_deep (data);
402
403
		break;
	default:
404
		break;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
405
	}
406
407
408
409
410


	/* this is the second use case of this callback.
	 * we have to do the actual work for the drop.
	 */
411
	if (drag_info->drop_occured) {
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434

		switch (info) {
		case NAUTILUS_ICON_DND_GNOME_ICON_LIST:
			nautilus_icon_container_receive_dropped_icons
				(NAUTILUS_ICON_CONTAINER (widget),
				 context, x, y);
			gtk_drag_finish (context, TRUE, FALSE, time);
			break;
		case NAUTILUS_ICON_DND_COLOR:
			nautilus_background_receive_dropped_color
				(nautilus_get_widget_background (widget),
				 widget, x, y, data);
			gtk_drag_finish (context, TRUE, FALSE, time);
			break;
		case NAUTILUS_ICON_DND_BGIMAGE:
			receive_dropped_tile_image
				(NAUTILUS_ICON_CONTAINER (widget),
				 data);
			gtk_drag_finish (context, FALSE, FALSE, time);
			break;
		case NAUTILUS_ICON_DND_KEYWORD:
			receive_dropped_keyword
				(NAUTILUS_ICON_CONTAINER (widget),
435
				 (char*) data->data, x, y);
436
437
			gtk_drag_finish (context, FALSE, FALSE, time);
			break;
438
439
440
441
442
443
444
		case NAUTILUS_ICON_DND_URI_LIST:
			receive_dropped_uri_list
				(NAUTILUS_ICON_CONTAINER (widget),
				 (char*) data->data, x, y);
			gtk_drag_finish (context, FALSE, FALSE, time);
			break;

445
446
447
448
449
450
451
452
453
454
455
456
		default:
			gtk_drag_finish (context, FALSE, FALSE, time);
		}
		
		nautilus_icon_container_free_drag_data (NAUTILUS_ICON_CONTAINER (widget));
		
		set_drop_target (NAUTILUS_ICON_CONTAINER (widget), NULL);

		/* reinitialise it for the next dnd */
		drag_info->drop_occured = FALSE;
	}

457
458
459
}

static void
460
461
462
nautilus_icon_container_ensure_drag_data (NautilusIconContainer *container,
					  GdkDragContext *context,
					  guint32 time)
463
{
464
	NautilusIconDndInfo *dnd_info;
465

466
	dnd_info = container->details->dnd_info;
467

468
	if (!dnd_info->drag_info.got_drop_data_type) {
469
470
471
		gtk_drag_get_data (GTK_WIDGET (container), context,
				   GPOINTER_TO_INT (context->targets->data),
				   time);
472
	}
473
474
}

Ettore Perazzoli's avatar
Ettore Perazzoli committed
475
static void
476
477
478
drag_end_callback (GtkWidget *widget,
		   GdkDragContext *context,
		   gpointer data)
Ettore Perazzoli's avatar
Ettore Perazzoli committed
479
{
480
481
	NautilusIconContainer *container;
	NautilusIconDndInfo *dnd_info;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
482

483
	container = NAUTILUS_ICON_CONTAINER (widget);
484
	dnd_info = container->details->dnd_info;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
485

486
487
	nautilus_drag_destroy_selection_list (dnd_info->drag_info.selection_list);
	dnd_info->drag_info.selection_list = NULL;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
488
489
}

Pavel Cisler's avatar
Pavel Cisler committed
490
491
492
493
494
static NautilusIcon *
nautilus_icon_container_item_at (NautilusIconContainer *container,
				 int x, int y)
{
	GList *p;
495
	int size;
Pavel Cisler's avatar
Pavel Cisler committed
496
497
	ArtDRect point;

498
499
500
501
502
	/* 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
	 */
	
	size = MAX (1, 1 + (1 / GNOME_CANVAS (container)->pixels_per_unit));
Pavel Cisler's avatar
Pavel Cisler committed
503
504
	point.x0 = x;
	point.y0 = y;
505
506
	point.x1 = x + size;
	point.y1 = y + size;
Pavel Cisler's avatar
Pavel Cisler committed
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526

	for (p = container->details->icons; p != NULL; p = p->next) {
		NautilusIcon *icon;
		icon = p->data;
		if (nautilus_icon_canvas_item_hit_test_rectangle
			(icon->item, &point)) {
			return icon;
		}
	}

	return NULL;
}

static char *
get_container_uri (const NautilusIconContainer *container)
{
	char *uri;

	/* get the URI associated with the container */
	uri = NULL;
527
	gtk_signal_emit_by_name (GTK_OBJECT (container), "get_container_uri", &uri);
Pavel Cisler's avatar
Pavel Cisler committed
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
	return uri;
}

static gboolean
nautilus_icon_container_selection_items_local (const NautilusIconContainer *container,
					       const GList *items)
{
	char *container_uri_string;
	gboolean result;

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

	result = FALSE;

	/* get the URI associated with the container */
	container_uri_string = get_container_uri (container);
545
546
547
548
549
550
551
552
553
	
	if (nautilus_uri_is_trash (container_uri_string)) {
		/* Special-case "trash:" because the nautilus_drag_items_local
		 * would not work for it.
		 */
		result = nautilus_drag_items_in_trash (items);
	} else {
		result = nautilus_drag_items_local (container_uri_string, items);
	}
Pavel Cisler's avatar
Pavel Cisler committed
554
555
556
557
558
	g_free (container_uri_string);
	
	return result;
}

559
/* handle dropped tile images */
560
static void
561
receive_dropped_tile_image (NautilusIconContainer *container, GtkSelectionData *data)
562
{
563
564
	g_assert (data != NULL);
	nautilus_background_receive_dropped_background_image
565
		(nautilus_get_widget_background (GTK_WIDGET (container)), data->data);
566
567
}

568
569
570
571
572
573
574
575
576
577
/* handle dropped keywords */
static void
receive_dropped_keyword (NautilusIconContainer *container, char* keyword, int x, int y)
{
	char *uri;
	double world_x, world_y;

	NautilusIcon *drop_target_icon;
	NautilusFile *file;
	
578
	g_assert (keyword != NULL);
579
580
581
582

	/* find the item we hit with our drop, if any */
  	gnome_canvas_window_to_world (GNOME_CANVAS (container), x, y, &world_x, &world_y);
	drop_target_icon = nautilus_icon_container_item_at (container, world_x, world_y);
583
	if (drop_target_icon == NULL) {
584
		return;
585
586
	}

587
588
	/* FIXME bugzilla.eazel.com 2485: 
	 * This does not belong in the icon code.
589
590
591
592
593
	 * It has to be in the file manager.
	 * The icon code has no right to deal with the file directly.
	 * But luckily there's no issue of not getting a file object,
	 * so we don't have to worry about async. issues here.
	 */
594
	uri = nautilus_icon_container_get_icon_uri (container, drop_target_icon);
595
596
	file = nautilus_file_get (uri);
	g_free (uri);
597
	
598
599
	nautilus_drag_file_receive_dropped_keyword (file, keyword);

600
	nautilus_file_unref (file);
601
602
603
	nautilus_icon_container_update_icon (container, drop_target_icon);
}

604
605
606
607
/* handle dropped uri list */
static void
receive_dropped_uri_list (NautilusIconContainer *container, char *uri_list, int x, int y)
{
608
	/* FIXME bugzilla.eazel.com 5080:
609
610
	 * this needs a better name - it's link/desktop specific 
	 */
611
612
613
614
615
	GList *li, *files;
	int argc;
	char **argv;
	int i;
	
616
617
618
	if (uri_list == NULL) {
		return;
	}
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637

	files = gnome_uri_list_extract_filenames (uri_list);
	argc = g_list_length (files);
	argv = g_new (char *, argc + 1);
	argv[argc] = NULL;

	for (i=0, li = files; li; i++, li = g_list_next (li)) {
		argv[i] = li->data;
	}

	/* Extract .desktop info and create link/links */
	gtk_signal_emit_by_name (GTK_OBJECT (container), "create_nautilus_links",
				 files,
				 x, y);

	gnome_uri_list_free_strings (files);
	g_free(argv);
}

Pavel Cisler's avatar
Pavel Cisler committed
638
639
640
641
642
643
644
645
646
647
648
649
static int
auto_scroll_timeout_callback (gpointer data)
{
	NautilusIconContainer *container;
	GtkWidget *widget;
	float x_scroll_delta, y_scroll_delta;
	GdkRectangle exposed_area;

	g_assert (NAUTILUS_IS_ICON_CONTAINER (data));
	widget = GTK_WIDGET (data);
	container = NAUTILUS_ICON_CONTAINER (widget);

650
651
	if (container->details->dnd_info->drag_info.waiting_to_autoscroll
	    && container->details->dnd_info->drag_info.start_auto_scroll_in > nautilus_get_system_time()) {
Pavel Cisler's avatar
Pavel Cisler committed
652
653
654
655
		/* not yet */
		return TRUE;
	}

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

658
	nautilus_drag_autoscroll_calculate_delta (widget, &x_scroll_delta, &y_scroll_delta);
659
660
661
662
	if (x_scroll_delta == 0 && y_scroll_delta == 0) {
		/* no work */
		return TRUE;
	}
663
664
665
666
667
668
669

	if (!nautilus_icon_container_scroll (container, (int)x_scroll_delta, (int)y_scroll_delta)) {
		/* 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
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699

	/* 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
	 */
	exposed_area.x = widget->allocation.x;
	exposed_area.y = widget->allocation.y;
	exposed_area.width = widget->allocation.width;
	exposed_area.height = widget->allocation.height;

	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 */
	exposed_area.x -= widget->allocation.x;
	exposed_area.y -= widget->allocation.y;

700
	gtk_widget_draw (widget, &exposed_area);
Pavel Cisler's avatar
Pavel Cisler committed
701
702
703
704
705
706
707

	return TRUE;
}

static void
set_up_auto_scroll_if_needed (NautilusIconContainer *container)
{
708
709
710
711
	nautilus_drag_autoscroll_start (&container->details->dnd_info->drag_info,
					GTK_WIDGET (container),
					auto_scroll_timeout_callback,
					container);
Pavel Cisler's avatar
Pavel Cisler committed
712
713
714
715
716
}

static void
stop_auto_scroll (NautilusIconContainer *container)
{
717
	nautilus_drag_autoscroll_stop (&container->details->dnd_info->drag_info);
Pavel Cisler's avatar
Pavel Cisler committed
718
719
}

720
721
722
723
static gboolean
confirm_switch_to_manual_layout (NautilusIconContainer *container)
{
	const char *message;
724
	GnomeDialog *dialog;
725

Darin Adler's avatar
Darin Adler committed
726
727
728
729
	/* FIXME bugzilla.eazel.com 915: Use of the word "directory"
	 * makes this FMIconView specific. Move these messages into
	 * FMIconView so NautilusIconContainer can be used for things
	 * that are not directories?
730
731
	 */
	if (nautilus_icon_container_has_stored_icon_positions (container)) {
732
		if (nautilus_g_list_exactly_one_item (container->details->dnd_info->drag_info.selection_list)) {
733
			message = _("This folder uses automatic layout. "
734
735
			"Do you want to switch to manual layout and leave this item where you dropped it? "
			"This will clobber the stored manual layout.");
736
		} else {
737
			message = _("This folder uses automatic layout. "
738
739
			"Do you want to switch to manual layout and leave these items where you dropped them? "
			"This will clobber the stored manual layout.");
740
741
		}
	} else {
742
		if (nautilus_g_list_exactly_one_item (container->details->dnd_info->drag_info.selection_list)) {
743
			message = _("This folder uses automatic layout. "
744
			"Do you want to switch to manual layout and leave this item where you dropped it?");
745
		} else {
746
			message = _("This folder uses automatic layout. "
747
			"Do you want to switch to manual layout and leave these items where you dropped them?");
748
749
750
		}
	}

751
752
753
754
	dialog = nautilus_show_yes_no_dialog (message, _("Switch to Manual Layout?"),
					      _("Switch"), GNOME_STOCK_BUTTON_CANCEL,
					      GTK_WINDOW (gtk_widget_get_ancestor 
					 	    (GTK_WIDGET (container), GTK_TYPE_WINDOW)));
755
756

	return gnome_dialog_run (dialog) == GNOME_OK;
757
758
}

759
760
761
762
763
static void
handle_local_move (NautilusIconContainer *container,
		   double world_x, double world_y)
{
	GList *moved_icons, *p;
764
	DragSelectionItem *item;
765
766
767
	NautilusIcon *icon;

	if (container->details->auto_layout) {
768
769
770
771
		if (!confirm_switch_to_manual_layout (container)) {
			return;
		}
		nautilus_icon_container_freeze_icon_positions (container);
772
773
	}

774
	/* Move and select the icons. */
775
	moved_icons = NULL;
776
	for (p = container->details->dnd_info->drag_info.selection_list; p != NULL; p = p->next) {
777
778
779
780
		item = p->data;
		
		icon = nautilus_icon_container_get_icon_by_uri
			(container, item->uri);
781

782
783
784
785
786
		if (item->got_icon_position) {
			nautilus_icon_container_move_icon
				(container, icon,
				 world_x + item->icon_x, world_y + item->icon_y,
				 icon->scale_x, icon->scale_y,
787
				 TRUE, TRUE);
788
789
790
791
792
		}
		moved_icons = g_list_prepend (moved_icons, icon);
	}		
	nautilus_icon_container_select_list_unselect_others
		(container, moved_icons);
793
794
	/* Might have been moved in a way that requires adjusting scroll region. */
	nautilus_icon_container_update_scroll_region (container);
795
796
797
798
799
800
801
	g_list_free (moved_icons);
}

static void
handle_nonlocal_move (NautilusIconContainer *container,
		      GdkDragContext *context,
		      int x, int y,
802
803
		      const char *target_uri,
		      gboolean icon_hit)
804
805
{
	GList *source_uris, *p;
806
807
	GArray *source_item_locations;
	int index;
808

809
	if (container->details->dnd_info->drag_info.selection_list == NULL) {
810
811
812
813
		return;
	}
	
	source_uris = NULL;
814
	for (p = container->details->dnd_info->drag_info.selection_list; p != NULL; p = p->next) {
815
		/* do a shallow copy of all the uri strings of the copied files */
816
		source_uris = g_list_prepend (source_uris, ((DragSelectionItem *)p->data)->uri);
817
818
819
	}
	source_uris = g_list_reverse (source_uris);
	
820
	source_item_locations = g_array_new (FALSE, TRUE, sizeof (GdkPoint));
821
	if (!icon_hit) {
822
823
824
		/* Drop onto a container. Pass along the item points to allow placing
		 * the items in their same relative positions in the new container.
		 */
825
826
827
828
829
830
831
832
833
		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) {
		     	g_array_index (source_item_locations, GdkPoint, index).x =
		     		((DragSelectionItem *)p->data)->icon_x;
		     	g_array_index (source_item_locations, GdkPoint, index).y =
				((DragSelectionItem *)p->data)->icon_y;
834
835
		}
	}
836
		
837
838
839
840
841
842
843
	/* start the copy */
	gtk_signal_emit_by_name (GTK_OBJECT (container), "move_copy_items",
				 source_uris,
				 source_item_locations,
				 target_uri,
				 context->action,
				 x, y);
844

845
	g_list_free (source_uris);
846
	g_array_free (source_item_locations, TRUE);
847
848
}

849
850
851
852
853
static char *
nautilus_icon_container_find_drop_target (NautilusIconContainer *container,
					  GdkDragContext *context,
					  int x, int y,
					  gboolean *icon_hit)
Ettore Perazzoli's avatar
Ettore Perazzoli committed
854
{
Pavel Cisler's avatar
Pavel Cisler committed
855
856
	NautilusIcon *drop_target_icon;
	double world_x, world_y;
857
858
	NautilusFile *file;
	char *icon_uri;
859

860
	*icon_hit = FALSE;
861
	if (container->details->dnd_info->drag_info.selection_list == NULL) {
862
		return NULL;
863
	}
864

865
  	gnome_canvas_window_to_world (GNOME_CANVAS (container),
Pavel Cisler's avatar
Pavel Cisler committed
866
				      x, y, &world_x, &world_y);
867
	
868
869
	/* FIXME bugzilla.eazel.com 2485: 
	 * These "can_accept_items" tests need to be done by
870
871
872
873
	 * the icon view, not here. This file is not supposed to know
	 * that the target is a file.
	 */

874
	/* Find the item we hit with our drop, if any */	
Pavel Cisler's avatar
Pavel Cisler committed
875
	drop_target_icon = nautilus_icon_container_item_at (container, world_x, world_y);
876
877
878
879
880
	if (drop_target_icon != NULL) {
		icon_uri = nautilus_icon_container_get_icon_uri (container, drop_target_icon);
		if (icon_uri != NULL) {
			file = nautilus_file_get (icon_uri);

881
			if (!nautilus_drag_can_accept_items (file, 
882
883
884
885
886
887
888
889
890
891
					container->details->dnd_info->drag_info.selection_list)) {
			 	/* 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);
		}
Pavel Cisler's avatar
Pavel Cisler committed
892
893
	}

894
	if (drop_target_icon == NULL) {
895
896
897
898
899
		*icon_hit = FALSE;
		return get_container_uri (container);
	}
	
	*icon_hit = TRUE;
900
	return nautilus_icon_container_get_icon_drop_target_uri (container, drop_target_icon);
901
902
}

903
904
/* FIXME bugzilla.eazel.com 2485: This belongs in FMDirectoryView, not here. */
static gboolean
905
selection_includes_special_link (GList *selection_list)
906
907
908
{
	GList *node;
	char *uri, *local_path;
909
	gboolean link_in_selection;
910

911
	link_in_selection = FALSE;
912
913
914
915
916
917

	for (node = selection_list; node != NULL; node = node->next) {
		uri = ((DragSelectionItem *) node->data)->uri;

		/* FIXME bugzilla.eazel.com 3020: This does sync. I/O and works only locally. */
		local_path = gnome_vfs_get_local_path_from_uri (uri);
918
919
920
		link_in_selection = local_path != NULL
			&& (nautilus_link_local_is_trash_link (local_path) || nautilus_link_local_is_home_link (local_path) ||
			nautilus_link_local_is_volume_link (local_path));
921
922
		g_free (local_path);
		
923
		if (link_in_selection) {
924
925
926
927
			break;
		}
	}
	
928
	return link_in_selection;
929
930
}

931
932
933
934
935
936
937
938
939
static void
nautilus_icon_container_receive_dropped_icons (NautilusIconContainer *container,
					       GdkDragContext *context,
					       int x, int y)
{
	char *drop_target;
	gboolean local_move_only;
	double world_x, world_y;
	gboolean icon_hit;
940
	GdkDragAction action;
941
942
943

	drop_target = NULL;

944
945
946
947
	if (container->details->dnd_info->drag_info.selection_list == NULL) {
		return;
	}

948
	if (context->action == GDK_ACTION_ASK) {
949
		/* FIXME bugzilla.eazel.com 2485: This belongs in FMDirectoryView, not here. */
950
		/* Check for special case items in selection list */
951
		if (selection_includes_special_link (container->details->dnd_info->drag_info.selection_list)) {
952
953
954
955
			/* We only want to move the trash */
			action = GDK_ACTION_MOVE;
		} else {
			action = GDK_ACTION_MOVE | GDK_ACTION_COPY | GDK_ACTION_LINK;
956
		}
957
		context->action = nautilus_drag_drop_action_ask (action);
958
	}
959

960
	if (context->action > 0) {
961
962
	  	gnome_canvas_window_to_world (GNOME_CANVAS (container),
					      x, y, &world_x, &world_y);
963

964
965
		drop_target = nautilus_icon_container_find_drop_target (container, 
			context, x, y, &icon_hit);
Pavel Cisler's avatar
Pavel Cisler committed
966

967
968
969
970
971
972
973
974
975
976
977
978
		local_move_only = FALSE;
		if (!icon_hit && context->action == GDK_ACTION_MOVE) {
			/* we can just move the icon positions if the move ended up in
			 * the item's parent container
			 */
			local_move_only = nautilus_icon_container_selection_items_local
				(container, container->details->dnd_info->drag_info.selection_list);
		}

		if (local_move_only) {
			handle_local_move (container, world_x, world_y);
		} else {
979
			handle_nonlocal_move (container, context, world_x, world_y, drop_target, icon_hit);
980
		}
981
	}
Pavel Cisler's avatar
Pavel Cisler committed
982

983
	g_free (drop_target);
984
985
	nautilus_drag_destroy_selection_list (container->details->dnd_info->drag_info.selection_list);
	container->details->dnd_info->drag_info.selection_list = NULL;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
986
987
}

988
989
990
991
992
993
994
995
996
static void
nautilus_icon_container_get_drop_action (NautilusIconContainer *container,
					 GdkDragContext *context,
					 int x, int y,
					 int *default_action,
					 int *non_default_action)
{
	char *drop_target;
	gboolean icon_hit;
997
998
999
1000
	NautilusIcon *icon;
	double world_x, world_y;
	
	icon_hit = FALSE;
1001
1002
1003
1004
	if (!container->details->dnd_info->drag_info.got_drop_data_type) {
		/* drag_data_received_callback didn't get called yet */
		return;
	}
1005

1006
1007
1008
1009
1010
1011
1012
	/* find out if we're over an icon */
  	gnome_canvas_window_to_world (GNOME_CANVAS (container),
				      x, y, &world_x, &world_y);
	
	icon = nautilus_icon_container_item_at (container, world_x, world_y);

	/* case out on the type of object being dragged */
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
	switch (container->details->dnd_info->drag_info.data_type) {
	case NAUTILUS_ICON_DND_GNOME_ICON_LIST:
		if (container->details->dnd_info->drag_info.selection_list == NULL) {
			*default_action = 0;
			*non_default_action = 0;
			return;
		}
		drop_target = nautilus_icon_container_find_drop_target (container,
			context, x, y, &icon_hit);
		if (!drop_target) {
			*default_action = 0;
			*non_default_action = 0;
			return;
		}
1027
		nautilus_drag_default_drop_action_for_icons (context, drop_target, 
1028
1029
			container->details->dnd_info->drag_info.selection_list, 
			default_action, non_default_action);
1030
		g_free (drop_target);
1031
		break;
1032

1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
	/* handle emblems by setting the action if we're over an object */
	case NAUTILUS_ICON_DND_KEYWORD:
		if (icon == NULL) {
			*default_action = 0;
			*non_default_action = 0;
		} else {
			*default_action = context->suggested_action;
			*non_default_action = context->suggested_action;
		}
		
		break;
	
	/* handle colors and backgrounds by setting the action if we're over the background */		
1046
1047
	case NAUTILUS_ICON_DND_COLOR:
	case NAUTILUS_ICON_DND_BGIMAGE:
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
		if (icon == NULL) {
			*default_action = context->suggested_action;
			*non_default_action = context->suggested_action;
		} else {
			*default_action = 0;
			*non_default_action = 0;
		}
	
		break;
	
1058
	case NAUTILUS_ICON_DND_URI_LIST:
1059
		*default_action = context->suggested_action;
1060
		*non_default_action = context->suggested_action;
1061
		break;
1062
	
1063
	default:
1064
	}
1065

1066
1067
}

1068
static void
1069
1070
1071
1072
1073
set_drop_target (NautilusIconContainer *container,
		 NautilusIcon *icon)
{
	NautilusIcon *old_icon;

1074
1075
1076
	/* Check if current drop target changed, update icon drop
	 * higlight if needed.
	 */
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
	old_icon = container->details->drop_target;
	if (icon == old_icon) {
		return;
	}

	/* Remember the new drop target for the next round. */
	container->details->drop_target = icon;
	nautilus_icon_container_update_icon (container, old_icon);
	nautilus_icon_container_update_icon (container, icon);
}

static void
nautilus_icon_dnd_update_drop_target (NautilusIconContainer *container,
1090
1091
1092
				      GdkDragContext *context,
				      int x, int y)
{
1093
	NautilusIcon *icon;
1094
1095
1096
	double world_x, world_y;
	
	g_assert (NAUTILUS_IS_ICON_CONTAINER (container));
1097
1098
	if ((container->details->dnd_info->drag_info.selection_list == NULL) 
	   && (container->details->dnd_info->drag_info.data_type != NAUTILUS_ICON_DND_KEYWORD)) {
1099
1100
1101
1102
1103
1104
1105
		return;
	}

  	gnome_canvas_window_to_world (GNOME_CANVAS (container),
				      x, y, &world_x, &world_y);

	/* Find the item we hit with our drop, if any. */
1106
	icon = nautilus_icon_container_item_at (container, world_x, world_y);
1107

1108
1109
	/* FIXME bugzilla.eazel.com 2485: 
	 * These "can_accept_items" tests need to be done by
1110
1111
1112
1113
	 * the icon view, not here. This file is not supposed to know
	 * that the target is a file.
	 */

1114
	/* Find if target icon accepts our drop. */
1115
	if (icon != NULL 
1116
		&& (container->details->dnd_info->drag_info.data_type != NAUTILUS_ICON_DND_KEYWORD) 
1117
1118
1119
		&& !nautilus_drag_can_accept_items 
			(nautilus_file_get (
				nautilus_icon_container_get_icon_uri (container, icon)),