nautilus-icon-dnd.c 39.3 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
	result = nautilus_drag_items_local (container_uri_string, items);
Pavel Cisler's avatar
Pavel Cisler committed
546
547
548
549
550
	g_free (container_uri_string);
	
	return result;
}

551
/* handle dropped tile images */
552
static void
553
receive_dropped_tile_image (NautilusIconContainer *container, GtkSelectionData *data)
554
{
555
556
	g_assert (data != NULL);
	nautilus_background_receive_dropped_background_image
557
		(nautilus_get_widget_background (GTK_WIDGET (container)), data->data);
558
559
}

560
561
562
563
564
565
566
567
568
569
/* 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;
	
570
	g_assert (keyword != NULL);
571
572
573
574

	/* 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);
575
	if (drop_target_icon == NULL) {
576
		return;
577
578
	}

579
580
	/* FIXME bugzilla.eazel.com 2485: 
	 * This does not belong in the icon code.
581
582
583
584
585
	 * 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.
	 */
586
	uri = nautilus_icon_container_get_icon_uri (container, drop_target_icon);
587
588
	file = nautilus_file_get (uri);
	g_free (uri);
589
	
590
591
	nautilus_drag_file_receive_dropped_keyword (file, keyword);

592
	nautilus_file_unref (file);
593
594
595
	nautilus_icon_container_update_icon (container, drop_target_icon);
}

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

	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
630
631
632
633
634
635
636
637
638
639
640
641
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);

642
643
	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
644
645
646
647
		/* not yet */
		return TRUE;
	}

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

650
	nautilus_drag_autoscroll_calculate_delta (widget, &x_scroll_delta, &y_scroll_delta);
651
652
653
654
	if (x_scroll_delta == 0 && y_scroll_delta == 0) {
		/* no work */
		return TRUE;
	}
655
656
657
658
659
660
661

	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
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691

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

692
	gtk_widget_draw (widget, &exposed_area);
Pavel Cisler's avatar
Pavel Cisler committed
693
694
695
696
697
698
699

	return TRUE;
}

static void
set_up_auto_scroll_if_needed (NautilusIconContainer *container)
{
700
701
702
703
	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
704
705
706
707
708
}

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

712
713
714
715
static gboolean
confirm_switch_to_manual_layout (NautilusIconContainer *container)
{
	const char *message;
716
	GnomeDialog *dialog;
717

Darin Adler's avatar
Darin Adler committed
718
719
720
721
	/* 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?
722
723
	 */
	if (nautilus_icon_container_has_stored_icon_positions (container)) {
724
		if (nautilus_g_list_exactly_one_item (container->details->dnd_info->drag_info.selection_list)) {
725
			message = _("This folder uses automatic layout. "
726
727
			"Do you want to switch to manual layout and leave this item where you dropped it? "
			"This will clobber the stored manual layout.");
728
		} else {
729
			message = _("This folder uses automatic layout. "
730
731
			"Do you want to switch to manual layout and leave these items where you dropped them? "
			"This will clobber the stored manual layout.");
732
733
		}
	} else {
734
		if (nautilus_g_list_exactly_one_item (container->details->dnd_info->drag_info.selection_list)) {
735
			message = _("This folder uses automatic layout. "
736
			"Do you want to switch to manual layout and leave this item where you dropped it?");
737
		} else {
738
			message = _("This folder uses automatic layout. "
739
			"Do you want to switch to manual layout and leave these items where you dropped them?");
740
741
742
		}
	}

743
744
745
746
747
748
	dialog = nautilus_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)));

	return gnome_dialog_run (dialog) == GNOME_OK;
749
750
}

751
752
753
754
755
static void
handle_local_move (NautilusIconContainer *container,
		   double world_x, double world_y)
{
	GList *moved_icons, *p;
756
	DragSelectionItem *item;
757
758
759
	NautilusIcon *icon;

	if (container->details->auto_layout) {
760
761
762
763
		if (!confirm_switch_to_manual_layout (container)) {
			return;
		}
		nautilus_icon_container_freeze_icon_positions (container);
764
765
	}

766
	/* Move and select the icons. */
767
	moved_icons = NULL;
768
	for (p = container->details->dnd_info->drag_info.selection_list; p != NULL; p = p->next) {
769
770
771
772
		item = p->data;
		
		icon = nautilus_icon_container_get_icon_by_uri
			(container, item->uri);
773

774
775
776
777
778
		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,
779
				 TRUE, TRUE);
780
781
782
783
784
		}
		moved_icons = g_list_prepend (moved_icons, icon);
	}		
	nautilus_icon_container_select_list_unselect_others
		(container, moved_icons);
