1 /* -*- Mode: C; indent-tabs-mode: f; c-basic-offset: 4; tab-width: 4 -*- */
2 
3 /* nemo-dnd.c - Common Drag & drop handling code shared by the icon container
4    and the list view.
5 
6    Copyright (C) 2000, 2001 Eazel, Inc.
7 
8    The Gnome Library is free software; you can redistribute it and/or
9    modify it under the terms of the GNU Library General Public License as
10    published by the Free Software Foundation; either version 2 of the
11    License, or (at your option) any later version.
12 
13    The Gnome Library is distributed in the hope that it will be useful,
14    but WITHOUT ANY WARRANTY; without even the implied warranty of
15    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16    Library General Public License for more details.
17 
18    You should have received a copy of the GNU Library General Public
19    License along with the Gnome Library; see the file COPYING.LIB.  If not,
20    write to the Free Software Foundation, Inc., 51 Franklin Street - Suite 500,
21    Boston, MA 02110-1335, USA.
22 
23    Authors: Pavel Cisler <pavel@eazel.com>,
24    	    Ettore Perazzoli <ettore@gnu.org>
25 */
26 
27 #include <config.h>
28 #include "nemo-dnd.h"
29 
30 #include "nemo-program-choosing.h"
31 #include "nemo-link.h"
32 #include <eel/eel-glib-extensions.h>
33 #include <eel/eel-gtk-extensions.h>
34 #include <eel/eel-string.h>
35 #include <eel/eel-vfs-extensions.h>
36 #include <gtk/gtk.h>
37 #include <glib/gi18n.h>
38 #include <libnemo-private/nemo-file-utilities.h>
39 #include <stdio.h>
40 #include <string.h>
41 
42 /* a set of defines stolen from the eel-icon-dnd.c file.
43  * These are in microseconds.
44  */
45 #define AUTOSCROLL_TIMEOUT_INTERVAL 100
46 #define AUTOSCROLL_INITIAL_DELAY 100000
47 
48 /* drag this close to the view edge to start auto scroll*/
49 #define AUTO_SCROLL_MARGIN 30
50 
51 /* the smallest amount of auto scroll used when we just enter the autoscroll
52  * margin
53  */
54 #define MIN_AUTOSCROLL_DELTA 5
55 
56 /* the largest amount of auto scroll used when we are right over the view
57  * edge
58  */
59 #define MAX_AUTOSCROLL_DELTA 50
60 
61 void
nemo_drag_init(NemoDragInfo * drag_info,const GtkTargetEntry * drag_types,int drag_type_count,gboolean add_text_targets)62 nemo_drag_init (NemoDragInfo     *drag_info,
63 		    const GtkTargetEntry *drag_types,
64 		    int                   drag_type_count,
65 		    gboolean              add_text_targets)
66 {
67 	drag_info->target_list = gtk_target_list_new (drag_types,
68 						   drag_type_count);
69 
70 	if (add_text_targets) {
71 		gtk_target_list_add_text_targets (drag_info->target_list,
72 						  NEMO_ICON_DND_TEXT);
73 	}
74 
75 	drag_info->drop_occured = FALSE;
76 	drag_info->need_to_destroy = FALSE;
77     drag_info->source_fs = NULL;
78     drag_info->can_delete_source = FALSE;
79 }
80 
81 void
nemo_drag_finalize(NemoDragInfo * drag_info)82 nemo_drag_finalize (NemoDragInfo *drag_info)
83 {
84 	gtk_target_list_unref (drag_info->target_list);
85 	nemo_drag_destroy_selection_list (drag_info->selection_list);
86     g_free (drag_info->source_fs);
87     drag_info->can_delete_source = FALSE;
88 
89 	g_free (drag_info);
90 }
91 
92 
93 /* Functions to deal with NemoDragSelectionItems.  */
94 
95 NemoDragSelectionItem *
nemo_drag_selection_item_new(void)96 nemo_drag_selection_item_new (void)
97 {
98 	return g_new0 (NemoDragSelectionItem, 1);
99 }
100 
101 static void
drag_selection_item_destroy(NemoDragSelectionItem * item)102 drag_selection_item_destroy (NemoDragSelectionItem *item)
103 {
104 	g_free (item->uri);
105 	g_free (item);
106 }
107 
108 void
nemo_drag_destroy_selection_list(GList * list)109 nemo_drag_destroy_selection_list (GList *list)
110 {
111 	GList *p;
112 
113 	if (list == NULL)
114 		return;
115 
116 	for (p = list; p != NULL; p = p->next)
117 		drag_selection_item_destroy (p->data);
118 
119 	g_list_free (list);
120 }
121 
122 char **
nemo_drag_uri_array_from_selection_list(const GList * selection_list)123 nemo_drag_uri_array_from_selection_list (const GList *selection_list)
124 {
125 	GList *uri_list;
126 	char **uris;
127 
128 	uri_list = nemo_drag_uri_list_from_selection_list (selection_list);
129 	uris = nemo_drag_uri_array_from_list (uri_list);
130 	g_list_free_full (uri_list, g_free);
131 
132 	return uris;
133 }
134 
135 GList *
nemo_drag_uri_list_from_selection_list(const GList * selection_list)136 nemo_drag_uri_list_from_selection_list (const GList *selection_list)
137 {
138 	NemoDragSelectionItem *selection_item;
139 	GList *uri_list;
140 	const GList *l;
141 
142 	uri_list = NULL;
143 	for (l = selection_list; l != NULL; l = l->next) {
144 		selection_item = (NemoDragSelectionItem *) l->data;
145 		if (selection_item->uri != NULL) {
146 			uri_list = g_list_prepend (uri_list, g_strdup (selection_item->uri));
147 		}
148 	}
149 
150 	return g_list_reverse (uri_list);
151 }
152 
153 char **
nemo_drag_uri_array_from_list(const GList * uri_list)154 nemo_drag_uri_array_from_list (const GList *uri_list)
155 {
156 	const GList *l;
157 	char **uris;
158 	int i;
159 
160 	if (uri_list == NULL) {
161 		return NULL;
162 	}
163 
164 	uris = g_new0 (char *, g_list_length ((GList *) uri_list));
165 	for (i = 0, l = uri_list; l != NULL; l = l->next) {
166 		uris[i++] = g_strdup ((char *) l->data);
167 	}
168 	uris[i] = NULL;
169 
170 	return uris;
171 }
172 
173 GList *
nemo_drag_uri_list_from_array(const char ** uris)174 nemo_drag_uri_list_from_array (const char **uris)
175 {
176 	GList *uri_list;
177 	int i;
178 
179 	if (uris == NULL) {
180 		return NULL;
181 	}
182 
183 	uri_list = NULL;
184 
185 	for (i = 0; uris[i] != NULL; i++) {
186 		uri_list = g_list_prepend (uri_list, g_strdup (uris[i]));
187 	}
188 
189 	return g_list_reverse (uri_list);
190 }
191 
192 GList *
nemo_drag_build_selection_list(GtkSelectionData * data)193 nemo_drag_build_selection_list (GtkSelectionData *data)
194 {
195 	GList *result;
196 	const guchar *p, *oldp;
197 	int size;
198 
199 	result = NULL;
200 	oldp = gtk_selection_data_get_data (data);
201 	size = gtk_selection_data_get_length (data);
202 
203 	while (size > 0) {
204 		NemoDragSelectionItem *item;
205 		guint len;
206 
207 		/* The list is in the form:
208 
209 		   name\rx:y:width:height\r\n
210 
211 		   The geometry information after the first \r is optional.  */
212 
213 		/* 1: Decode name. */
214 
215 		p = memchr (oldp, '\r', size);
216 		if (p == NULL) {
217 			break;
218 		}
219 
220 		item = nemo_drag_selection_item_new ();
221 
222 		len = p - oldp;
223 
224 		item->uri = g_malloc (len + 1);
225 		memcpy (item->uri, oldp, len);
226 		item->uri[len] = 0;
227 
228 		p++;
229 		if (*p == '\n' || *p == '\0') {
230 			result = g_list_prepend (result, item);
231 			if (p == 0) {
232 				g_warning ("Invalid x-special/gnome-icon-list data received: "
233 					   "missing newline character.");
234 				break;
235 			} else {
236 				oldp = p + 1;
237 				continue;
238 			}
239 		}
240 
241 		size -= p - oldp;
242 		oldp = p;
243 
244 		/* 2: Decode geometry information.  */
245 
246 		item->got_icon_position = sscanf ((const gchar *) p, "%d:%d:%d:%d%*s",
247 						  &item->icon_x, &item->icon_y,
248 						  &item->icon_width, &item->icon_height) == 4;
249 		if (!item->got_icon_position) {
250 			g_warning ("Invalid x-special/gnome-icon-list data received: "
251 				   "invalid icon position specification.");
252 		}
253 
254 		result = g_list_prepend (result, item);
255 
256 		p = memchr (p, '\r', size);
257 		if (p == NULL || p[1] != '\n') {
258 			g_warning ("Invalid x-special/gnome-icon-list data received: "
259 				   "missing newline character.");
260 			if (p == NULL) {
261 				break;
262 			}
263 		} else {
264 			p += 2;
265 		}
266 
267 		size -= p - oldp;
268 		oldp = p;
269 	}
270 
271 	return g_list_reverse (result);
272 }
273 
274 static gboolean
nemo_drag_file_local_internal(const char * target_uri_string,const char * first_source_uri)275 nemo_drag_file_local_internal (const char *target_uri_string,
276 				   const char *first_source_uri)
277 {
278 	/* check if the first item on the list has target_uri_string as a parent
279 	 * FIXME:
280 	 * we should really test each item but that would be slow for large selections
281 	 * and currently dropped items can only be from the same container
282 	 */
283 	GFile *target, *item, *parent;
284 	gboolean result;
285 
286 	result = FALSE;
287 
288 	target = g_file_new_for_uri (target_uri_string);
289 
290 	/* get the parent URI of the first item in the selection */
291 	item = g_file_new_for_uri (first_source_uri);
292 	parent = g_file_get_parent (item);
293 	g_object_unref (item);
294 
295 	if (parent != NULL) {
296 		result = g_file_equal (parent, target);
297 		g_object_unref (parent);
298 	}
299 
300 	g_object_unref (target);
301 
302 	return result;
303 }
304 
305 gboolean
nemo_drag_uris_local(const char * target_uri,const GList * source_uri_list)306 nemo_drag_uris_local (const char *target_uri,
307 			  const GList *source_uri_list)
308 {
309 	/* must have at least one item */
310 	g_assert (source_uri_list);
311 
312 	return nemo_drag_file_local_internal (target_uri, source_uri_list->data);
313 }
314 
315 gboolean
nemo_drag_items_local(const char * target_uri_string,const GList * selection_list)316 nemo_drag_items_local (const char *target_uri_string,
317 			   const GList *selection_list)
318 {
319 	/* must have at least one item */
320 	g_assert (selection_list);
321 
322 	return nemo_drag_file_local_internal (target_uri_string,
323 						  ((NemoDragSelectionItem *)selection_list->data)->uri);
324 }
325 
326 gboolean
nemo_drag_items_in_trash(const GList * selection_list)327 nemo_drag_items_in_trash (const GList *selection_list)
328 {
329 	/* check if the first item on the list is in trash.
330 	 * FIXME:
331 	 * we should really test each item but that would be slow for large selections
332 	 * and currently dropped items can only be from the same container
333 	 */
334 	return eel_uri_is_trash (((NemoDragSelectionItem *)selection_list->data)->uri);
335 }
336 
337 gboolean
nemo_drag_items_on_desktop(const GList * selection_list)338 nemo_drag_items_on_desktop (const GList *selection_list)
339 {
340 	char *uri;
341 	GFile *desktop, *item, *parent;
342 	gboolean result;
343 
344 	/* check if the first item on the list is in trash.
345 	 * FIXME:
346 	 * we should really test each item but that would be slow for large selections
347 	 * and currently dropped items can only be from the same container
348 	 */
349 	uri = ((NemoDragSelectionItem *)selection_list->data)->uri;
350 	if (eel_uri_is_desktop (uri)) {
351 		return TRUE;
352 	}
353 
354 	desktop = nemo_get_desktop_location ();
355 
356 	item = g_file_new_for_uri (uri);
357 	parent = g_file_get_parent (item);
358 	g_object_unref (item);
359 
360 	result = FALSE;
361 
362 	if (parent) {
363 		result = g_file_equal (desktop, parent);
364 		g_object_unref (parent);
365 	}
366 	g_object_unref (desktop);
367 
368 	return result;
369 
370 }
371 
372 GdkDragAction
nemo_drag_default_drop_action_for_netscape_url(GdkDragContext * context)373 nemo_drag_default_drop_action_for_netscape_url (GdkDragContext *context)
374 {
375 	/* Mozilla defaults to copy, but unless thats the
376 	   only allowed thing (enforced by ctrl) we want to LINK */
377 	if (gdk_drag_context_get_suggested_action (context) == GDK_ACTION_COPY &&
378 	    gdk_drag_context_get_actions (context) != GDK_ACTION_COPY) {
379 		return GDK_ACTION_LINK;
380 	} else if (gdk_drag_context_get_suggested_action (context) == GDK_ACTION_MOVE) {
381 		/* Don't support move */
382 		return GDK_ACTION_COPY;
383 	}
384 
385 	return gdk_drag_context_get_suggested_action (context);
386 }
387 
388 static gboolean
check_same_fs(NemoFile * target_file,NemoFile * source_file,const gchar * source_fs_for_desktop)389 check_same_fs (NemoFile    *target_file,
390                NemoFile    *source_file,
391                const gchar *source_fs_for_desktop)
392 {
393     char *target_id, *source_id;
394     gboolean result;
395 
396     result = FALSE;
397 
398     if (target_file != NULL && source_file != NULL) {
399         source_id = nemo_file_get_filesystem_id (source_file);
400         target_id = nemo_file_get_filesystem_id (target_file);
401 
402         if (source_id != NULL && target_id != NULL) {
403             result = (strcmp (source_id, target_id) == 0);
404         }
405 
406         g_free (source_id);
407         g_free (target_id);
408     } else if (target_file != NULL && source_fs_for_desktop != NULL) {
409         target_id = nemo_file_get_filesystem_id (target_file);
410 
411         if (target_id != NULL) {
412             result = (strcmp (source_fs_for_desktop, target_id) == 0);
413         }
414 
415         g_free (target_id);
416     }
417 
418     return result;
419 }
420 
421 static gboolean
source_is_deletable(GFile * file)422 source_is_deletable (GFile *file)
423 {
424 	NemoFile *naut_file;
425 	gboolean ret;
426 
427 	/* if there's no a cached NemoFile, it returns NULL */
428 	naut_file = nemo_file_get_existing (file);
429 	if (naut_file == NULL) {
430 		return FALSE;
431 	}
432 
433 	ret = nemo_file_can_delete (naut_file);
434 	nemo_file_unref (naut_file);
435 
436 	return ret;
437 }
438 
439 static gboolean
uri_contains_desktop(const gchar * uri)440 uri_contains_desktop (const gchar *uri)
441 {
442     gchar *real_desktop_uri;
443 
444     if (eel_uri_is_desktop (uri)) {
445         return TRUE;
446     }
447 
448     real_desktop_uri = nemo_get_desktop_directory_uri ();
449 
450     if (g_str_has_prefix (uri, real_desktop_uri)) {
451         g_free (real_desktop_uri);
452         return TRUE;
453     }
454 
455     return FALSE;
456 }
457 
458 void
nemo_drag_default_drop_action_for_icons(GdkDragContext * context,const char * target_uri_string,const GList * items,int * action,gchar ** source_fs,gboolean * can_delete_source)459 nemo_drag_default_drop_action_for_icons (GdkDragContext *context,
460                                          const char     *target_uri_string,
461                                          const GList    *items,
462                                          int            *action,
463                                          gchar         **source_fs,
464                                          gboolean       *can_delete_source)
465 {
466 	gboolean same_fs;
467 	gboolean target_is_source_parent;
468 	gboolean source_deletable;
469 	const char *dropped_uri;
470 	GFile *target, *dropped, *dropped_directory;
471 	GdkDragAction actions;
472 	NemoFile *dropped_file, *target_file;
473     gboolean fav_target, fav_item;
474 
475 	if (target_uri_string == NULL) {
476 		*action = 0;
477 		return;
478 	}
479 
480     dropped_uri = ((NemoDragSelectionItem *)items->data)->uri;
481 
482     fav_target = eel_uri_is_favorite (target_uri_string);
483     fav_item = eel_uri_is_favorite (dropped_uri);
484 
485     if (fav_item && fav_target) {
486         *action = 0;
487         return;
488     }
489 
490     if (fav_item != fav_target) {
491         *action = GDK_ACTION_COPY;
492         return;
493     }
494 
495 	actions = gdk_drag_context_get_actions (context) & (GDK_ACTION_MOVE | GDK_ACTION_COPY);
496 	if (actions == 0) {
497 		 /* We can't use copy or move, just go with the suggested action. */
498 		*action = gdk_drag_context_get_suggested_action (context);
499 		return;
500 	}
501 
502 	if (gdk_drag_context_get_suggested_action (context) == GDK_ACTION_ASK) {
503 		/* Don't override ask */
504 		*action = gdk_drag_context_get_suggested_action (context);
505 		return;
506 	}
507 
508     dropped_file = nemo_file_get_existing_by_uri (dropped_uri);
509 
510     /* To/from desktop preparation - since we are separate processes, we don't have the full filesystem
511      * info on the source and destination - we only have the destination info.  Creating a NemoFile for it
512      * is an async operation, so here we'll grab the source filesystem type and store it for the duration of
513      * the drag.  We can use it to compare below with the destination type to ensure a proper move action -
514      * (we default to copy when we can't confirm source and destination fs types are the same.)
515      */
516     if (dropped_file == NULL && (uri_contains_desktop (target_uri_string) || uri_contains_desktop (dropped_uri))) {
517         if (*source_fs == NULL) {
518             GFile *source_file;
519 
520             source_file = g_file_new_for_uri (dropped_uri);
521 
522             if (g_file_is_native (source_file)) {
523                 GFileInfo *fs_info;
524                 GError *error = NULL;
525 
526                 fs_info = g_file_query_info (source_file,
527                                              G_FILE_ATTRIBUTE_ID_FILESYSTEM "," G_FILE_ATTRIBUTE_ACCESS_CAN_DELETE,
528                                              G_FILE_QUERY_INFO_NONE,
529                                              NULL,
530                                              &error);
531 
532                 if (error != NULL) {
533                     g_warning ("Cannot fetch filesystem type for drag involving the desktop: %s", error->message);
534                     g_clear_error (&error);
535                 } else {
536                     if (g_file_info_has_attribute (fs_info, G_FILE_ATTRIBUTE_ID_FILESYSTEM)) {
537                         *source_fs = g_strdup (g_file_info_get_attribute_string (fs_info,
538                                                                                  G_FILE_ATTRIBUTE_ID_FILESYSTEM));
539                     } else {
540                         *source_fs = NULL;
541                     }
542 
543                     if (g_file_info_has_attribute (fs_info, G_FILE_ATTRIBUTE_ACCESS_CAN_DELETE)) {
544                         *can_delete_source = g_file_info_get_attribute_boolean (fs_info,
545                                                                                 G_FILE_ATTRIBUTE_ACCESS_CAN_DELETE);
546                     } else {
547                         *can_delete_source = FALSE;
548                     }
549                 }
550 
551                 g_object_unref (fs_info);
552             }
553 
554             g_object_unref (source_file);
555         }
556     }
557 
558 	target_file = nemo_file_get_existing_by_uri (target_uri_string);
559 
560 	/*
561 	 * Check for trash URI.  We do a find_directory for any Trash directory.
562 	 * Passing 0 permissions as gnome-vfs would override the permissions
563 	 * passed with 700 while creating .Trash directory
564 	 */
565 	if (eel_uri_is_trash (target_uri_string)) {
566 		/* Only move to Trash */
567 		if (actions & GDK_ACTION_MOVE) {
568 			*action = GDK_ACTION_MOVE;
569 		}
570 
571 		nemo_file_unref (dropped_file);
572 		nemo_file_unref (target_file);
573 		return;
574 
575 	} else if (dropped_file != NULL && nemo_file_is_launcher (dropped_file)) {
576 		if (actions & GDK_ACTION_MOVE) {
577 			*action = GDK_ACTION_MOVE;
578 		}
579 		nemo_file_unref (dropped_file);
580 		nemo_file_unref (target_file);
581 		return;
582 	} else if (eel_uri_is_desktop (target_uri_string)) {
583 		target = nemo_get_desktop_location ();
584 
585 		nemo_file_unref (target_file);
586 		target_file = nemo_file_get (target);
587 
588 		if (eel_uri_is_desktop (dropped_uri)) {
589 			/* Only move to Desktop icons */
590 			if (actions & GDK_ACTION_MOVE) {
591 				*action = GDK_ACTION_MOVE;
592 			}
593 
594 			g_object_unref (target);
595 			nemo_file_unref (dropped_file);
596 			nemo_file_unref (target_file);
597 			return;
598 		}
599 	} else if (target_file != NULL && nemo_file_is_archive (target_file)) {
600 		*action = GDK_ACTION_COPY;
601 
602 		nemo_file_unref (dropped_file);
603 		nemo_file_unref (target_file);
604 		return;
605 	} else {
606 		target = g_file_new_for_uri (target_uri_string);
607 	}
608 
609 	same_fs = check_same_fs (target_file, dropped_file, *source_fs);
610 
611 	nemo_file_unref (dropped_file);
612 	nemo_file_unref (target_file);
613 
614 	/* Compare the first dropped uri with the target uri for same fs match. */
615 	dropped = g_file_new_for_uri (dropped_uri);
616 	dropped_directory = g_file_get_parent (dropped);
617 	target_is_source_parent = FALSE;
618 	if (dropped_directory != NULL) {
619 		/* If the dropped file is already in the same directory but
620 		   is in another filesystem we still want to move, not copy
621 		   as this is then just a move of a mountpoint to another
622 		   position in the dir */
623 		target_is_source_parent = g_file_equal (dropped_directory, target);
624 		g_object_unref (dropped_directory);
625 	}
626 	source_deletable = source_is_deletable (dropped);
627 
628 	if ((same_fs && (source_deletable || *can_delete_source)) || target_is_source_parent ||
629 	    g_file_has_uri_scheme (dropped, "trash")) {
630 		if (actions & GDK_ACTION_MOVE) {
631 			*action = GDK_ACTION_MOVE;
632 		} else {
633 			*action = gdk_drag_context_get_suggested_action (context);
634 		}
635 	} else {
636 		if (actions & GDK_ACTION_COPY) {
637 			*action = GDK_ACTION_COPY;
638 		} else {
639 			*action = gdk_drag_context_get_suggested_action (context);
640 		}
641 	}
642 
643 	g_object_unref (target);
644 	g_object_unref (dropped);
645 
646 }
647 
648 GdkDragAction
nemo_drag_default_drop_action_for_uri_list(GdkDragContext * context,const char * target_uri_string)649 nemo_drag_default_drop_action_for_uri_list (GdkDragContext *context,
650 						const char *target_uri_string)
651 {
652 	if (eel_uri_is_trash (target_uri_string) && (gdk_drag_context_get_actions (context) & GDK_ACTION_MOVE)) {
653 		/* Only move to Trash */
654 		return GDK_ACTION_MOVE;
655 	} else {
656 		return gdk_drag_context_get_suggested_action (context);
657 	}
658 }
659 
660 /* Encode a "x-special/gnome-icon-list" selection.
661    Along with the URIs of the dragged files, this encodes
662    the location and size of each icon relative to the cursor.
663 */
664 static void
add_one_gnome_icon(const char * uri,int x,int y,int w,int h,gpointer data)665 add_one_gnome_icon (const char *uri, int x, int y, int w, int h,
666 		    gpointer data)
667 {
668 	GString *result;
669 
670 	result = (GString *) data;
671 	g_string_append_printf (result, "%s\r%d:%d:%hu:%hu\r\n",
672 				uri, x, y, w, h);
673 }
674 
675 /*
676  * Cf. #48423
677  */
678 #ifdef THIS_WAS_REALLY_BROKEN
679 static gboolean
is_path_that_gnome_uri_list_extract_filenames_can_parse(const char * path)680 is_path_that_gnome_uri_list_extract_filenames_can_parse (const char *path)
681 {
682 	if (path == NULL || path [0] == '\0') {
683 		return FALSE;
684 	}
685 
686 	/* It strips leading and trailing spaces. So it can't handle
687 	 * file names with leading and trailing spaces.
688 	 */
689 	if (g_ascii_isspace (path [0])) {
690 		return FALSE;
691 	}
692 	if (g_ascii_isspace (path [strlen (path) - 1])) {
693 		return FALSE;
694 	}
695 
696 	/* # works as a comment delimiter, and \r and \n are used to
697 	 * separate the lines, so it can't handle file names with any
698 	 * of these.
699 	 */
700 	if (strchr (path, '#') != NULL
701 	    || strchr (path, '\r') != NULL
702 	    || strchr (path, '\n') != NULL) {
703 		return FALSE;
704 	}
705 
706 	return TRUE;
707 }
708 
709 /* Encode a "text/plain" selection; this is a broken URL -- just
710  * "file:" with a path after it (no escaping or anything). We are
711  * trying to make the old gnome_uri_list_extract_filenames function
712  * happy, so this is coded to its idiosyncrasises.
713  */
714 static void
add_one_compatible_uri(const char * uri,int x,int y,int w,int h,gpointer data)715 add_one_compatible_uri (const char *uri, int x, int y, int w, int h, gpointer data)
716 {
717 	GString *result;
718 	char *local_path;
719 
720 	result = (GString *) data;
721 
722 	/* For URLs that do not have a file: scheme, there's no harm
723 	 * in passing the real URL. But for URLs that do have a file:
724 	 * scheme, we have to send a URL that will work with the old
725 	 * gnome-libs function or nothing will be able to understand
726 	 * it.
727 	 */
728 	if (!g_str_has_prefix (uri, "file:")) {
729 		g_string_append (result, uri);
730 		g_string_append (result, "\r\n");
731 	} else {
732 		local_path = g_filename_from_uri (uri, NULL, NULL);
733 
734 		/* Check for characters that confuse the old
735 		 * gnome_uri_list_extract_filenames implementation, and just leave
736 		 * out any paths with those in them.
737 		 */
738 		if (is_path_that_gnome_uri_list_extract_filenames_can_parse (local_path)) {
739 			g_string_append (result, "file:");
740 			g_string_append (result, local_path);
741 			g_string_append (result, "\r\n");
742 		}
743 
744 		g_free (local_path);
745 	}
746 }
747 #endif
748 
749 static void
add_one_uri(const char * uri,int x,int y,int w,int h,gpointer data)750 add_one_uri (const char *uri, int x, int y, int w, int h, gpointer data)
751 {
752 	GString *result;
753 
754 	result = (GString *) data;
755 
756 	g_string_append (result, uri);
757 	g_string_append (result, "\r\n");
758 }
759 
760 /* Common function for drag_data_get_callback calls.
761  * Returns FALSE if it doesn't handle drag data */
762 gboolean
nemo_drag_drag_data_get(GtkWidget * widget,GdkDragContext * context,GtkSelectionData * selection_data,guint info,guint32 time,gpointer container_context,NemoDragEachSelectedItemIterator each_selected_item_iterator)763 nemo_drag_drag_data_get (GtkWidget *widget,
764 			GdkDragContext *context,
765 			GtkSelectionData *selection_data,
766 			guint info,
767 			guint32 time,
768 			gpointer container_context,
769 			NemoDragEachSelectedItemIterator each_selected_item_iterator)
770 {
771 	GString *result;
772 
773 	switch (info) {
774 	case NEMO_ICON_DND_GNOME_ICON_LIST:
775 		result = g_string_new (NULL);
776 		(* each_selected_item_iterator) (add_one_gnome_icon, container_context, result);
777 		break;
778 
779 	case NEMO_ICON_DND_URI_LIST:
780 	case NEMO_ICON_DND_TEXT:
781 		result = g_string_new (NULL);
782 		(* each_selected_item_iterator) (add_one_uri, container_context, result);
783 		break;
784 
785 	default:
786 		return FALSE;
787 	}
788 
789 	gtk_selection_data_set (selection_data,
790 				gtk_selection_data_get_target (selection_data),
791 				8, (guchar *) result->str, result->len);
792 	g_string_free (result, TRUE);
793 
794 	return TRUE;
795 }
796 
797 typedef struct
798 {
799 	GMainLoop *loop;
800 	GdkDragAction chosen;
801 } DropActionMenuData;
802 
803 static void
menu_deactivate_callback(GtkWidget * menu,gpointer data)804 menu_deactivate_callback (GtkWidget *menu,
805 			  gpointer   data)
806 {
807 	DropActionMenuData *damd;
808 
809 	damd = data;
810 
811 	if (g_main_loop_is_running (damd->loop))
812 		g_main_loop_quit (damd->loop);
813 }
814 
815 static void
drop_action_activated_callback(GtkWidget * menu_item,gpointer data)816 drop_action_activated_callback (GtkWidget  *menu_item,
817 				gpointer    data)
818 {
819 	DropActionMenuData *damd;
820 
821 	damd = data;
822 
823 	damd->chosen = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (menu_item),
824 							   "action"));
825 
826 	if (g_main_loop_is_running (damd->loop))
827 		g_main_loop_quit (damd->loop);
828 }
829 
830 static void
append_drop_action_menu_item(GtkWidget * menu,const char * text,GdkDragAction action,gboolean sensitive,DropActionMenuData * damd)831 append_drop_action_menu_item (GtkWidget          *menu,
832 			      const char         *text,
833 			      GdkDragAction       action,
834 			      gboolean            sensitive,
835 			      DropActionMenuData *damd)
836 {
837 	GtkWidget *menu_item;
838 
839 	menu_item = gtk_menu_item_new_with_mnemonic (text);
840 	gtk_widget_set_sensitive (menu_item, sensitive);
841 	gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);
842 
843 	g_object_set_data (G_OBJECT (menu_item),
844 			   "action",
845 			   GINT_TO_POINTER (action));
846 
847 	g_signal_connect (menu_item, "activate",
848 			  G_CALLBACK (drop_action_activated_callback),
849 			  damd);
850 
851 	gtk_widget_show (menu_item);
852 }
853 
854 /* Pops up a menu of actions to perform on dropped files */
855 GdkDragAction
nemo_drag_drop_action_ask(GtkWidget * widget,GdkDragAction actions)856 nemo_drag_drop_action_ask (GtkWidget *widget,
857 			       GdkDragAction actions)
858 {
859 	GtkWidget *menu;
860 	GtkWidget *menu_item;
861 	DropActionMenuData damd;
862 
863 	/* Create the menu and set the sensitivity of the items based on the
864 	 * allowed actions.
865 	 */
866 	menu = gtk_menu_new ();
867 	gtk_menu_set_screen (GTK_MENU (menu), gtk_widget_get_screen (widget));
868 
869 	append_drop_action_menu_item (menu, _("_Move Here"),
870 				      GDK_ACTION_MOVE,
871 				      (actions & GDK_ACTION_MOVE) != 0,
872 				      &damd);
873 
874 	append_drop_action_menu_item (menu, _("_Copy Here"),
875 				      GDK_ACTION_COPY,
876 				      (actions & GDK_ACTION_COPY) != 0,
877 				      &damd);
878 
879 	append_drop_action_menu_item (menu, _("_Link Here"),
880 				      GDK_ACTION_LINK,
881 				      (actions & GDK_ACTION_LINK) != 0,
882 				      &damd);
883 
884 	eel_gtk_menu_append_separator (GTK_MENU (menu));
885 
886 	menu_item = gtk_menu_item_new_with_mnemonic (_("Cancel"));
887 	gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);
888 	gtk_widget_show (menu_item);
889 
890 	damd.chosen = 0;
891 	damd.loop = g_main_loop_new (NULL, FALSE);
892 
893 	g_signal_connect (menu, "deactivate",
894 			  G_CALLBACK (menu_deactivate_callback),
895 			  &damd);
896 
897 	gtk_grab_add (menu);
898 
899 	gtk_menu_popup (GTK_MENU (menu), NULL, NULL,
900 			NULL, NULL, 0, GDK_CURRENT_TIME);
901 
902 	g_main_loop_run (damd.loop);
903 
904 	gtk_grab_remove (menu);
905 
906 	g_main_loop_unref (damd.loop);
907 
908 	g_object_ref_sink (menu);
909 	g_object_unref (menu);
910 
911 	return damd.chosen;
912 }
913 
914 gboolean
nemo_drag_autoscroll_in_scroll_region(GtkWidget * widget)915 nemo_drag_autoscroll_in_scroll_region (GtkWidget *widget)
916 {
917 	float x_scroll_delta, y_scroll_delta;
918 
919 	nemo_drag_autoscroll_calculate_delta (widget, &x_scroll_delta, &y_scroll_delta);
920 
921 	return x_scroll_delta != 0 || y_scroll_delta != 0;
922 }
923 
924 
925 void
nemo_drag_autoscroll_calculate_delta(GtkWidget * widget,float * x_scroll_delta,float * y_scroll_delta)926 nemo_drag_autoscroll_calculate_delta (GtkWidget *widget, float *x_scroll_delta, float *y_scroll_delta)
927 {
928 	GtkAllocation allocation;
929 	GdkDeviceManager *manager;
930 	GdkDevice *pointer;
931 	int x, y;
932 
933 	g_assert (GTK_IS_WIDGET (widget));
934 
935 	manager = gdk_display_get_device_manager (gtk_widget_get_display (widget));
936 	pointer = gdk_device_manager_get_client_pointer (manager);
937 	gdk_window_get_device_position (gtk_widget_get_window (widget), pointer,
938 					&x, &y, NULL);
939 
940 	/* Find out if we are anywhere close to the tree view edges
941 	 * to see if we need to autoscroll.
942 	 */
943 	*x_scroll_delta = 0;
944 	*y_scroll_delta = 0;
945 
946 	if (x < AUTO_SCROLL_MARGIN) {
947 		*x_scroll_delta = (float)(x - AUTO_SCROLL_MARGIN);
948 	}
949 
950 	gtk_widget_get_allocation (widget, &allocation);
951 	if (x > allocation.width - AUTO_SCROLL_MARGIN) {
952 		if (*x_scroll_delta != 0) {
953 			/* Already trying to scroll because of being too close to
954 			 * the top edge -- must be the window is really short,
955 			 * don't autoscroll.
956 			 */
957 			return;
958 		}
959 		*x_scroll_delta = (float)(x - (allocation.width - AUTO_SCROLL_MARGIN));
960 	}
961 
962 	if (y < AUTO_SCROLL_MARGIN) {
963 		*y_scroll_delta = (float)(y - AUTO_SCROLL_MARGIN);
964 	}
965 
966 	if (y > allocation.height - AUTO_SCROLL_MARGIN) {
967 		if (*y_scroll_delta != 0) {
968 			/* Already trying to scroll because of being too close to
969 			 * the top edge -- must be the window is really narrow,
970 			 * don't autoscroll.
971 			 */
972 			return;
973 		}
974 		*y_scroll_delta = (float)(y - (allocation.height - AUTO_SCROLL_MARGIN));
975 	}
976 
977 	if (*x_scroll_delta == 0 && *y_scroll_delta == 0) {
978 		/* no work */
979 		return;
980 	}
981 
982 	/* Adjust the scroll delta to the proper acceleration values depending on how far
983 	 * into the sroll margins we are.
984 	 * FIXME bugzilla.eazel.com 2486:
985 	 * we could use an exponential acceleration factor here for better feel
986 	 */
987 	if (*x_scroll_delta != 0) {
988 		*x_scroll_delta /= AUTO_SCROLL_MARGIN;
989 		*x_scroll_delta *= (MAX_AUTOSCROLL_DELTA - MIN_AUTOSCROLL_DELTA);
990 		*x_scroll_delta += MIN_AUTOSCROLL_DELTA;
991 	}
992 
993 	if (*y_scroll_delta != 0) {
994 		*y_scroll_delta /= AUTO_SCROLL_MARGIN;
995 		*y_scroll_delta *= (MAX_AUTOSCROLL_DELTA - MIN_AUTOSCROLL_DELTA);
996 		*y_scroll_delta += MIN_AUTOSCROLL_DELTA;
997 	}
998 
999 }
1000 
1001 
1002 
1003 void
nemo_drag_autoscroll_start(NemoDragInfo * drag_info,GtkWidget * widget,GSourceFunc callback,gpointer user_data)1004 nemo_drag_autoscroll_start (NemoDragInfo *drag_info,
1005 				GtkWidget        *widget,
1006 				GSourceFunc       callback,
1007 				gpointer          user_data)
1008 {
1009 	if (nemo_drag_autoscroll_in_scroll_region (widget)) {
1010 		if (drag_info->auto_scroll_timeout_id == 0) {
1011 			drag_info->waiting_to_autoscroll = TRUE;
1012 			drag_info->start_auto_scroll_in = g_get_monotonic_time ()
1013 				+ AUTOSCROLL_INITIAL_DELAY;
1014 
1015 			drag_info->auto_scroll_timeout_id = g_timeout_add
1016 				(AUTOSCROLL_TIMEOUT_INTERVAL,
1017 				 callback,
1018 			 	 user_data);
1019 		}
1020 	} else {
1021 		if (drag_info->auto_scroll_timeout_id != 0) {
1022 			g_source_remove (drag_info->auto_scroll_timeout_id);
1023 			drag_info->auto_scroll_timeout_id = 0;
1024 		}
1025 	}
1026 }
1027 
1028 void
nemo_drag_autoscroll_stop(NemoDragInfo * drag_info)1029 nemo_drag_autoscroll_stop (NemoDragInfo *drag_info)
1030 {
1031 	if (drag_info->auto_scroll_timeout_id != 0) {
1032 		g_source_remove (drag_info->auto_scroll_timeout_id);
1033 		drag_info->auto_scroll_timeout_id = 0;
1034 	}
1035 }
1036 
1037 gboolean
nemo_drag_selection_includes_special_link(GList * selection_list)1038 nemo_drag_selection_includes_special_link (GList *selection_list)
1039 {
1040 	GList *node;
1041 	char *uri;
1042 
1043 	for (node = selection_list; node != NULL; node = node->next) {
1044 		uri = ((NemoDragSelectionItem *) node->data)->uri;
1045 
1046 		if (eel_uri_is_desktop (uri)) {
1047 			return TRUE;
1048 		}
1049 	}
1050 
1051 	return FALSE;
1052 }
1053