nautilus-icon-dnd.c 40.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
111
static GtkTargetList *drop_types_list = NULL;

112

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

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

128
	/* if we're only dragging a single item, don't worry about the shadow */
129
	if (list->next == NULL) {
130
		return NULL;
131
	}
132
		
133
	stipple = container->details->dnd_info->drag_info.stipple;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
	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));
	
	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,
				 "y1", (double) y1,
				 "x2", (double) x2,
				 "y2", (double) y2,
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
	window_rect = nautilus_art_irect_offset_by (window_rect, 
		- container->details->dnd_info->drag_info.start_x,
		- container->details->dnd_info->drag_info.start_y);

	window_rect = nautilus_art_irect_scale_by (window_rect, 
		1 / GNOME_CANVAS (container)->pixels_per_unit);
	
249
250
	/* pass the uri, mouse-relative x/y and icon width/height */
	context->iteratee (uri, 
251
252
			   (int) window_rect.x0,
			   (int) window_rect.y0,
253
254
255
			   window_rect.x1 - window_rect.x0,
			   window_rect.y1 - window_rect.y0,
			   context->iteratee_data);
Ettore Perazzoli's avatar
Ettore Perazzoli committed
256

257
258
259
	g_free (uri);

	return TRUE;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
260
261
}

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

272
	for (p = container->details->icons; p != NULL; p = p->next) {
Ettore Perazzoli's avatar
Ettore Perazzoli committed
273
		icon = p->data;
274
		if (!icon->is_selected) {
Ettore Perazzoli's avatar
Ettore Perazzoli committed
275
			continue;
276
277
278
279
		}
		if (!each_function (icon, data)) {
			return;
		}
Ettore Perazzoli's avatar
Ettore Perazzoli committed
280
	}
281
282
283
}

/* Adaptor function used with nautilus_icon_container_each_selected_icon
284
 * to help iterate over all selected items, passing uris, x, y, w and h
285
286
287
288
289
290
291
292
 * 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
293

294
295
	g_assert (NAUTILUS_IS_ICON_CONTAINER (iterator_context));
	container = NAUTILUS_ICON_CONTAINER (iterator_context);
Ettore Perazzoli's avatar
Ettore Perazzoli committed
296

297
298
299
300
	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
301
302
}

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

316
317
318
319
320
321
	/* 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
322
323
}

324

Ettore Perazzoli's avatar
Ettore Perazzoli committed
325
326
327
/* Target-side handling of the drag.  */

static void
328
329
nautilus_icon_container_position_shadow (NautilusIconContainer *container,
					 int x, int y)
330
331
332
333
{
	GnomeCanvasItem *shadow;
	double world_x, world_y;

334
	shadow = container->details->dnd_info->shadow;
335
	if (shadow == NULL) {
336
		return;
337
	}
338
339
	gnome_canvas_window_to_world (GNOME_CANVAS (container),
				      x, y, &world_x, &world_y);
340

341
	set_shadow_position (shadow, world_x, world_y);
342
	gnome_canvas_item_show (shadow);
343
344
345
}

static void
346
347
348
nautilus_icon_container_dropped_icon_feedback (GtkWidget *widget,
					       GtkSelectionData *data,
					       int x, int y)
Ettore Perazzoli's avatar
Ettore Perazzoli committed
349
{
350
351
	NautilusIconContainer *container;
	NautilusIconDndInfo *dnd_info;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
352

353
	container = NAUTILUS_ICON_CONTAINER (widget);
354
	dnd_info = container->details->dnd_info;
355
	
356
	/* Delete old selection list. */
357
358
	nautilus_drag_destroy_selection_list (dnd_info->drag_info.selection_list);
	dnd_info->drag_info.selection_list = NULL;
359

360
	/* Delete old shadow if any. */
361
	if (dnd_info->shadow != NULL) {
362
363
		/* FIXME bugzilla.eazel.com 2484: 
		 * Is a destroy really sufficient here? Who does the unref? */
Ettore Perazzoli's avatar
Ettore Perazzoli committed
364
		gtk_object_destroy (GTK_OBJECT (dnd_info->shadow));
365
	}
366
367

	/* Build the selection list and the shadow. */
368
369
	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);
