nautilus-icon-dnd.c 36.4 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 <math.h>
34
35
#include <string.h>
#include <stdio.h>
36
37
#include <gdk/gdkx.h>
#include <gdk/gdkkeysyms.h>
38
#include <gtk/gtksignal.h>
Pavel Cisler's avatar
Pavel Cisler committed
39
#include <gtk/gtkmain.h>
Pavel Cisler's avatar
Pavel Cisler committed
40
#include <libgnomevfs/gnome-vfs-uri.h>
41
42
43
#include <libgnome/gnome-i18n.h>
#include <libgnomeui/gnome-stock.h>
#include <libgnomeui/gnome-canvas-rect-ellipse.h>
44
45

#include "nautilus-background.h"
46
#include <libnautilus-extensions/nautilus-gdk-pixbuf-extensions.h>
47
#include "nautilus-glib-extensions.h"
48
#include "nautilus-gtk-extensions.h"
49
#include "nautilus-gtk-macros.h"
50
#include "nautilus-gnome-extensions.h"
51
#include "nautilus-graphic-effects.h"
52
#include "nautilus-stock-dialogs.h"
53
#include "nautilus-string.h"
54

55
#include "nautilus-icon-private.h"
56

57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
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);
72

73
74
75
76
static void     nautilus_icon_container_receive_dropped_icons    (NautilusIconContainer *container,
								  GdkDragContext *context,
								  int x, int y);
static void     receive_dropped_tile_image                       (NautilusIconContainer *container, 
77
								  GtkSelectionData *data);
78
79
80
81
82
83
84
85
86
static void     receive_dropped_keyword                          (NautilusIconContainer *container, 
								  char* keyword, 
								  int x, 
								  int y);
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
87
static GtkTargetEntry drag_types [] = {
88
	{ NAUTILUS_ICON_DND_GNOME_ICON_LIST_TYPE, 0, NAUTILUS_ICON_DND_GNOME_ICON_LIST },
89
90
	{ NAUTILUS_ICON_DND_URI_LIST_TYPE, 0, NAUTILUS_ICON_DND_URI_LIST },
	{ NAUTILUS_ICON_DND_URL_TYPE, 0, NAUTILUS_ICON_DND_URL }
Ettore Perazzoli's avatar
Ettore Perazzoli committed
91
92
93
};

static GtkTargetEntry drop_types [] = {
94
	{ NAUTILUS_ICON_DND_GNOME_ICON_LIST_TYPE, 0, NAUTILUS_ICON_DND_GNOME_ICON_LIST },
95
96
	{ NAUTILUS_ICON_DND_URI_LIST_TYPE, 0, NAUTILUS_ICON_DND_URI_LIST },
	{ NAUTILUS_ICON_DND_URL_TYPE, 0, NAUTILUS_ICON_DND_URL },
97
	{ NAUTILUS_ICON_DND_COLOR_TYPE, 0, NAUTILUS_ICON_DND_COLOR },
98
99
	{ 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
100
101
};

102

103
104
/* special reserved name for the erase emblem */
#define ERASE_KEYWORD "erase"
105

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

118
119
120
	if (list == NULL) {
		return NULL;
	}
Ettore Perazzoli's avatar
Ettore Perazzoli committed
121

122
	/* if we're only dragging a single item, don't worry about the shadow */
123
	if (list->next == NULL) {
124
		return NULL;
125
	}
126
		
127
	stipple = container->details->dnd_info->drag_info.stipple;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
128
129
130
131
132
133
134
135
	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.  */

John Sullivan's avatar
John Sullivan committed
136
137
138
	/* FIXME bugzilla.eazel.com 624: 
	 * Does this work properly if the window is scrolled? 
	 */
Ettore Perazzoli's avatar
Ettore Perazzoli committed
139
140
141
142
143
144
145
146
147
148
149
150
151
	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));
	
152
	pixels_per_unit = canvas->pixels_per_unit;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
153
	for (p = list; p != NULL; p = p->next) {
154
		DragSelectionItem *item;
155
		int x1, y1, x2, y2;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
156
157
158

		item = p->data;

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

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

	return GNOME_CANVAS_ITEM (group);
}

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

