1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
2 
3 /* caja-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 Mate 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 Mate 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 Mate Library; see the file COPYING.LIB.  If not,
20    write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
21    Boston, MA 02110-1301, USA.
22 
23    Authors: Pavel Cisler <pavel@eazel.com>,
24    	    Ettore Perazzoli <ettore@gnu.org>
25 */
26 
27 /* FIXME: This should really be back in Caja, not here in Eel. */
28 
29 #include <config.h>
30 #include <gtk/gtk.h>
31 #include <glib/gi18n.h>
32 #include <stdio.h>
33 #include <string.h>
34 
35 #include <eel/eel-glib-extensions.h>
36 #include <eel/eel-gtk-extensions.h>
37 #include <eel/eel-string.h>
38 #include <eel/eel-vfs-extensions.h>
39 
40 #include "caja-dnd.h"
41 #include "caja-program-choosing.h"
42 #include "caja-link.h"
43 #include "caja-window-slot-info.h"
44 #include "caja-window-info.h"
45 #include "caja-view.h"
46 #include "caja-file-utilities.h"
47 
48 /* a set of defines stolen from the eel-icon-dnd.c file.
49  * These are in microseconds.
50  */
51 #define AUTOSCROLL_TIMEOUT_INTERVAL 100
52 #define AUTOSCROLL_INITIAL_DELAY 100000
53 
54 /* drag this close to the view edge to start auto scroll*/
55 #define AUTO_SCROLL_MARGIN 30
56 
57 /* the smallest amount of auto scroll used when we just enter the autoscroll
58  * margin
59  */
60 #define MIN_AUTOSCROLL_DELTA 5
61 
62 /* the largest amount of auto scroll used when we are right over the view
63  * edge
64  */
65 #define MAX_AUTOSCROLL_DELTA 50
66 
67 void
caja_drag_init(CajaDragInfo * drag_info,const GtkTargetEntry * drag_types,int drag_type_count,gboolean add_text_targets)68 caja_drag_init (CajaDragInfo     *drag_info,
69                 const GtkTargetEntry *drag_types,
70                 int                   drag_type_count,
71                 gboolean              add_text_targets)
72 {
73     drag_info->target_list = gtk_target_list_new (drag_types,
74                              drag_type_count);
75 
76     if (add_text_targets)
77     {
78         gtk_target_list_add_text_targets (drag_info->target_list,
79                                           CAJA_ICON_DND_TEXT);
80     }
81 
82     drag_info->drop_occured = FALSE;
83     drag_info->need_to_destroy = FALSE;
84 }
85 
86 void
caja_drag_finalize(CajaDragInfo * drag_info)87 caja_drag_finalize (CajaDragInfo *drag_info)
88 {
89     gtk_target_list_unref (drag_info->target_list);
90     caja_drag_destroy_selection_list (drag_info->selection_list);
91 
92     g_free (drag_info);
93 }
94 
95 
96 /* Functions to deal with CajaDragSelectionItems.  */
97 
98 CajaDragSelectionItem *
caja_drag_selection_item_new(void)99 caja_drag_selection_item_new (void)
100 {
101     return g_new0 (CajaDragSelectionItem, 1);
102 }
103 
104 static void
drag_selection_item_destroy(CajaDragSelectionItem * item)105 drag_selection_item_destroy (CajaDragSelectionItem *item)
106 {
107     g_free (item->uri);
108     g_free (item);
109 }
110 
111 void
caja_drag_destroy_selection_list(GList * list)112 caja_drag_destroy_selection_list (GList *list)
113 {
114     GList *p;
115 
116     if (list == NULL)
117         return;
118 
119     for (p = list; p != NULL; p = p->next)
120         drag_selection_item_destroy (p->data);
121 
122     g_list_free (list);
123 }
124 
125 GList *
caja_drag_uri_list_from_selection_list(const GList * selection_list)126 caja_drag_uri_list_from_selection_list (const GList *selection_list)
127 {
128     GList *uri_list;
129     const GList *l;
130     CajaDragSelectionItem *selection_item = NULL;
131 
132     uri_list = NULL;
133     for (l = selection_list; l != NULL; l = l->next)
134     {
135         selection_item = (CajaDragSelectionItem *) l->data;
136         if (selection_item->uri != NULL)
137         {
138             uri_list = g_list_prepend (uri_list, g_strdup (selection_item->uri));
139         }
140     }
141 
142     return g_list_reverse (uri_list);
143 }
144 
145 GList *
caja_drag_uri_list_from_array(const char ** uris)146 caja_drag_uri_list_from_array (const char **uris)
147 {
148     GList *uri_list;
149     int i;
150 
151     if (uris == NULL)
152     {
153         return NULL;
154     }
155 
156     uri_list = NULL;
157 
158     for (i = 0; uris[i] != NULL; i++)
159     {
160         uri_list = g_list_prepend (uri_list, g_strdup (uris[i]));
161     }
162 
163     return g_list_reverse (uri_list);
164 }
165 
166 GList *
caja_drag_build_selection_list(GtkSelectionData * data)167 caja_drag_build_selection_list (GtkSelectionData *data)
168 {
169     GList *result;
170     const guchar *p, *oldp;
171     int size;
172 
173     result = NULL;
174     oldp = gtk_selection_data_get_data (data);
175     size = gtk_selection_data_get_length (data);
176 
177     while (size > 0)
178     {
179         CajaDragSelectionItem *item;
180         guint len;
181 
182         /* The list is in the form:
183 
184            name\rx:y:width:height\r\n
185 
186            The geometry information after the first \r is optional.  */
187 
188         /* 1: Decode name. */
189 
190         p = memchr (oldp, '\r', size);
191         if (p == NULL)
192         {
193             break;
194         }
195 
196         item = caja_drag_selection_item_new ();
197 
198         len = p - oldp;
199 
200         item->uri = g_malloc (len + 1);
201         memcpy (item->uri, oldp, len);
202         item->uri[len] = 0;
203 
204         p++;
205         if (*p == '\n' || *p == '\0')
206         {
207             result = g_list_prepend (result, item);
208             if (p == 0)
209             {
210                 g_warning ("Invalid x-special/mate-icon-list data received: "
211                            "missing newline character.");
212                 break;
213             }
214             else
215             {
216                 oldp = p + 1;
217                 continue;
218             }
219         }
220 
221         size -= p - oldp;
222         oldp = p;
223 
224         /* 2: Decode geometry information.  */
225 
226         item->got_icon_position = sscanf (p, "%d:%d:%d:%d%*s",
227                                           &item->icon_x, &item->icon_y,
228                                           &item->icon_width, &item->icon_height) == 4;
229         if (!item->got_icon_position)
230         {
231             g_warning ("Invalid x-special/mate-icon-list data received: "
232                        "invalid icon position specification.");
233         }
234 
235         result = g_list_prepend (result, item);
236 
237         p = memchr (p, '\r', size);
238         if (p == NULL || p[1] != '\n')
239         {
240             g_warning ("Invalid x-special/mate-icon-list data received: "
241                        "missing newline character.");
242             if (p == NULL)
243             {
244                 break;
245             }
246         }
247         else
248         {
249             p += 2;
250         }
251 
252         size -= p - oldp;
253         oldp = p;
254     }
255 
256     return g_list_reverse (result);
257 }
258 
259 static gboolean
caja_drag_file_local_internal(const char * target_uri_string,const char * first_source_uri)260 caja_drag_file_local_internal (const char *target_uri_string,
261                                const char *first_source_uri)
262 {
263     /* check if the first item on the list has target_uri_string as a parent
264      * FIXME:
265      * we should really test each item but that would be slow for large selections
266      * and currently dropped items can only be from the same container
267      */
268     GFile *target, *item, *parent;
269     gboolean result;
270 
271     result = FALSE;
272 
273     target = g_file_new_for_uri (target_uri_string);
274 
275     /* get the parent URI of the first item in the selection */
276     item = g_file_new_for_uri (first_source_uri);
277     parent = g_file_get_parent (item);
278     g_object_unref (item);
279 
280     if (parent != NULL)
281     {
282         result = g_file_equal (parent, target);
283         g_object_unref (parent);
284     }
285 
286     g_object_unref (target);
287 
288     return result;
289 }
290 
291 gboolean
caja_drag_uris_local(const char * target_uri,const GList * source_uri_list)292 caja_drag_uris_local (const char *target_uri,
293                       const GList *source_uri_list)
294 {
295     /* must have at least one item */
296     g_assert (source_uri_list);
297 
298     return caja_drag_file_local_internal (target_uri, source_uri_list->data);
299 }
300 
301 gboolean
caja_drag_items_local(const char * target_uri_string,const GList * selection_list)302 caja_drag_items_local (const char *target_uri_string,
303                        const GList *selection_list)
304 {
305     /* must have at least one item */
306     g_assert (selection_list);
307 
308     return caja_drag_file_local_internal (target_uri_string,
309                                           ((CajaDragSelectionItem *)selection_list->data)->uri);
310 }
311 
312 gboolean
caja_drag_items_on_desktop(const GList * selection_list)313 caja_drag_items_on_desktop (const GList *selection_list)
314 {
315     char *uri;
316     GFile *desktop, *item, *parent;
317     gboolean result;
318 
319     /* check if the first item on the list is in trash.
320      * FIXME:
321      * we should really test each item but that would be slow for large selections
322      * and currently dropped items can only be from the same container
323      */
324     uri = ((CajaDragSelectionItem *)selection_list->data)->uri;
325     if (eel_uri_is_desktop (uri))
326     {
327         return TRUE;
328     }
329 
330     desktop = caja_get_desktop_location ();
331 
332     item = g_file_new_for_uri (uri);
333     parent = g_file_get_parent (item);
334     g_object_unref (item);
335 
336     result = FALSE;
337 
338     if (parent)
339     {
340         result = g_file_equal (desktop, parent);
341         g_object_unref (parent);
342     }
343     g_object_unref (desktop);
344 
345     return result;
346 
347 }
348 
349 GdkDragAction
caja_drag_default_drop_action_for_netscape_url(GdkDragContext * context)350 caja_drag_default_drop_action_for_netscape_url (GdkDragContext *context)
351 {
352     /* Mozilla defaults to copy, however by default caja creates a link
353      * if available.
354      *
355      * Enforced action X:
356      * ((actions & X) != 0) && (suggested_action == X)
357      *    - GDK_ACTION_LINK enforced by Alt
358      *    - GDK_ACTION_MOVE enforced by Shift
359      *    - GDK_ACTION_COPY enforced by Ctrl
360      */
361     GdkDragAction suggested_action = gdk_drag_context_get_suggested_action (context);
362     GdkDragAction actions = gdk_drag_context_get_actions (context);
363     if ((actions & GDK_ACTION_LINK) == GDK_ACTION_LINK)
364     {
365         return GDK_ACTION_LINK;
366     }
367     else if (suggested_action == GDK_ACTION_MOVE)
368     {
369         /* Don't support move */
370         return GDK_ACTION_COPY;
371     }
372 
373     return suggested_action;
374 }
375 
376 static gboolean
check_same_fs(const char * target_uri,CajaFile * target_file,const char * dropped_uri,CajaFile * dropped_file)377 check_same_fs (const char *target_uri,
378                CajaFile *target_file,
379                const char *dropped_uri,
380                CajaFile *dropped_file)
381 {
382     gboolean result;
383 
384     result = FALSE;
385 
386     char *target_fs = NULL, *dropped_fs = NULL;
387 
388     if (target_file != NULL)
389     {
390         target_fs = caja_file_get_filesystem_id (target_file);
391     }
392     if (target_fs == NULL)
393     {
394         target_fs = caja_get_filesystem_id_by_uri (target_uri, TRUE);
395     }
396 
397     if (dropped_file != NULL && !caja_file_is_symbolic_link (dropped_file))
398     {
399         dropped_fs = caja_file_get_filesystem_id (dropped_file);
400     }
401     if (dropped_fs == NULL)
402     {
403         dropped_fs = caja_get_filesystem_id_by_uri (dropped_uri, FALSE);
404     }
405 
406     if (target_fs != NULL && dropped_fs != NULL)
407     {
408         result = (strcmp (target_fs, dropped_fs) == 0);
409     }
410 
411     g_free (target_fs);
412     g_free (dropped_fs);
413 
414     return result;
415 }
416 
417 static gboolean
source_is_deletable(GFile * file)418 source_is_deletable (GFile *file)
419 {
420     CajaFile *naut_file;
421     gboolean ret;
422 
423     /* if there's no a cached CajaFile, it returns NULL */
424     naut_file = caja_file_get_existing (file);
425     if (naut_file == NULL)
426     {
427         return FALSE;
428     }
429 
430     ret = caja_file_can_delete (naut_file);
431     caja_file_unref (naut_file);
432 
433     return ret;
434 }
435 
436 void
caja_drag_default_drop_action_for_icons(GdkDragContext * context,const char * target_uri_string,const GList * items,int * action)437 caja_drag_default_drop_action_for_icons (GdkDragContext *context,
438         const char *target_uri_string, const GList *items,
439         int *action)
440 {
441     gboolean same_fs;
442     gboolean target_is_source_parent;
443     gboolean source_deletable;
444     const char *dropped_uri;
445     GFile *target, *dropped, *dropped_directory;
446     GdkDragAction actions;
447     CajaFile *dropped_file, *target_file;
448 
449     if (target_uri_string == NULL)
450     {
451         *action = 0;
452         return;
453     }
454 
455     actions = gdk_drag_context_get_actions (context) & (GDK_ACTION_MOVE | GDK_ACTION_COPY);
456     if (actions == 0)
457     {
458         /* We can't use copy or move, just go with the suggested action. */
459         *action = gdk_drag_context_get_suggested_action (context);
460         return;
461     }
462 
463     if (gdk_drag_context_get_suggested_action (context) == GDK_ACTION_ASK)
464     {
465         /* Don't override ask */
466         *action = gdk_drag_context_get_suggested_action (context);
467         return;
468     }
469 
470     dropped_uri = ((CajaDragSelectionItem *)items->data)->uri;
471     dropped_file = caja_file_get_existing_by_uri (dropped_uri);
472     target_file = caja_file_get_existing_by_uri (target_uri_string);
473 
474     /*
475      * Check for trash URI.  We do a find_directory for any Trash directory.
476      * Passing 0 permissions as mate-vfs would override the permissions
477      * passed with 700 while creating .Trash directory
478      */
479     if (eel_uri_is_trash (target_uri_string))
480     {
481         /* Only move to Trash */
482         if (actions & GDK_ACTION_MOVE)
483         {
484             *action = GDK_ACTION_MOVE;
485         }
486 
487         caja_file_unref (dropped_file);
488         caja_file_unref (target_file);
489         return;
490 
491     }
492     else if (dropped_file != NULL && caja_file_is_launcher (dropped_file))
493     {
494         if (actions & GDK_ACTION_MOVE)
495         {
496             *action = GDK_ACTION_MOVE;
497         }
498         caja_file_unref (dropped_file);
499         caja_file_unref (target_file);
500         return;
501     }
502     else if (eel_uri_is_desktop (target_uri_string))
503     {
504         target = caja_get_desktop_location ();
505 
506         caja_file_unref (target_file);
507         target_file = caja_file_get (target);
508 
509         if (eel_uri_is_desktop (dropped_uri))
510         {
511             /* Only move to Desktop icons */
512             if (actions & GDK_ACTION_MOVE)
513             {
514                 *action = GDK_ACTION_MOVE;
515             }
516 
517             g_object_unref (target);
518             caja_file_unref (dropped_file);
519             caja_file_unref (target_file);
520             return;
521         }
522     }
523     else if (target_file != NULL && caja_file_is_archive (target_file))
524     {
525         *action = GDK_ACTION_COPY;
526 
527         caja_file_unref (dropped_file);
528         caja_file_unref (target_file);
529         return;
530     }
531     else
532     {
533         target = g_file_new_for_uri (target_uri_string);
534     }
535 
536     same_fs = check_same_fs (target_uri_string, target_file, dropped_uri, dropped_file);
537 
538     caja_file_unref (dropped_file);
539     caja_file_unref (target_file);
540 
541     /* Compare the first dropped uri with the target uri for same fs match. */
542     dropped = g_file_new_for_uri (dropped_uri);
543     dropped_directory = g_file_get_parent (dropped);
544     target_is_source_parent = FALSE;
545     if (dropped_directory != NULL)
546     {
547         /* If the dropped file is already in the same directory but
548            is in another filesystem we still want to move, not copy
549            as this is then just a move of a mountpoint to another
550            position in the dir */
551         target_is_source_parent = g_file_equal (dropped_directory, target);
552         g_object_unref (dropped_directory);
553     }
554     source_deletable = source_is_deletable (dropped);
555 
556     if ((same_fs && source_deletable) || target_is_source_parent ||
557             g_file_has_uri_scheme (dropped, "trash"))
558     {
559         if (actions & GDK_ACTION_MOVE)
560         {
561             *action = GDK_ACTION_MOVE;
562         }
563         else
564         {
565             *action = gdk_drag_context_get_suggested_action (context);
566         }
567     }
568     else
569     {
570         if (actions & GDK_ACTION_COPY)
571         {
572             *action = GDK_ACTION_COPY;
573         }
574         else
575         {
576             *action = gdk_drag_context_get_suggested_action (context);
577         }
578     }
579 
580     g_object_unref (target);
581     g_object_unref (dropped);
582 
583 }
584 
585 GdkDragAction
caja_drag_default_drop_action_for_uri_list(GdkDragContext * context,const char * target_uri_string)586 caja_drag_default_drop_action_for_uri_list (GdkDragContext *context,
587         const char *target_uri_string)
588 {
589     if (eel_uri_is_trash (target_uri_string) && (gdk_drag_context_get_actions (context) & GDK_ACTION_MOVE))
590     {
591         /* Only move to Trash */
592         return GDK_ACTION_MOVE;
593     }
594     else
595     {
596         return gdk_drag_context_get_suggested_action (context);
597     }
598 }
599 
600 /* Encode a "x-special/mate-icon-list" selection.
601    Along with the URIs of the dragged files, this encodes
602    the location and size of each icon relative to the cursor.
603 */
604 static void
add_one_mate_icon(const char * uri,int x,int y,int w,int h,gpointer data)605 add_one_mate_icon (const char *uri, int x, int y, int w, int h,
606                    gpointer data)
607 {
608     GString *result;
609 
610     result = (GString *) data;
611 
612     g_string_append_printf (result, "%s\r%d:%d:%hu:%hu\r\n",
613                             uri, x, y, w, h);
614 }
615 
616 /*
617  * Cf. #48423
618  */
619 #ifdef THIS_WAS_REALLY_BROKEN
620 static gboolean
is_path_that_mate_uri_list_extract_filenames_can_parse(const char * path)621 is_path_that_mate_uri_list_extract_filenames_can_parse (const char *path)
622 {
623     if (path == NULL || path [0] == '\0')
624     {
625         return FALSE;
626     }
627 
628     /* It strips leading and trailing spaces. So it can't handle
629      * file names with leading and trailing spaces.
630      */
631     if (g_ascii_isspace (path [0]))
632     {
633         return FALSE;
634     }
635     if (g_ascii_isspace (path [strlen (path) - 1]))
636     {
637         return FALSE;
638     }
639 
640     /* # works as a comment delimiter, and \r and \n are used to
641      * separate the lines, so it can't handle file names with any
642      * of these.
643      */
644     if (strchr (path, '#') != NULL
645             || strchr (path, '\r') != NULL
646             || strchr (path, '\n') != NULL)
647     {
648         return FALSE;
649     }
650 
651     return TRUE;
652 }
653 
654 /* Encode a "text/plain" selection; this is a broken URL -- just
655  * "file:" with a path after it (no escaping or anything). We are
656  * trying to make the old mate_uri_list_extract_filenames function
657  * happy, so this is coded to its idiosyncrasises.
658  */
659 static void
add_one_compatible_uri(const char * uri,int x,int y,int w,int h,gpointer data)660 add_one_compatible_uri (const char *uri, int x, int y, int w, int h, gpointer data)
661 {
662     GString *result;
663 
664     result = (GString *) data;
665 
666     /* For URLs that do not have a file: scheme, there's no harm
667      * in passing the real URL. But for URLs that do have a file:
668      * scheme, we have to send a URL that will work with the old
669      * mate-libs function or nothing will be able to understand
670      * it.
671      */
672     if (!eel_istr_has_prefix (uri, "file:"))
673     {
674         g_string_append (result, uri);
675         g_string_append (result, "\r\n");
676     }
677     else
678     {
679         char *local_path;
680 
681         local_path = g_filename_from_uri (uri, NULL, NULL);
682 
683         /* Check for characters that confuse the old
684          * mate_uri_list_extract_filenames implementation, and just leave
685          * out any paths with those in them.
686          */
687         if (is_path_that_mate_uri_list_extract_filenames_can_parse (local_path))
688         {
689             g_string_append (result, "file:");
690             g_string_append (result, local_path);
691             g_string_append (result, "\r\n");
692         }
693 
694         g_free (local_path);
695     }
696 }
697 #endif
698 
699 static void
add_one_uri(const char * uri,int x,int y,int w,int h,gpointer data)700 add_one_uri (const char *uri, int x, int y, int w, int h, gpointer data)
701 {
702     GString *result;
703 
704     result = (GString *) data;
705 
706     g_string_append (result, uri);
707     g_string_append (result, "\r\n");
708 }
709 
710 /* Common function for drag_data_get_callback calls.
711  * Returns FALSE if it doesn't handle drag data */
712 gboolean
caja_drag_drag_data_get(GtkWidget * widget,GdkDragContext * context,GtkSelectionData * selection_data,guint info,guint32 time,gpointer container_context,CajaDragEachSelectedItemIterator each_selected_item_iterator)713 caja_drag_drag_data_get (GtkWidget *widget,
714                          GdkDragContext *context,
715                          GtkSelectionData *selection_data,
716                          guint info,
717                          guint32 time,
718                          gpointer container_context,
719                          CajaDragEachSelectedItemIterator each_selected_item_iterator)
720 {
721     GString *result;
722 
723     switch (info)
724     {
725     case CAJA_ICON_DND_MATE_ICON_LIST:
726         result = g_string_new (NULL);
727         (* each_selected_item_iterator) (add_one_mate_icon, container_context, result);
728         break;
729 
730     case CAJA_ICON_DND_URI_LIST:
731     case CAJA_ICON_DND_TEXT:
732         result = g_string_new (NULL);
733         (* each_selected_item_iterator) (add_one_uri, container_context, result);
734         break;
735 
736     default:
737         return FALSE;
738     }
739 
740     gtk_selection_data_set (selection_data,
741                             gtk_selection_data_get_target (selection_data),
742                             8, result->str, result->len);
743     g_string_free (result, TRUE);
744 
745     return TRUE;
746 }
747 
748 typedef struct
749 {
750     GMainLoop *loop;
751     GdkDragAction chosen;
752 } DropActionMenuData;
753 
754 static void
menu_deactivate_callback(GtkWidget * menu,gpointer data)755 menu_deactivate_callback (GtkWidget *menu,
756                           gpointer   data)
757 {
758     DropActionMenuData *damd;
759 
760     damd = data;
761 
762     if (g_main_loop_is_running (damd->loop))
763         g_main_loop_quit (damd->loop);
764 }
765 
766 static void
drop_action_activated_callback(GtkWidget * menu_item,gpointer data)767 drop_action_activated_callback (GtkWidget  *menu_item,
768                                 gpointer    data)
769 {
770     DropActionMenuData *damd;
771 
772     damd = data;
773 
774     damd->chosen = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (menu_item),
775                                     "action"));
776 
777     if (g_main_loop_is_running (damd->loop))
778         g_main_loop_quit (damd->loop);
779 }
780 
781 static void
append_drop_action_menu_item(GtkWidget * menu,const char * text,GdkDragAction action,gboolean sensitive,DropActionMenuData * damd)782 append_drop_action_menu_item (GtkWidget          *menu,
783                               const char         *text,
784                               GdkDragAction       action,
785                               gboolean            sensitive,
786                               DropActionMenuData *damd)
787 {
788     GtkWidget *menu_item;
789 
790     menu_item = gtk_menu_item_new_with_mnemonic (text);
791     gtk_widget_set_sensitive (menu_item, sensitive);
792     gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);
793 
794     g_object_set_data (G_OBJECT (menu_item),
795                        "action",
796                        GINT_TO_POINTER (action));
797 
798     g_signal_connect (menu_item, "activate",
799                       G_CALLBACK (drop_action_activated_callback),
800                       damd);
801 
802     gtk_widget_show (menu_item);
803 }
804 
805 /* Pops up a menu of actions to perform on dropped files */
806 GdkDragAction
caja_drag_drop_action_ask(GtkWidget * widget,GdkDragAction actions)807 caja_drag_drop_action_ask (GtkWidget *widget,
808                            GdkDragAction actions)
809 {
810     GtkWidget *menu;
811     GtkWidget *menu_item;
812     DropActionMenuData damd;
813 
814     /* Create the menu and set the sensitivity of the items based on the
815      * allowed actions.
816      */
817     menu = gtk_menu_new ();
818     gtk_menu_set_screen (GTK_MENU (menu), gtk_widget_get_screen (widget));
819 
820     append_drop_action_menu_item (menu, _("_Move Here"),
821                                   GDK_ACTION_MOVE,
822                                   (actions & GDK_ACTION_MOVE) != 0,
823                                   &damd);
824 
825     append_drop_action_menu_item (menu, _("_Copy Here"),
826                                   GDK_ACTION_COPY,
827                                   (actions & GDK_ACTION_COPY) != 0,
828                                   &damd);
829 
830     append_drop_action_menu_item (menu, _("_Link Here"),
831                                   GDK_ACTION_LINK,
832                                   (actions & GDK_ACTION_LINK) != 0,
833                                   &damd);
834 
835     append_drop_action_menu_item (menu, _("Set as _Background"),
836                                   CAJA_DND_ACTION_SET_AS_BACKGROUND,
837                                   (actions & CAJA_DND_ACTION_SET_AS_BACKGROUND) != 0,
838                                   &damd);
839 
840     eel_gtk_menu_append_separator (GTK_MENU (menu));
841 
842     menu_item = gtk_menu_item_new_with_mnemonic (_("Cancel"));
843     gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);
844     gtk_widget_show (menu_item);
845 
846     damd.chosen = 0;
847     damd.loop = g_main_loop_new (NULL, FALSE);
848 
849     g_signal_connect (menu, "deactivate",
850                       G_CALLBACK (menu_deactivate_callback),
851                       &damd);
852 
853     gtk_grab_add (menu);
854 
855     gtk_menu_popup_at_pointer (GTK_MENU (menu), NULL);
856 
857     g_main_loop_run (damd.loop);
858 
859     gtk_grab_remove (menu);
860 
861     g_main_loop_unref (damd.loop);
862 
863     g_object_ref_sink (menu);
864     g_object_unref (menu);
865 
866     return damd.chosen;
867 }
868 
869 GdkDragAction
caja_drag_drop_background_ask(GtkWidget * widget,GdkDragAction actions)870 caja_drag_drop_background_ask (GtkWidget *widget,
871                                GdkDragAction actions)
872 {
873     GtkWidget *menu;
874     GtkWidget *menu_item;
875     DropActionMenuData damd;
876 
877     /* Create the menu and set the sensitivity of the items based on the
878      * allowed actions.
879      */
880     menu = gtk_menu_new ();
881     gtk_menu_set_screen (GTK_MENU (menu), gtk_widget_get_screen (widget));
882 
883     append_drop_action_menu_item (menu, _("Set as background for _all folders"),
884                                   CAJA_DND_ACTION_SET_AS_GLOBAL_BACKGROUND,
885                                   (actions & CAJA_DND_ACTION_SET_AS_GLOBAL_BACKGROUND) != 0,
886                                   &damd);
887 
888     append_drop_action_menu_item (menu, _("Set as background for _this folder"),
889                                   CAJA_DND_ACTION_SET_AS_FOLDER_BACKGROUND,
890                                   (actions & CAJA_DND_ACTION_SET_AS_FOLDER_BACKGROUND) != 0,
891                                   &damd);
892 
893     eel_gtk_menu_append_separator (GTK_MENU (menu));
894 
895     menu_item = gtk_menu_item_new_with_mnemonic (_("Cancel"));
896     gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);
897     gtk_widget_show (menu_item);
898 
899     damd.chosen = 0;
900     damd.loop = g_main_loop_new (NULL, FALSE);
901 
902     g_signal_connect (menu, "deactivate",
903                       G_CALLBACK (menu_deactivate_callback),
904                       &damd);
905 
906     gtk_grab_add (menu);
907 
908     gtk_menu_popup_at_pointer (GTK_MENU (menu), NULL);
909 
910     g_main_loop_run (damd.loop);
911 
912     gtk_grab_remove (menu);
913 
914     g_main_loop_unref (damd.loop);
915 
916     g_object_ref_sink (menu);
917     g_object_unref (menu);
918 
919     return damd.chosen;
920 }
921 
922 gboolean
caja_drag_autoscroll_in_scroll_region(GtkWidget * widget)923 caja_drag_autoscroll_in_scroll_region (GtkWidget *widget)
924 {
925     float x_scroll_delta, y_scroll_delta;
926 
927     caja_drag_autoscroll_calculate_delta (widget, &x_scroll_delta, &y_scroll_delta);
928 
929     return x_scroll_delta != 0 || y_scroll_delta != 0;
930 }
931 
932 
933 void
caja_drag_autoscroll_calculate_delta(GtkWidget * widget,float * x_scroll_delta,float * y_scroll_delta)934 caja_drag_autoscroll_calculate_delta (GtkWidget *widget, float *x_scroll_delta, float *y_scroll_delta)
935 {
936     GtkAllocation allocation;
937     GdkDisplay *display;
938     GdkSeat *seat;
939     GdkDevice *pointer;
940     int x, y;
941 
942     g_assert (GTK_IS_WIDGET (widget));
943 
944     display = gtk_widget_get_display (widget);
945     seat = gdk_display_get_default_seat (display);
946     pointer = gdk_seat_get_pointer (seat);
947     gdk_window_get_device_position (gtk_widget_get_window (widget), pointer,
948                                     &x, &y, NULL);
949 
950     /* Find out if we are anywhere close to the tree view edges
951      * to see if we need to autoscroll.
952      */
953     *x_scroll_delta = 0;
954     *y_scroll_delta = 0;
955 
956     if (x < AUTO_SCROLL_MARGIN)
957     {
958         *x_scroll_delta = (float)(x - AUTO_SCROLL_MARGIN);
959     }
960 
961     gtk_widget_get_allocation (widget, &allocation);
962     if (x > allocation.width - AUTO_SCROLL_MARGIN)
963     {
964         if (*x_scroll_delta != 0)
965         {
966             /* Already trying to scroll because of being too close to
967              * the top edge -- must be the window is really short,
968              * don't autoscroll.
969              */
970             return;
971         }
972         *x_scroll_delta = (float)(x - (allocation.width - AUTO_SCROLL_MARGIN));
973     }
974 
975     if (y < AUTO_SCROLL_MARGIN)
976     {
977         *y_scroll_delta = (float)(y - AUTO_SCROLL_MARGIN);
978     }
979 
980     if (y > allocation.height - AUTO_SCROLL_MARGIN)
981     {
982         if (*y_scroll_delta != 0)
983         {
984             /* Already trying to scroll because of being too close to
985              * the top edge -- must be the window is really narrow,
986              * don't autoscroll.
987              */
988             return;
989         }
990         *y_scroll_delta = (float)(y - (allocation.height - AUTO_SCROLL_MARGIN));
991     }
992 
993     if (*x_scroll_delta == 0 && *y_scroll_delta == 0)
994     {
995         /* no work */
996         return;
997     }
998 
999     /* Adjust the scroll delta to the proper acceleration values depending on how far
1000      * into the sroll margins we are.
1001      * FIXME bugzilla.eazel.com 2486:
1002      * we could use an exponential acceleration factor here for better feel
1003      */
1004     if (*x_scroll_delta != 0)
1005     {
1006         *x_scroll_delta /= AUTO_SCROLL_MARGIN;
1007         *x_scroll_delta *= (MAX_AUTOSCROLL_DELTA - MIN_AUTOSCROLL_DELTA);
1008         *x_scroll_delta += MIN_AUTOSCROLL_DELTA;
1009     }
1010 
1011     if (*y_scroll_delta != 0)
1012     {
1013         *y_scroll_delta /= AUTO_SCROLL_MARGIN;
1014         *y_scroll_delta *= (MAX_AUTOSCROLL_DELTA - MIN_AUTOSCROLL_DELTA);
1015         *y_scroll_delta += MIN_AUTOSCROLL_DELTA;
1016     }
1017 
1018 }
1019 
1020 
1021 
1022 void
caja_drag_autoscroll_start(CajaDragInfo * drag_info,GtkWidget * widget,GSourceFunc callback,gpointer user_data)1023 caja_drag_autoscroll_start (CajaDragInfo *drag_info,
1024                             GtkWidget        *widget,
1025                             GSourceFunc       callback,
1026                             gpointer          user_data)
1027 {
1028     if (caja_drag_autoscroll_in_scroll_region (widget))
1029     {
1030         if (drag_info->auto_scroll_timeout_id == 0)
1031         {
1032             drag_info->waiting_to_autoscroll = TRUE;
1033             drag_info->start_auto_scroll_in = g_get_monotonic_time()
1034                                               + AUTOSCROLL_INITIAL_DELAY;
1035 
1036             drag_info->auto_scroll_timeout_id = g_timeout_add
1037                                                 (AUTOSCROLL_TIMEOUT_INTERVAL,
1038                                                  callback,
1039                                                  user_data);
1040         }
1041     }
1042     else
1043     {
1044         if (drag_info->auto_scroll_timeout_id != 0)
1045         {
1046             g_source_remove (drag_info->auto_scroll_timeout_id);
1047             drag_info->auto_scroll_timeout_id = 0;
1048         }
1049     }
1050 }
1051 
1052 void
caja_drag_autoscroll_stop(CajaDragInfo * drag_info)1053 caja_drag_autoscroll_stop (CajaDragInfo *drag_info)
1054 {
1055     if (drag_info->auto_scroll_timeout_id != 0)
1056     {
1057         g_source_remove (drag_info->auto_scroll_timeout_id);
1058         drag_info->auto_scroll_timeout_id = 0;
1059     }
1060 }
1061 
1062 gboolean
caja_drag_selection_includes_special_link(GList * selection_list)1063 caja_drag_selection_includes_special_link (GList *selection_list)
1064 {
1065     GList *node;
1066 
1067     for (node = selection_list; node != NULL; node = node->next)
1068     {
1069         char *uri;
1070 
1071         uri = ((CajaDragSelectionItem *) node->data)->uri;
1072 
1073         if (eel_uri_is_desktop (uri))
1074         {
1075             return TRUE;
1076         }
1077     }
1078 
1079     return FALSE;
1080 }
1081 
1082 static gboolean
slot_proxy_drag_motion(GtkWidget * widget,GdkDragContext * context,int x,int y,unsigned int time,gpointer user_data)1083 slot_proxy_drag_motion (GtkWidget          *widget,
1084                         GdkDragContext     *context,
1085                         int                 x,
1086                         int                 y,
1087                         unsigned int        time,
1088                         gpointer            user_data)
1089 {
1090     CajaDragSlotProxyInfo *drag_info;
1091     CajaWindowSlotInfo *target_slot;
1092     GtkWidget *window;
1093     GdkAtom target;
1094     int action;
1095     char *target_uri;
1096 
1097     drag_info = user_data;
1098 
1099     action = 0;
1100 
1101     if (gtk_drag_get_source_widget (context) == widget)
1102     {
1103         goto out;
1104     }
1105 
1106     window = gtk_widget_get_toplevel (widget);
1107     g_assert (CAJA_IS_WINDOW_INFO (window));
1108 
1109     if (!drag_info->have_data)
1110     {
1111         target = gtk_drag_dest_find_target (widget, context, NULL);
1112 
1113         if (target == GDK_NONE)
1114         {
1115             goto out;
1116         }
1117 
1118         gtk_drag_get_data (widget, context, target, time);
1119     }
1120 
1121     target_uri = NULL;
1122     if (drag_info->target_location != NULL)
1123     {
1124         target_uri = g_file_get_uri (drag_info->target_location);
1125     }
1126     else
1127     {
1128         if (drag_info->target_slot != NULL)
1129         {
1130             target_slot = drag_info->target_slot;
1131         }
1132         else
1133         {
1134             target_slot = caja_window_info_get_active_slot (CAJA_WINDOW_INFO (window));
1135         }
1136 
1137         if (target_slot != NULL)
1138         {
1139             target_uri = caja_window_slot_info_get_current_location (target_slot);
1140         }
1141     }
1142 
1143     if (drag_info->have_data &&
1144             drag_info->have_valid_data)
1145     {
1146         if (drag_info->info == CAJA_ICON_DND_MATE_ICON_LIST)
1147         {
1148             caja_drag_default_drop_action_for_icons (context, target_uri,
1149                     drag_info->data.selection_list,
1150                     &action);
1151         }
1152         else if (drag_info->info == CAJA_ICON_DND_URI_LIST)
1153         {
1154             action = caja_drag_default_drop_action_for_uri_list (context, target_uri);
1155         }
1156         else if (drag_info->info == CAJA_ICON_DND_NETSCAPE_URL)
1157         {
1158             action = caja_drag_default_drop_action_for_netscape_url (context);
1159         }
1160     }
1161 
1162     g_free (target_uri);
1163 
1164 out:
1165     if (action != 0)
1166     {
1167         gtk_drag_highlight (widget);
1168     }
1169     else
1170     {
1171         gtk_drag_unhighlight (widget);
1172     }
1173 
1174     gdk_drag_status (context, action, time);
1175 
1176     return TRUE;
1177 }
1178 
1179 static void
drag_info_clear(CajaDragSlotProxyInfo * drag_info)1180 drag_info_clear (CajaDragSlotProxyInfo *drag_info)
1181 {
1182     if (!drag_info->have_data)
1183     {
1184         goto out;
1185     }
1186 
1187     if (drag_info->info == CAJA_ICON_DND_MATE_ICON_LIST)
1188     {
1189         caja_drag_destroy_selection_list (drag_info->data.selection_list);
1190     }
1191     else if (drag_info->info == CAJA_ICON_DND_URI_LIST)
1192     {
1193         g_list_free (drag_info->data.uri_list);
1194     }
1195     else if (drag_info->info == CAJA_ICON_DND_NETSCAPE_URL)
1196     {
1197         g_free (drag_info->data.netscape_url);
1198     }
1199 
1200 out:
1201     drag_info->have_data = FALSE;
1202     drag_info->have_valid_data = FALSE;
1203 
1204     drag_info->drop_occured = FALSE;
1205 }
1206 
1207 static void
slot_proxy_drag_leave(GtkWidget * widget,GdkDragContext * context,unsigned int time,gpointer user_data)1208 slot_proxy_drag_leave (GtkWidget          *widget,
1209                        GdkDragContext     *context,
1210                        unsigned int        time,
1211                        gpointer            user_data)
1212 {
1213     CajaDragSlotProxyInfo *drag_info;
1214 
1215     drag_info = user_data;
1216 
1217     gtk_drag_unhighlight (widget);
1218     drag_info_clear (drag_info);
1219 }
1220 
1221 static gboolean
slot_proxy_drag_drop(GtkWidget * widget,GdkDragContext * context,int x,int y,unsigned int time,gpointer user_data)1222 slot_proxy_drag_drop (GtkWidget          *widget,
1223                       GdkDragContext     *context,
1224                       int                 x,
1225                       int                 y,
1226                       unsigned int        time,
1227                       gpointer            user_data)
1228 {
1229     GdkAtom target;
1230     CajaDragSlotProxyInfo *drag_info;
1231 
1232     drag_info = user_data;
1233     g_assert (!drag_info->have_data);
1234 
1235     drag_info->drop_occured = TRUE;
1236 
1237     target = gtk_drag_dest_find_target (widget, context, NULL);
1238     gtk_drag_get_data (widget, context, target, time);
1239 
1240     return TRUE;
1241 }
1242 
1243 
1244 static void
slot_proxy_handle_drop(GtkWidget * widget,GdkDragContext * context,unsigned int time,CajaDragSlotProxyInfo * drag_info)1245 slot_proxy_handle_drop (GtkWidget                *widget,
1246                         GdkDragContext           *context,
1247                         unsigned int              time,
1248                         CajaDragSlotProxyInfo *drag_info)
1249 {
1250     GtkWidget *window;
1251     CajaWindowSlotInfo *target_slot;
1252     CajaView *target_view;
1253     char *target_uri;
1254 
1255     if (!drag_info->have_data ||
1256             !drag_info->have_valid_data)
1257     {
1258         gtk_drag_finish (context, FALSE, FALSE, time);
1259         drag_info_clear (drag_info);
1260         return;
1261     }
1262 
1263     window = gtk_widget_get_toplevel (widget);
1264     g_assert (CAJA_IS_WINDOW_INFO (window));
1265 
1266     if (drag_info->target_slot != NULL)
1267     {
1268         target_slot = drag_info->target_slot;
1269     }
1270     else
1271     {
1272         target_slot = caja_window_info_get_active_slot (CAJA_WINDOW_INFO (window));
1273     }
1274 
1275     target_uri = NULL;
1276     if (drag_info->target_location != NULL)
1277     {
1278         target_uri = g_file_get_uri (drag_info->target_location);
1279     }
1280     else if (target_slot != NULL)
1281     {
1282         target_uri = caja_window_slot_info_get_current_location (target_slot);
1283     }
1284 
1285     target_view = NULL;
1286     if (target_slot != NULL)
1287     {
1288         target_view = caja_window_slot_info_get_current_view (target_slot);
1289     }
1290 
1291     if (target_slot != NULL && target_view != NULL)
1292     {
1293         if (drag_info->info == CAJA_ICON_DND_MATE_ICON_LIST)
1294         {
1295             GList *uri_list;
1296 
1297             uri_list = caja_drag_uri_list_from_selection_list (drag_info->data.selection_list);
1298             g_assert (uri_list != NULL);
1299 
1300             caja_view_drop_proxy_received_uris (target_view,
1301                                                 uri_list,
1302                                                 target_uri,
1303                                                 gdk_drag_context_get_selected_action (context));
1304             g_list_free_full (uri_list, g_free);
1305         }
1306         else if (drag_info->info == CAJA_ICON_DND_URI_LIST)
1307         {
1308             caja_view_drop_proxy_received_uris (target_view,
1309                                                 drag_info->data.uri_list,
1310                                                 target_uri,
1311                                                 gdk_drag_context_get_selected_action (context));
1312         }
1313         if (drag_info->info == CAJA_ICON_DND_NETSCAPE_URL)
1314         {
1315             caja_view_drop_proxy_received_netscape_url (target_view,
1316                     drag_info->data.netscape_url,
1317                     target_uri,
1318                     gdk_drag_context_get_selected_action (context));
1319         }
1320 
1321 
1322         gtk_drag_finish (context, TRUE, FALSE, time);
1323     }
1324     else
1325     {
1326         gtk_drag_finish (context, FALSE, FALSE, time);
1327     }
1328 
1329     if (target_view != NULL)
1330     {
1331         g_object_unref (target_view);
1332     }
1333 
1334     g_free (target_uri);
1335 
1336     drag_info_clear (drag_info);
1337 }
1338 
1339 static void
slot_proxy_drag_data_received(GtkWidget * widget,GdkDragContext * context,int x,int y,GtkSelectionData * data,unsigned int info,unsigned int time,gpointer user_data)1340 slot_proxy_drag_data_received (GtkWidget          *widget,
1341                                GdkDragContext     *context,
1342                                int                 x,
1343                                int                 y,
1344                                GtkSelectionData   *data,
1345                                unsigned int        info,
1346                                unsigned int        time,
1347                                gpointer            user_data)
1348 {
1349     CajaDragSlotProxyInfo *drag_info;
1350     char **uris;
1351 
1352     drag_info = user_data;
1353 
1354     g_assert (!drag_info->have_data);
1355 
1356     drag_info->have_data = TRUE;
1357     drag_info->info = info;
1358 
1359     if (gtk_selection_data_get_length (data) < 0)
1360     {
1361         drag_info->have_valid_data = FALSE;
1362         return;
1363     }
1364 
1365     if (info == CAJA_ICON_DND_MATE_ICON_LIST)
1366     {
1367         drag_info->data.selection_list = caja_drag_build_selection_list (data);
1368 
1369         drag_info->have_valid_data = drag_info->data.selection_list != NULL;
1370     }
1371     else if (info == CAJA_ICON_DND_URI_LIST)
1372     {
1373         uris = gtk_selection_data_get_uris (data);
1374         drag_info->data.uri_list = caja_drag_uri_list_from_array ((const char **) uris);
1375         g_strfreev (uris);
1376 
1377         drag_info->have_valid_data = drag_info->data.uri_list != NULL;
1378     }
1379     else if (info == CAJA_ICON_DND_NETSCAPE_URL)
1380     {
1381         drag_info->data.netscape_url = g_strdup ((char *) gtk_selection_data_get_data (data));
1382 
1383         drag_info->have_valid_data = drag_info->data.netscape_url != NULL;
1384     }
1385 
1386     if (drag_info->drop_occured)
1387     {
1388         slot_proxy_handle_drop (widget, context, time, drag_info);
1389     }
1390 }
1391 
1392 void
caja_drag_slot_proxy_init(GtkWidget * widget,CajaDragSlotProxyInfo * drag_info)1393 caja_drag_slot_proxy_init (GtkWidget *widget,
1394                            CajaDragSlotProxyInfo *drag_info)
1395 {
1396     const GtkTargetEntry targets[] =
1397     {
1398         { CAJA_ICON_DND_MATE_ICON_LIST_TYPE, 0, CAJA_ICON_DND_MATE_ICON_LIST },
1399         { CAJA_ICON_DND_NETSCAPE_URL_TYPE, 0, CAJA_ICON_DND_NETSCAPE_URL }
1400     };
1401     GtkTargetList *target_list;
1402 
1403     g_assert (GTK_IS_WIDGET (widget));
1404     g_assert (drag_info != NULL);
1405 
1406     gtk_drag_dest_set (widget, 0,
1407                        NULL, 0,
1408                        GDK_ACTION_MOVE |
1409                        GDK_ACTION_COPY |
1410                        GDK_ACTION_LINK |
1411                        GDK_ACTION_ASK);
1412 
1413     target_list = gtk_target_list_new (targets, G_N_ELEMENTS (targets));
1414     gtk_target_list_add_uri_targets (target_list, CAJA_ICON_DND_URI_LIST);
1415     gtk_drag_dest_set_target_list (widget, target_list);
1416     gtk_target_list_unref (target_list);
1417 
1418     g_signal_connect (widget, "drag-motion",
1419                       G_CALLBACK (slot_proxy_drag_motion),
1420                       drag_info);
1421     g_signal_connect (widget, "drag-drop",
1422                       G_CALLBACK (slot_proxy_drag_drop),
1423                       drag_info);
1424     g_signal_connect (widget, "drag-data-received",
1425                       G_CALLBACK (slot_proxy_drag_data_received),
1426                       drag_info);
1427     g_signal_connect (widget, "drag-leave",
1428                       G_CALLBACK (slot_proxy_drag_leave),
1429                       drag_info);
1430 }
1431 
1432 
1433