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