204

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

207
208
209
210
211
212
213
214
215
/* 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
216
{
217
218
219
220
221
	IconGetDataBinderContext *context;
	ArtDRect world_rect;
	ArtIRect window_rect;
	char *uri;
	NautilusIconContainer *container;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
222

223
	context = (IconGetDataBinderContext *)data;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
224

225
	g_assert (NAUTILUS_IS_ICON_CONTAINER (context->iterator_context));
Ettore Perazzoli's avatar
Ettore Perazzoli committed
226

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

229
230
231
232
233
234
235
236
237
	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
238
239
	}

240
241
242
243
244
245
246
	/* 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
247

248
249
250
	g_free (uri);

	return TRUE;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
251
252
}

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

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

/* Adaptor function used with nautilus_icon_container_each_selected_icon
 * to help iterate over all selected items, passing uris, x,y,w and h
 * 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
284

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

288
289
290
291
	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
292
293
}

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

307
308
309
310
311
312
	/* 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
313
314
}

315

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

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

325
	shadow = container->details->dnd_info->shadow;
326
	if (shadow == NULL) {
327
		return;
328
	}
329
330
331
332

	gnome_canvas_window_to_world (GNOME_CANVAS (container),
				      x, y, &world_x, &world_y);
	set_shadow_position (shadow, world_x, world_y);
333
	gnome_canvas_item_show (shadow);
334
335
336
}

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

344
	container = NAUTILUS_ICON_CONTAINER (widget);
345
	dnd_info = container->details->dnd_info;
346

347
	/* Delete old selection list. */
348
349
	nautilus_drag_destroy_selection_list (dnd_info->drag_info.selection_list);
	dnd_info->drag_info.selection_list = NULL;
350

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

	/* Build the selection list and the shadow. */
359
360
	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);
361
	nautilus_icon_container_position_shadow (container, x, y);
Ettore Perazzoli's avatar
Ettore Perazzoli committed
362
363
}

364
365
366
367
368
369
370
371
/** 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.
*/

372
static void
373
374
375
376
377
378
379
380
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
381
{
382
    	NautilusDragInfo *drag_info;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
383

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

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

389
	switch (info) {
390
	case NAUTILUS_ICON_DND_GNOME_ICON_LIST:
391
		nautilus_icon_container_dropped_icon_feedback (widget, data, x, y);
392
		break;
393
	case NAUTILUS_ICON_DND_COLOR:
394
	case NAUTILUS_ICON_DND_BGIMAGE:	
395
	case NAUTILUS_ICON_DND_KEYWORD:	
396
		/* Save the data so we can do the actual work on drop. */
397
398
		g_assert (drag_info->selection_data == NULL);
		drag_info->selection_data = nautilus_gtk_selection_data_copy_deep (data);
399
400
		break;
	default:
401
		break;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
402
	}
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446


	/* this is the second use case of this callback.
	 * we have to do the actual work for the drop.
	 */
	if (drag_info->drop_occured == TRUE) {

		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),
				 (char *)data, x, y);
			gtk_drag_finish (context, FALSE, FALSE, time);
			break;
		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;
	}

447
448
449
}

static void
450
451
452
nautilus_icon_container_ensure_drag_data (NautilusIconContainer *container,
					  GdkDragContext *context,
					  guint32 time)
453
{
454
	NautilusIconDndInfo *dnd_info;
455

456
	dnd_info = container->details->dnd_info;
457

458
	if (!dnd_info->drag_info.got_drop_data_type) {
459
460
461
		gtk_drag_get_data (GTK_WIDGET (container), context,
				   GPOINTER_TO_INT (context->targets->data),
				   time);
462
	}
463
464
}

Ettore Perazzoli's avatar
Ettore Perazzoli committed
465
static void
466
467
468
drag_end_callback (GtkWidget *widget,
		   GdkDragContext *context,
		   gpointer data)
Ettore Perazzoli's avatar
Ettore Perazzoli committed
469
{
470
471
	NautilusIconContainer *container;
	NautilusIconDndInfo *dnd_info;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
472

473
	container = NAUTILUS_ICON_CONTAINER (widget);
474
	dnd_info = container->details->dnd_info;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
475

476
477
	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
478
479
}