785
786
	/* Might have been moved in a way that requires adjusting scroll region. */
	nautilus_icon_container_update_scroll_region (container);
787
788
789
790
791
792
793
	g_list_free (moved_icons);
}

static void
handle_nonlocal_move (NautilusIconContainer *container,
		      GdkDragContext *context,
		      int x, int y,
794
795
		      const char *target_uri,
		      gboolean icon_hit)
796
797
{
	GList *source_uris, *p;
798
799
	GArray *source_item_locations;
	int index;
800

801
	if (container->details->dnd_info->drag_info.selection_list == NULL) {
802
803
804
805
		return;
	}
	
	source_uris = NULL;
806
	for (p = container->details->dnd_info->drag_info.selection_list; p != NULL; p = p->next) {
807
		/* do a shallow copy of all the uri strings of the copied files */
808
		source_uris = g_list_prepend (source_uris, ((DragSelectionItem *)p->data)->uri);
809
810
811
	}
	source_uris = g_list_reverse (source_uris);
	
812
	source_item_locations = g_array_new (FALSE, TRUE, sizeof (GdkPoint));
813
	if (!icon_hit) {
814
815
816
		/* Drop onto a container. Pass along the item points to allow placing
		 * the items in their same relative positions in the new container.
		 */
817
818
819
820
821
822
823
824
825
		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;
826
827
		}
	}
828
		
829
830
831
832
833
834
835
	/* 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);
836

837
	g_list_free (source_uris);
838
	g_array_free (source_item_locations, TRUE);
839
840
}

841
842
843
844
845
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
846
{
Pavel Cisler's avatar
Pavel Cisler committed
847
848
	NautilusIcon *drop_target_icon;
	double world_x, world_y;
849
850
	NautilusFile *file;
	char *icon_uri;
851

852
	*icon_hit = FALSE;
853
	if (container->details->dnd_info->drag_info.selection_list == NULL) {
854
		return NULL;
855
	}
856

857
  	gnome_canvas_window_to_world (GNOME_CANVAS (container),
Pavel Cisler's avatar
Pavel Cisler committed
858
				      x, y, &world_x, &world_y);
859
	
860
861
	/* FIXME bugzilla.eazel.com 2485: 
	 * These "can_accept_items" tests need to be done by
862
863
864
865
	 * the icon view, not here. This file is not supposed to know
	 * that the target is a file.
	 */

866
	/* Find the item we hit with our drop, if any */	
Pavel Cisler's avatar
Pavel Cisler committed
867
	drop_target_icon = nautilus_icon_container_item_at (container, world_x, world_y);
868
869
870
871
872
	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);

873
			if (!nautilus_drag_can_accept_items (file, 
874
875
876
877
878
879
880
881
882
883
					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
884
885
	}

886
	if (drop_target_icon == NULL) {
887
888
889
890
891
		*icon_hit = FALSE;
		return get_container_uri (container);
	}
	
	*icon_hit = TRUE;
892
	return nautilus_icon_container_get_icon_drop_target_uri (container, drop_target_icon);
893
894
}

895
896
/* FIXME bugzilla.eazel.com 2485: This belongs in FMDirectoryView, not here. */
static gboolean
897
selection_includes_special_link (GList *selection_list)
898
899
900
{
	GList *node;
	char *uri, *local_path;
901
	gboolean link_in_selection;
902

903
	link_in_selection = FALSE;
904
905
906
907
908
909

	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);
910
911
912
		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));
913
914
		g_free (local_path);
		
915
		if (link_in_selection) {
916
917
918
919
			break;
		}
	}
	
920
	return link_in_selection;
921
922
}

923
924
925
926
927
928
929
930
931
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;
932
	GdkDragAction action;
933
934
935

	drop_target = NULL;

936
937
938
939
	if (container->details->dnd_info->drag_info.selection_list == NULL) {
		return;
	}