370
	nautilus_icon_container_position_shadow (container, x, y);
Ettore Perazzoli's avatar
Ettore Perazzoli committed
371
372
}

373
374
375
376
377
378
379
380
/** 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.
*/

381
static void
382
383
384
385
386
387
388
389
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
390
{
391
    	NautilusDragInfo *drag_info;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
392

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

395
396
	drag_info->got_drop_data_type = TRUE;
	drag_info->data_type = info;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
397

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


	/* this is the second use case of this callback.
	 * we have to do the actual work for the drop.
	 */
418
	if (drag_info->drop_occured) {
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441

		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),
442
				 (char*) data->data, x, y);
443
444
			gtk_drag_finish (context, FALSE, FALSE, time);
			break;
445
446
447
448
449
450
451
		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;

452
453
454
455
456
457
458
459
460
461
462
463
		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;
	}

464
465
}

466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
/* FIXME bugzilla.eazel.com 7445: Needs to become a shared function */
static void
get_data_on_first_target_we_support (GtkWidget *widget, GdkDragContext *context, guint32 time)
{
	GList *target;

	if (drop_types_list == NULL)
		drop_types_list = gtk_target_list_new (drop_types,
						       NAUTILUS_N_ELEMENTS (drop_types));

	for (target = context->targets; target != NULL; target = target->next) {
		guint dummy_info;
		GdkAtom target_atom = GPOINTER_TO_UINT (target->data);

		if (gtk_target_list_find (drop_types_list, 
					  target_atom,
					  &dummy_info)) {
			gtk_drag_get_data (GTK_WIDGET (widget), context,
					   target_atom,
					   time);
			break;
		}
	}
}

491
static void
492
493
494
nautilus_icon_container_ensure_drag_data (NautilusIconContainer *container,
					  GdkDragContext *context,
					  guint32 time)
495
{
496
	NautilusIconDndInfo *dnd_info;
497

498
	dnd_info = container->details->dnd_info;
499

500
	if (!dnd_info->drag_info.got_drop_data_type) {
501
		get_data_on_first_target_we_support (GTK_WIDGET (container), context, time);
502
	}
503
504
}

Ettore Perazzoli's avatar
Ettore Perazzoli committed
505
static void
506
507
508
drag_end_callback (GtkWidget *widget,
		   GdkDragContext *context,
		   gpointer data)
Ettore Perazzoli's avatar
Ettore Perazzoli committed
509
{
510
511
	NautilusIconContainer *container;
	NautilusIconDndInfo *dnd_info;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
512

513
	container = NAUTILUS_ICON_CONTAINER (widget);
514
	dnd_info = container->details->dnd_info;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
515

516
517
	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
518
519
}

Pavel Cisler's avatar
Pavel Cisler committed
520
521
522
523
524
static NautilusIcon *
nautilus_icon_container_item_at (NautilusIconContainer *container,
				 int x, int y)
{
	GList *p;
525
	int size;
Pavel Cisler's avatar
Pavel Cisler committed
526
	ArtDRect point;
527
	ArtIRect canvas_point;
Pavel Cisler's avatar
Pavel Cisler committed
528

529
530
531
532
533
	/* 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
534
535
	point.x0 = x;
	point.y0 = y;
536
537
	point.x1 = x + size;
	point.y1 = y + size;
Pavel Cisler's avatar
Pavel Cisler committed
538
539
540
541

	for (p = container->details->icons; p != NULL; p = p->next) {
		NautilusIcon *icon;
		icon = p->data;
542
543
544
545
		
		nautilus_gnome_canvas_world_to_canvas_rectangle (GNOME_CANVAS_ITEM (icon->item)->canvas, &point, &canvas_point);
		
		if (nautilus_icon_canvas_item_hit_test_rectangle (icon->item, &canvas_point)) {
Pavel Cisler's avatar
Pavel Cisler committed
546
547
548
			return icon;
		}
	}
549
	
Pavel Cisler's avatar
Pavel Cisler committed
550
551
552
553
554
555
556
557
558
559
	return NULL;
}

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

	/* get the URI associated with the container */
	uri = NULL;
560
	gtk_signal_emit_by_name (GTK_OBJECT (container), "get_container_uri", &uri);
Pavel Cisler's avatar
Pavel Cisler committed
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
	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);