Pavel Cisler's avatar
Pavel Cisler committed
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
static NautilusIcon *
nautilus_icon_container_item_at (NautilusIconContainer *container,
				 int x, int y)
{
	GList *p;
	ArtDRect point;

	/* hit test a single pixel rectangle */
	point.x0 = x;
	point.y0 = y;
	point.x1 = x + 1;
	point.y1 = y + 1;

	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;
	gtk_signal_emit_by_name (GTK_OBJECT (container),
			 "get_container_uri",
			 &uri);
	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);
532
	result = nautilus_drag_items_local (container_uri_string, items);
Pavel Cisler's avatar
Pavel Cisler committed
533
534
535
536
537
	g_free (container_uri_string);
	
	return result;
}

538
/* handle dropped tile images */
539
static void
540
receive_dropped_tile_image (NautilusIconContainer *container, GtkSelectionData *data)
541
{
542
543
	g_assert (data != NULL);
	nautilus_background_receive_dropped_background_image
544
		(nautilus_get_widget_background (GTK_WIDGET (container)), data->data);
545
546
}

547
548
549
550
551
552
553
554
555
556
557
/* handle dropped keywords */
static void
receive_dropped_keyword (NautilusIconContainer *container, char* keyword, int x, int y)
{
	GList *keywords, *word;
	char *uri;
	double world_x, world_y;

	NautilusIcon *drop_target_icon;
	NautilusFile *file;
	
558
	g_assert (keyword != NULL);
559
560
561
562

	/* 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);
563
	if (drop_target_icon == NULL) {
564
		return;
565
566
	}

567
568
	/* FIXME bugzilla.eazel.com 2485: 
	 * This does not belong in the icon code.
569
570
571
572
573
	 * 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.
	 */
574
	uri = nautilus_icon_container_get_icon_uri (container, drop_target_icon);
575
576
	file = nautilus_file_get (uri);
	g_free (uri);
577
	
578
579
580
	/* special case the erase emblem */
	if (!nautilus_strcmp (keyword, ERASE_KEYWORD)) {
		keywords = NULL;
581
	} else {
582
583
584
585
586
587
588
589
590
		keywords = nautilus_file_get_keywords (file);
		word = g_list_find_custom (keywords, keyword, (GCompareFunc) strcmp);
		if (word == NULL) {
			keywords = g_list_append (keywords, g_strdup (keyword));
		} else {
			keywords = g_list_remove_link (keywords, word);
			g_free (word->data);
			g_list_free (word);
		}
591
	}
592
	
593
	nautilus_file_set_keywords (file, keywords);
594
	nautilus_file_unref (file);
595
596
597
	nautilus_icon_container_update_icon (container, drop_target_icon);
}

Pavel Cisler's avatar
Pavel Cisler committed
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
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);

	if (container->details->waiting_to_autoscroll
		&& container->details->start_auto_scroll_in < nautilus_get_system_time()) {
		/* not yet */
		return TRUE;
	}

	container->details->waiting_to_autoscroll = FALSE;

618
	nautilus_drag_autoscroll_calculate_delta (widget, &x_scroll_delta, &y_scroll_delta);
Pavel Cisler's avatar
Pavel Cisler committed
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650

	nautilus_icon_container_scroll (container, (int)x_scroll_delta, (int)y_scroll_delta);

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

651
	gtk_widget_draw (widget, &exposed_area);
Pavel Cisler's avatar
Pavel Cisler committed
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678

	return TRUE;
}

static void
set_up_auto_scroll_if_needed (NautilusIconContainer *container)
{
	if (container->details->auto_scroll_timeout_id == 0) {
		container->details->waiting_to_autoscroll = TRUE;
		container->details->start_auto_scroll_in = nautilus_get_system_time() 
			+ AUTOSCROLL_INITIAL_DELAY;
		container->details->auto_scroll_timeout_id = gtk_timeout_add
				(AUTOSCROLL_TIMEOUT_INTERVAL,
				 auto_scroll_timeout_callback,
			 	 container);
	}
}