940
	if (context->action == GDK_ACTION_ASK) {
941
		/* FIXME bugzilla.eazel.com 2485: This belongs in FMDirectoryView, not here. */
942
		/* Check for special case items in selection list */
943
		if (selection_includes_special_link (container->details->dnd_info->drag_info.selection_list)) {
944
945
946
947
			/* We only want to move the trash */
			action = GDK_ACTION_MOVE;
		} else {
			action = GDK_ACTION_MOVE | GDK_ACTION_COPY | GDK_ACTION_LINK;
948
		}
949
		context->action = nautilus_drag_drop_action_ask (action);
950
	}
951

952
	if (context->action > 0) {
953
954
	  	gnome_canvas_window_to_world (GNOME_CANVAS (container),
					      x, y, &world_x, &world_y);
955

956
957
		drop_target = nautilus_icon_container_find_drop_target (container, 
			context, x, y, &icon_hit);
Pavel Cisler's avatar
Pavel Cisler committed
958

959
960
961
962
963
964
965
966
967
968
969
970
		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 {
971
			handle_nonlocal_move (container, context, world_x, world_y, drop_target, icon_hit);
972
		}
973
	}
Pavel Cisler's avatar
Pavel Cisler committed
974

975
	g_free (drop_target);
976
977
	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
978
979
}

980
981
982
983
984
985
986
987
988
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;
989
990
991
992
	NautilusIcon *icon;
	double world_x, world_y;
	
	icon_hit = FALSE;
993
994
995
996
	if (!container->details->dnd_info->drag_info.got_drop_data_type) {
		/* drag_data_received_callback didn't get called yet */
		return;
	}
997

998
999
1000
1001
1002
1003
1004
	/* 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 */
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
	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;
		}
1019
		nautilus_drag_default_drop_action_for_icons (context, drop_target, 
1020
1021
			container->details->dnd_info->drag_info.selection_list, 
			default_action, non_default_action);
1022
		g_free (drop_target);
1023
		break;
1024

1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
	/* 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 */		
1038
1039
	case NAUTILUS_ICON_DND_COLOR:
	case NAUTILUS_ICON_DND_BGIMAGE:
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
		if (icon == NULL) {
			*default_action = context->suggested_action;
			*non_default_action = context->suggested_action;
		} else {
			*default_action = 0;
			*non_default_action = 0;
		}
	
		break;
	
1050
	case NAUTILUS_ICON_DND_URI_LIST:
1051
		*default_action = context->suggested_action;
1052
		*non_default_action = context->suggested_action;
1053
		break;
1054
	
1055
	default:
1056
	}
1057

1058
1059
}

1060
static void
1061
1062
1063
1064
1065
set_drop_target (NautilusIconContainer *container,
		 NautilusIcon *icon)
{
	NautilusIcon *old_icon;

1066
1067
1068
	/* Check if current drop target changed, update icon drop
	 * higlight if needed.
	 */
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
	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,
1082
1083
1084
				      GdkDragContext *context,
				      int x, int y)
{
1085
	NautilusIcon *icon;
1086
1087
1088
	double world_x, world_y;
	
	g_assert (NAUTILUS_IS_ICON_CONTAINER (container));
1089
1090
	if ((container->details->dnd_info->drag_info.selection_list == NULL) 
	   && (container->details->dnd_info->drag_info.data_type != NAUTILUS_ICON_DND_KEYWORD)) {
1091
1092
1093
1094
1095
1096
1097
		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. */
1098
	icon = nautilus_icon_container_item_at (container, world_x, world_y);
1099

1100
1101
	/* FIXME bugzilla.eazel.com 2485: 
	 * These "can_accept_items" tests need to be done by
1102
1103
1104
1105
	 * the icon view, not here. This file is not supposed to know
	 * that the target is a file.
	 */

1106
	/* Find if target icon accepts our drop. */
1107
	if (icon != NULL 
1108
		&& (container->details->dnd_info->drag_info.data_type != NAUTILUS_ICON_DND_KEYWORD) 
1109
1110
1111
		&& !nautilus_drag_can_accept_items 
			(nautilus_file_get (
				nautilus_icon_container_get_icon_uri (container, icon)), 
1112
			container->details->dnd_info->drag_info.selection_list)) {
1113
		icon = NULL;
Pavel Cisler's avatar