578
579
580
581
582
583
584
585
586
	
	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
587
588
589
590
591
	g_free (container_uri_string);
	
	return result;
}

592
/* handle dropped tile images */
593
static void
594
receive_dropped_tile_image (NautilusIconContainer *container, GtkSelectionData *data)
595
{
596
597
	g_assert (data != NULL);
	nautilus_background_receive_dropped_background_image
598
		(nautilus_get_widget_background (GTK_WIDGET (container)), data->data);
599
600
}

601
602
603
604
605
606
607
608
609
610
/* 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;
	
611
	g_assert (keyword != NULL);
612
613
614
615

	/* 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);
616
	if (drop_target_icon == NULL) {
617
		return;
618
619
	}

620
621
	/* FIXME bugzilla.eazel.com 2485: 
	 * This does not belong in the icon code.
622
623
624
625
626
	 * 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.
	 */
627
	uri = nautilus_icon_container_get_icon_uri (container, drop_target_icon);
628
629
	file = nautilus_file_get (uri);
	g_free (uri);
630
	
631
632
	nautilus_drag_file_receive_dropped_keyword (file, keyword);

633
	nautilus_file_unref (file);
634
635
636
	nautilus_icon_container_update_icon (container, drop_target_icon);
}

637
638
639
640
/* handle dropped uri list */
static void
receive_dropped_uri_list (NautilusIconContainer *container, char *uri_list, int x, int y)
{
641
	/* FIXME bugzilla.eazel.com 5080:
642
643
	 * this needs a better name - it's link/desktop specific 
	 */
644
645
646
647
648
	GList *li, *files;
	int argc;
	char **argv;
	int i;
	
649
650
651
	if (uri_list == NULL) {
		return;
	}
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670

	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
671
672
673
674
675
676
677
678
679
680
681
682
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);

683
684
	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
685
686
687
688
		/* not yet */
		return TRUE;
	}

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

691
	nautilus_drag_autoscroll_calculate_delta (widget, &x_scroll_delta, &y_scroll_delta);
692
693
694
695
	if (x_scroll_delta == 0 && y_scroll_delta == 0) {
		/* no work */
		return TRUE;
	}
696
697
698
699
700
701
702

	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
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732

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

733
	gtk_widget_draw (widget, &exposed_area);
Pavel Cisler's avatar
Pavel Cisler committed
734
735
736
737
738
739
740

	return TRUE;
}

static void
set_up_auto_scroll_if_needed (NautilusIconContainer *container)
{
741
742
743
744
	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
745
746
747
748
749
}

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

753
754
755
756
static gboolean
confirm_switch_to_manual_layout (NautilusIconContainer *container)
{
	const char *message;
757
	GnomeDialog *dialog;
758

Darin Adler's avatar
Darin Adler committed
759
760
761
762
	/* 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?
763
764
	 */
	if (nautilus_icon_container_has_stored_icon_positions (container)) {
765
		if (nautilus_g_list_exactly_one_item (container->details->dnd_info->drag_info.selection_list)) {
766
			message = _("This folder uses automatic layout. "
767
768
			"Do you want to switch to manual layout and leave this item where you dropped it? "
			"This will clobber the stored manual layout.");
769
		} else {
770
			message = _("This folder uses automatic layout. "
771
772
			"Do you want to switch to manual layout and leave these items where you dropped them? "
			"This will clobber the stored manual layout.");
773
774
		}
	} else {
775
		if (nautilus_g_list_exactly_one_item (container->details->dnd_info->drag_info.selection_list)) {
776
			message = _("This folder uses automatic layout. "
777
			"Do you want to switch to manual layout and leave this item where you dropped it?");
778
		} else {
779
			message = _("This folder uses automatic layout. "
780
			"Do you want to switch to manual layout and leave these items where you dropped them?");
781
782
783
		}
	}

784
785
786
787
	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)));
788
789

	return gnome_dialog_run (dialog) == GNOME_OK;
790
791
}