static void
stop_auto_scroll (NautilusIconContainer *container)
{
	if (container->details->auto_scroll_timeout_id) {
		gtk_timeout_remove (container->details->auto_scroll_timeout_id);
		container->details->auto_scroll_timeout_id = 0;
	}
}

679
680
681
682
683
static gboolean
confirm_switch_to_manual_layout (NautilusIconContainer *container)
{
	const char *message;

Darin Adler's avatar
Darin Adler committed
684
685
686
687
	/* 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?
688
689
	 */
	if (nautilus_icon_container_has_stored_icon_positions (container)) {
690
		if (nautilus_g_list_exactly_one_item (container->details->dnd_info->drag_info.selection_list)) {
691
692
693
			message = _("This directory uses automatic layout. "
			"Do you want to switch to manual layout and leave this item where you dropped it? "
			"This will clobber the stored manual layout.");
694
		} else {
695
696
697
			message = _("This directory uses automatic layout. "
			"Do you want to switch to manual layout and leave these items where you dropped them? "
			"This will clobber the stored manual layout.");
698
699
		}
	} else {
700
		if (nautilus_g_list_exactly_one_item (container->details->dnd_info->drag_info.selection_list)) {
701
702
			message = _("This directory uses automatic layout. "
			"Do you want to switch to manual layout and leave this item where you dropped it?");
703
		} else {
704
705
			message = _("This directory uses automatic layout. "
			"Do you want to switch to manual layout and leave these items where you dropped them?");
706
707
708
709
710
711
712
713
714
		}
	}

	return nautilus_simple_dialog
		(GTK_WIDGET (container), message,
		 _("Switch to Manual Layout?"),
		 _("Switch"), GNOME_STOCK_BUTTON_CANCEL, NULL) == 0;
}

715
716
717
718
719
static void
handle_local_move (NautilusIconContainer *container,
		   double world_x, double world_y)
{
	GList *moved_icons, *p;
720
	DragSelectionItem *item;
721
722
723
	NautilusIcon *icon;

	if (container->details->auto_layout) {
724
725
726
727
		if (!confirm_switch_to_manual_layout (container)) {
			return;
		}
		nautilus_icon_container_freeze_icon_positions (container);
728
729
	}

730
	/* Move and select the icons. */
731
	moved_icons = NULL;
732
	for (p = container->details->dnd_info->drag_info.selection_list; p != NULL; p = p->next) {
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
		item = p->data;
		
		icon = nautilus_icon_container_get_icon_by_uri
			(container, item->uri);
		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,
				 TRUE);
		}
		moved_icons = g_list_prepend (moved_icons, icon);
	}		
	nautilus_icon_container_select_list_unselect_others
		(container, moved_icons);
748
749
	/* Might have been moved in a way that requires adjusting scroll region */
	nautilus_icon_container_update_scroll_region (container);
750
751
752
753
754
755
756
	g_list_free (moved_icons);
}

static void
handle_nonlocal_move (NautilusIconContainer *container,
		      GdkDragContext *context,
		      int x, int y,
757
758
		      const char *target_uri,
		      gboolean icon_hit)