792
793
794
795
796
static void
handle_local_move (NautilusIconContainer *container,
		   double world_x, double world_y)
{
	GList *moved_icons, *p;
797
	DragSelectionItem *item;
798
799
800
	NautilusIcon *icon;

	if (container->details->auto_layout) {
801
802
803
804
		if (!confirm_switch_to_manual_layout (container)) {
			return;
		}
		nautilus_icon_container_freeze_icon_positions (container);
805
806
	}

807
	/* Move and select the icons. */
808
	moved_icons = NULL;
809
	for (p = container->details->dnd_info->drag_info.selection_list; p != NULL; p = p->next) {
810
811
812
813
		item = p->data;
		
		icon = nautilus_icon_container_get_icon_by_uri
			(container, item->uri);
814

815
816
817
818
819
		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,
820
				 TRUE, TRUE);
821
822
823
824
825
		}
		moved_icons = g_list_prepend (moved_icons, icon);
	}		
	nautilus_icon_container_select_list_unselect_others
		(container, moved_icons);
826
827
	/* Might have been moved in a way that requires adjusting scroll region. */
	nautilus_icon_container_update_scroll_region (container);
828
829
830
831
832
833
834
	g_list_free (moved_icons);
}

static void
handle_nonlocal_move (NautilusIconContainer *container,
		      GdkDragContext *context,
		      int x, int y,
835
836
		      const char *target_uri,
		      gboolean icon_hit)
837
838
{
	GList *source_uris, *p;
839
840
	GArray *source_item_locations;
	int index;
841

842
	if (container->details->dnd_info->drag_info.selection_list == NULL) {
843
844
845
846
		return;
	}
	
	source_uris = NULL;
847
	for (p = container->details->dnd_info->drag_info.selection_list; p != NULL; p = p->next) {
848
		/* do a shallow copy of all the uri strings of the copied files */
849
		source_uris = g_list_prepend (source_uris, ((DragSelectionItem *)p->data)->uri);
850
851
852
	}
	source_uris = g_list_reverse (source_uris);
	
853
	source_item_locations = g_array_new (FALSE, TRUE, sizeof (GdkPoint));
854
	if (!icon_hit) {
855
856
857
		/* Drop onto a container. Pass along the item points to allow placing
		 * the items in their same relative positions in the new container.
		 */
858
859
860
861
862
863
864
865
866
		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;
867
868
		}
	}
869
		
870
871
872
873
874
875
876
	/* 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);
877

878
	g_list_free (source_uris);
879
	g_array_free (source_item_locations, TRUE);
880
881
}

882
883
884
885
886
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
887
{
Pavel Cisler's avatar
Pavel Cisler committed
888
889
	NautilusIcon *drop_target_icon;
	double world_x, world_y;
890
891
	NautilusFile *file;
	char *icon_uri;
892

893
	*icon_hit = FALSE;
894
	if (container->details->dnd_info->drag_info.selection_list == NULL) {
895
		return NULL;
896
	}
897

898
  	gnome_canvas_window_to_world (GNOME_CANVAS (container),
Pavel Cisler's avatar
Pavel Cisler committed
899
				      x, y, &world_x, &world_y);
900
	
901
902
	/* FIXME bugzilla.eazel.com 2485: 
	 * These "can_accept_items" tests need to be done by
903
904
905
906
	 * the icon view, not here. This file is not supposed to know
	 * that the target is a file.
	 */

907
	/* Find the item we hit with our drop, if any */	
Pavel Cisler's avatar
Pavel Cisler committed
908
	drop_target_icon = nautilus_icon_container_item_at (container, world_x, world_y);
909
910
911
912
913
	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);

914
			if (!nautilus_drag_can_accept_items (file, 
915
916
917
918
919
920
921
922
923
924
					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
925
926
	}

927
	if (drop_target_icon == NULL) {
928
929
930
931
932
		*icon_hit = FALSE;
		return get_container_uri (container);
	}
	
	*icon_hit = TRUE;
933
	return nautilus_icon_container_get_icon_drop_target_uri (container, drop_target_icon);
934
935
}

936
937
/* FIXME bugzilla.eazel.com 2485: This belongs in FMDirectoryView, not here. */
static gboolean
938
selection_includes_special_link (GList *selection_list)
939
940
941
{
	GList *node;
	char *uri, *local_path;
942
	gboolean link_in_selection;
943

944
	link_in_selection = FALSE;
945
946
947
948
949
950

	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);
951
952
953
		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));
954
955
		g_free (local_path);
		
956
		if (link_in_selection) {
957
958
959
960
			break;
		}
	}
	
961
	return link_in_selection;
962
963
}

964
965
966
967
968
969
970
971
972
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;
973
	GdkDragAction action;
974
975
976

	drop_target = NULL;

977
978
979
980
	if (container->details->dnd_info->drag_info.selection_list == NULL) {
		return;
	}

981
	if (context->action == GDK_ACTION_ASK) {
982
		/* FIXME bugzilla.eazel.com 2485: This belongs in FMDirectoryView, not here. */
983
		/* Check for special case items in selection list */
984
		if (selection_includes_special_link (container->details->dnd_info->drag_info.selection_list)) {
985
986
987
988
			/* We only want to move the trash */
			action = GDK_ACTION_MOVE;
		} else {
			action = GDK_ACTION_MOVE | GDK_ACTION_COPY | GDK_ACTION_LINK;
989
		}
990
		context->action = nautilus_drag_drop_action_ask (action);
991
	}
992

993
	if (context->action > 0) {
994
995
	  	gnome_canvas_window_to_world (GNOME_CANVAS (container),
					      x, y, &world_x, &world_y);
996

997
998
		drop_target = nautilus_icon_container_find_drop_target (container, 
			context, x, y, &icon_hit);
Pavel Cisler's avatar
Pavel Cisler committed
999

1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
		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 {
1012
			handle_nonlocal_move (container, context, world_x, world_y, drop_target, icon_hit);
1013
		}
1014
	}
Pavel Cisler's avatar
Pavel Cisler committed
1015

1016
	g_free (drop_target);
1017
1018
	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
1019
1020
}

1021
1022
1023
1024
1025
1026
1027
1028
1029
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;
1030
1031
1032
1033
	NautilusIcon *icon;
	double world_x, world_y;
	
	icon_hit = FALSE;
1034
1035
1036
1037
	if (!container->details->dnd_info->drag_info.got_drop_data_type) {
		/* drag_data_received_callback didn't get called yet */
		return;
	}
1038

1039
1040
1041
1042
1043
1044
1045
	/* 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 */
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
	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;
		}
1060
		nautilus_drag_default_drop_action_for_icons (context, drop_target, 
1061
1062
			container->details->dnd_info->drag_info.selection_list, 
			default_action, non_default_action);
1063
		g_free (drop_target);
1064
		break;
1065

1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
	/* 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 */		
1079
1080
	case NAUTILUS_ICON_DND_COLOR:
	case NAUTILUS_ICON_DND_BGIMAGE:
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
		if (icon == NULL) {
			*default_action = context->suggested_action;
			*non_default_action = context->suggested_action;
		} else {
			*default_action = 0;
			*non_default_action = 0;
		}
	
		break;
	
1091
	case NAUTILUS_ICON_DND_URI_LIST:
1092
		*default_action = context->suggested_action;
1093
		*non_default_action = context->suggested_action;
1094
		break;
1095
	
1096
	default:
1097
	}
1098

1099
1100
}

1101
static void
1102
1103
1104
1105
1106
set_drop_target (NautilusIconContainer *container,
		 NautilusIcon *icon)
{
	NautilusIcon *old_icon;

1107
1108
1109
	/* Check if current drop target changed, update icon drop
	 * higlight if needed.
	 */
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
	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,
1123
1124
1125
				      GdkDragContext *context,
				      int x, int y)
{
1126
	NautilusIcon *icon;
1127
1128
1129
	double world_x, world_y;
	
	g_assert (NAUTILUS_IS_ICON_CONTAINER (container));
1130
1131
	if ((container->details->dnd_info->drag_info.selection_list == NULL) 
	   && (container->details->dnd_info->drag_info.data_type != NAUTILUS_ICON_DND_KEYWORD)) {