759
760
761
762
763
{
	GList *source_uris, *p;
	GdkPoint *source_item_locations;
	int i;

764
	if (container->details->dnd_info->drag_info.selection_list == NULL) {
765
766
767
768
		return;
	}
	
	source_uris = NULL;
769
	for (p = container->details->dnd_info->drag_info.selection_list; p != NULL; p = p->next) {
770
		/* do a shallow copy of all the uri strings of the copied files */
771
		source_uris = g_list_prepend (source_uris, ((DragSelectionItem *)p->data)->uri);
772
773
774
775
	}
	source_uris = g_list_reverse (source_uris);
	
	source_item_locations = NULL;
776
	if (!icon_hit) {
777
778
779
780
		/* Drop onto a container. Pass along the item points to allow placing
		 * the items in their same relative positions in the new container.
		 */
		source_item_locations = g_new (GdkPoint, g_list_length (source_uris));
781
		for (i = 0, p = container->details->dnd_info->drag_info.selection_list;
782
783
784
785
		     p != NULL; i++, p = p->next) {
			/* FIXME bugzilla.eazel.com 626:
			 * subtract the original click coordinates from each point here
			 */
786
787
			source_item_locations[i].x = ((DragSelectionItem *)p->data)->icon_x;
			source_item_locations[i].y = ((DragSelectionItem *)p->data)->icon_y;
788
789
		}
	}
790
		
791
792
793
794
795
796
797
798
799
800
801
	/* 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);
	g_list_free (source_uris);
	g_free (source_item_locations);
}

802
803
804
805
806
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
807
{
Pavel Cisler's avatar
Pavel Cisler committed
808
809
	NautilusIcon *drop_target_icon;
	double world_x, world_y;
810
811
	NautilusFile *file;
	char *icon_uri;
812

813
	if (container->details->dnd_info->drag_info.selection_list == NULL) {
814
		return NULL;
815
	}
816

817
  	gnome_canvas_window_to_world (GNOME_CANVAS (container),
Pavel Cisler's avatar
Pavel Cisler committed
818
819
				      x, y, &world_x, &world_y);

820
821
	/* FIXME bugzilla.eazel.com 2485: 
	 * These "can_accept_items" tests need to be done by
822
823
824
825
	 * the icon view, not here. This file is not supposed to know
	 * that the target is a file.
	 */

826
	/* Find the item we hit with our drop, if any */	
Pavel Cisler's avatar
Pavel Cisler committed
827
	drop_target_icon = nautilus_icon_container_item_at (container, world_x, world_y);
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
	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);

			if ( !nautilus_drag_can_accept_items (file, 
					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
844
845
	}

846
	if (drop_target_icon == NULL) {
847
848
849
850
851
		*icon_hit = FALSE;
		return get_container_uri (container);
	}
	
	*icon_hit = TRUE;
852
853

	return nautilus_icon_container_get_icon_drop_target_uri (container, drop_target_icon);
854
855
856
857
858
859
860
861
862
863
864
}

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;
865
866
867

	drop_target = NULL;

868
869
870
871
	if (container->details->dnd_info->drag_info.selection_list == NULL) {
		return;
	}

872
873
874
875
	if (context->action == GDK_ACTION_ASK) {
		context->action = nautilus_drag_drop_action_ask 
			(GDK_ACTION_MOVE | GDK_ACTION_COPY | GDK_ACTION_LINK);
	}
876

877
	if (context->action > 0) {
878
879
	  	gnome_canvas_window_to_world (GNOME_CANVAS (container),
					      x, y, &world_x, &world_y);
880

881
882
		drop_target = nautilus_icon_container_find_drop_target (container, 
			context, x, y, &icon_hit);
Pavel Cisler's avatar
Pavel Cisler committed
883

884
885
886
887
888
889
890
891
892
893
894
895
896
897
		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 {
			handle_nonlocal_move (container, context, x, y, drop_target, icon_hit);
		}
898
	}
Pavel Cisler's avatar
Pavel Cisler committed
899

900
	g_free (drop_target);
901
902
	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
903
904
}

905
906
907
908
909
910
911
912
913
914
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;

915
916
917
918
	if (!container->details->dnd_info->drag_info.got_drop_data_type) {
		/* drag_data_received_callback didn't get called yet */
		return;
	}
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933

	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;
		}
934
		nautilus_drag_default_drop_action_for_icons (context, drop_target, 
935
936
937
			container->details->dnd_info->drag_info.selection_list, 
			default_action, non_default_action);
		break;
938

939
940
941
	case NAUTILUS_ICON_DND_COLOR:
	case NAUTILUS_ICON_DND_BGIMAGE:
	case NAUTILUS_ICON_DND_KEYWORD:
942
		*default_action = context->suggested_action;
943
		*non_default_action = context->suggested_action;
944
		break;
945

946
	default:
947
	}
948

949
950
}

951
static void
952
953
954
955
956
set_drop_target (NautilusIconContainer *container,
		 NautilusIcon *icon)
{
	NautilusIcon *old_icon;

957
958
959
	/* Check if current drop target changed, update icon drop
	 * higlight if needed.
	 */
960
961
962
963
964
965
966
967
968
969
970
971
972
	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,
973
974
975
				      GdkDragContext *context,
				      int x, int y)
{
976
	NautilusIcon *icon;
977
978
979
	double world_x, world_y;
	
	g_assert (NAUTILUS_IS_ICON_CONTAINER (container));
980
981
	if ((container->details->dnd_info->drag_info.selection_list == NULL) 
	   && (container->details->dnd_info->drag_info.data_type != NAUTILUS_ICON_DND_KEYWORD)) {
982
983
984
985
986
987
988
		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. */
989
	icon = nautilus_icon_container_item_at (container, world_x, world_y);
990

991
992
	/* FIXME bugzilla.eazel.com 2485: 
	 * These "can_accept_items" tests need to be done by
993
994
995
996
	 * the icon view, not here. This file is not supposed to know
	 * that the target is a file.
	 */

997
	/* Find if target icon accepts our drop. */
998
	if (icon != NULL 
999
		&& (container->details->dnd_info->drag_info.data_type != NAUTILUS_ICON_DND_KEYWORD) 
1000
1001
1002
		&& !nautilus_drag_can_accept_items 
			(nautilus_file_get (
				nautilus_icon_container_get_icon_uri (container, icon)), 
1003
			container->details->dnd_info->drag_info.selection_list)) {
1004
		icon = NULL;
1005
1006
	}

1007
	set_drop_target (container, icon);
1008
1009
}

Ettore Perazzoli's avatar
Ettore Perazzoli committed
1010
static void
1011
nautilus_icon_container_free_drag_data (NautilusIconContainer *container)
Ettore Perazzoli's avatar
Ettore Perazzoli committed
1012
{
1013
	NautilusIconDndInfo *dnd_info;
1014
	
1015
	dnd_info = container->details->dnd_info;
1016
	
1017
	dnd_info->drag_info.got_drop_data_type = FALSE;
1018
	
Ettore Perazzoli's avatar
Ettore Perazzoli committed
1019
	if (dnd_info->shadow != NULL) {
1020
		gtk_object_destroy (GTK_OBJECT (dnd_info->shadow));
Ettore Perazzoli's avatar
Ettore Perazzoli committed
1021
1022
1023
		dnd_info->shadow = NULL;
	}

1024
1025
1026
	if (dnd_info->drag_info.selection_data != NULL) {
		nautilus_gtk_selection_data_free_deep (dnd_info->drag_info.selection_data);
		dnd_info->drag_info.selection_data = NULL;
1027
1028
1029
1030
	}
}

static void
1031
1032
1033
1034
drag_leave_callback (GtkWidget *widget,
		     GdkDragContext *context,
		     guint32 time,
		     gpointer data)
1035
{
1036
	NautilusIconDndInfo *dnd_info;
1037

1038
	dnd_info = NAUTILUS_ICON_CONTAINER (widget)->details->dnd_info;
1039
	
1040
1041
	if (dnd_info->shadow != NULL)
		gnome_canvas_item_hide (dnd_info->shadow);
1042
	
Pavel Cisler's avatar
Pavel Cisler committed
1043
	stop_auto_scroll (NAUTILUS_ICON_CONTAINER (widget));
1044
	nautilus_icon_container_free_drag_data(NAUTILUS_ICON_CONTAINER (widget));
1045
}
1046

Ettore Perazzoli's avatar
Ettore Perazzoli committed
1047
void
1048
1049
nautilus_icon_dnd_init (NautilusIconContainer *container,
			GdkBitmap *stipple)
Ettore Perazzoli's avatar
Ettore Perazzoli committed
1050
1051
{
	g_return_if_fail (container != NULL);
1052
	g_return_if_fail (NAUTILUS_IS_ICON_CONTAINER (container));
Ettore Perazzoli's avatar
Ettore Perazzoli committed
1053
1054


1055
1056
1057
	container->details->dnd_info = g_new0 (NautilusIconDndInfo, 1);
	nautilus_drag_init (&container->details->dnd_info->drag_info,
		drag_types, NAUTILUS_N_ELEMENTS (drag_types), stipple);
Ettore Perazzoli's avatar
Ettore Perazzoli committed
1058

1059
1060
1061
1062
	/* Set up the widget as a drag destination.
	 * (But not a source, as drags starting from this widget will be
         * implemented by dealing with events manually.)
	 */
Ettore Perazzoli's avatar
Ettore Perazzoli committed
1063
1064
	gtk_drag_dest_set  (GTK_WIDGET (container),
			    0,
1065
			    drop_types, NAUTILUS_N_ELEMENTS (drop_types),
1066
1067
			    GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK
			    | GDK_ACTION_ASK);
Ettore Perazzoli's avatar
Ettore Perazzoli committed
1068

1069
	/* Messages for outgoing drag. */
Ettore Perazzoli's avatar
Ettore Perazzoli committed
1070
	gtk_signal_connect (GTK_OBJECT (container), "drag_data_get",
1071
			    GTK_SIGNAL_FUNC (drag_data_get_callback), NULL);
Ettore Perazzoli's avatar
Ettore Perazzoli committed
1072
	gtk_signal_connect (GTK_OBJECT (container), "drag_end",
1073
			    GTK_SIGNAL_FUNC (drag_end_callback), NULL);
1074
1075

	/* Messages for incoming drag. */
Ettore Perazzoli's avatar
Ettore Perazzoli committed
1076
	gtk_signal_connect (GTK_OBJECT (container), "drag_data_received",
1077
			    GTK_SIGNAL_FUNC (drag_data_received_callback), NULL);
1078
	gtk_signal_connect (GTK_OBJECT (container), "drag_motion",
1079
			    GTK_SIGNAL_FUNC (drag_motion_callback), NULL);
Ettore Perazzoli's avatar
Ettore Perazzoli committed
1080
	gtk_signal_connect (GTK_OBJECT (container), "drag_drop",
1081
			    GTK_SIGNAL_FUNC (drag_drop_callback), NULL);
Ettore Perazzoli's avatar
Ettore Perazzoli committed
1082
	gtk_signal_connect (GTK_OBJECT (container), "drag_leave",
1083
			    GTK_SIGNAL_FUNC (drag_leave_callback), NULL);
Ettore Perazzoli's avatar
Ettore Perazzoli committed
1084
1085
1086
1087

}

void
1088
nautilus_icon_dnd_fini (NautilusIconContainer *container)
Ettore Perazzoli's avatar
Ettore Perazzoli committed
1089
1090
{
	g_return_if_fail (container != NULL);
1091
	g_return_if_fail (NAUTILUS_IS_ICON_CONTAINER (container));
1092
	g_return_if_fail (container->details->dnd_info != NULL);
Ettore Perazzoli's avatar
Ettore Perazzoli committed
1093

Pavel Cisler's avatar
Pavel Cisler committed
1094
	stop_auto_scroll (container);
1095
	if (container->details->dnd_info->shadow != NULL) {
1096
1097
		/* FIXME bugzilla.eazel.com 2484: 
		 * Is a destroy really sufficient here? Who does the unref? */
1098
		gtk_object_destroy (GTK_OBJECT (container->details->dnd_info->shadow));
1099
	}
Ettore Perazzoli's avatar
Ettore Perazzoli committed
1100

1101
	nautilus_drag_finalize (&container->details->dnd_info->drag_info);
Ettore Perazzoli's avatar
Ettore Perazzoli committed
1102
1103
1104
}

void
1105
1106
1107
1108
nautilus_icon_dnd_begin_drag (NautilusIconContainer *container,
			      GdkDragAction actions,
			      int button,
			      GdkEventMotion *event)
Ettore Perazzoli's avatar
Ettore Perazzoli committed
1109
{
1110
	NautilusIconDndInfo *dnd_info;
1111
	GnomeCanvas *canvas;
1112
	GdkDragContext *context;
1113
	GdkPixbuf *pixbuf, *transparent_pixbuf;
1114
1115
	GdkPixmap *pixmap_for_dragged_file;
	GdkBitmap *mask_for_dragged_file;
1116
	int x_offset, y_offset;