1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
3 /* nautilus-dnd.c - Common Drag & drop handling code shared by the icon container
6 Copyright (C) 2000, 2001 Eazel, Inc.
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.
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.
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., 59 Temple Place - Suite 330,
21 Boston, MA 02111-1307, USA.
23 Authors: Pavel Cisler <pavel@eazel.com>,
24 Ettore Perazzoli <ettore@gnu.org>
27 /* FIXME: This should really be back in Nautilus, not here in Eel. */
30 #include "nautilus-dnd.h"
32 #include "nautilus-program-choosing.h"
33 #include "nautilus-link.h"
34 #include <eel/eel-glib-extensions.h>
35 #include <eel/eel-gtk-extensions.h>
36 #include <eel/eel-string.h>
37 #include <eel/eel-vfs-extensions.h>
38 #include <gtk/gtkmain.h>
39 #include <gtk/gtkmenu.h>
40 #include <gtk/gtkseparatormenuitem.h>
41 #include <glib/gi18n.h>
42 #include <libgnomeui/gnome-uidefs.h>
43 #include <libnautilus-private/nautilus-file-utilities.h>
47 /* a set of defines stolen from the eel-icon-dnd.c file.
48 * These are in microseconds.
50 #define AUTOSCROLL_TIMEOUT_INTERVAL 100
51 #define AUTOSCROLL_INITIAL_DELAY 100000
53 /* drag this close to the view edge to start auto scroll*/
54 #define AUTO_SCROLL_MARGIN 30
56 /* the smallest amount of auto scroll used when we just enter the autoscroll
59 #define MIN_AUTOSCROLL_DELTA 5
61 /* the largest amount of auto scroll used when we are right over the view
64 #define MAX_AUTOSCROLL_DELTA 50
67 nautilus_drag_init (NautilusDragInfo
*drag_info
,
68 const GtkTargetEntry
*drag_types
,
70 gboolean add_text_targets
)
72 drag_info
->target_list
= gtk_target_list_new (drag_types
,
75 if (add_text_targets
) {
76 gtk_target_list_add_text_targets (drag_info
->target_list
,
77 NAUTILUS_ICON_DND_TEXT
);
80 drag_info
->drop_occured
= FALSE
;
81 drag_info
->need_to_destroy
= FALSE
;
85 nautilus_drag_finalize (NautilusDragInfo
*drag_info
)
87 gtk_target_list_unref (drag_info
->target_list
);
88 nautilus_drag_destroy_selection_list (drag_info
->selection_list
);
94 /* Functions to deal with NautilusDragSelectionItems. */
96 NautilusDragSelectionItem
*
97 nautilus_drag_selection_item_new (void)
99 return g_new0 (NautilusDragSelectionItem
, 1);
103 drag_selection_item_destroy (NautilusDragSelectionItem
*item
)
110 nautilus_drag_destroy_selection_list (GList
*list
)
117 for (p
= list
; p
!= NULL
; p
= p
->next
)
118 drag_selection_item_destroy (p
->data
);
124 nautilus_drag_build_selection_list (GtkSelectionData
*data
)
127 const guchar
*p
, *oldp
;
135 NautilusDragSelectionItem
*item
;
138 /* The list is in the form:
140 name\rx:y:width:height\r\n
142 The geometry information after the first \r is optional. */
144 /* 1: Decode name. */
146 p
= memchr (oldp
, '\r', size
);
151 item
= nautilus_drag_selection_item_new ();
155 item
->uri
= g_malloc (len
+ 1);
156 memcpy (item
->uri
, oldp
, len
);
160 if (*p
== '\n' || *p
== '\0') {
161 result
= g_list_prepend (result
, item
);
163 g_warning ("Invalid x-special/gnome-icon-list data received: "
164 "missing newline character.");
175 /* 2: Decode geometry information. */
177 item
->got_icon_position
= sscanf (p
, "%d:%d:%d:%d%*s",
178 &item
->icon_x
, &item
->icon_y
,
179 &item
->icon_width
, &item
->icon_height
) == 4;
180 if (!item
->got_icon_position
) {
181 g_warning ("Invalid x-special/gnome-icon-list data received: "
182 "invalid icon position specification.");
185 result
= g_list_prepend (result
, item
);
187 p
= memchr (p
, '\r', size
);
188 if (p
== NULL
|| p
[1] != '\n') {
189 g_warning ("Invalid x-special/gnome-icon-list data received: "
190 "missing newline character.");
202 return g_list_reverse (result
);
206 nautilus_drag_file_local_internal (const char *target_uri_string
,
207 const char *first_source_uri
)
209 /* check if the first item on the list has target_uri_string as a parent
211 * we should really test each item but that would be slow for large selections
212 * and currently dropped items can only be from the same container
214 GFile
*target
, *item
, *parent
;
219 target
= g_file_new_for_uri (target_uri_string
);
221 /* get the parent URI of the first item in the selection */
222 item
= g_file_new_for_uri (first_source_uri
);
223 parent
= g_file_get_parent (item
);
224 g_object_unref (item
);
226 if (parent
!= NULL
) {
227 result
= g_file_equal (parent
, target
);
228 g_object_unref (parent
);
235 nautilus_drag_uris_local (const char *target_uri
,
236 const GList
*source_uri_list
)
238 /* must have at least one item */
239 g_assert (source_uri_list
);
241 return nautilus_drag_file_local_internal (target_uri
, source_uri_list
->data
);
245 nautilus_drag_items_local (const char *target_uri_string
,
246 const GList
*selection_list
)
248 /* must have at least one item */
249 g_assert (selection_list
);
251 return nautilus_drag_file_local_internal (target_uri_string
,
252 ((NautilusDragSelectionItem
*)selection_list
->data
)->uri
);
256 nautilus_drag_items_in_trash (const GList
*selection_list
)
258 /* check if the first item on the list is in trash.
260 * we should really test each item but that would be slow for large selections
261 * and currently dropped items can only be from the same container
263 return eel_uri_is_trash (((NautilusDragSelectionItem
*)selection_list
->data
)->uri
);
267 nautilus_drag_items_on_desktop (const GList
*selection_list
)
270 GFile
*desktop
, *item
, *parent
;
273 /* check if the first item on the list is in trash.
275 * we should really test each item but that would be slow for large selections
276 * and currently dropped items can only be from the same container
278 uri
= ((NautilusDragSelectionItem
*)selection_list
->data
)->uri
;
279 if (eel_uri_is_desktop (uri
)) {
283 desktop
= nautilus_get_desktop_location ();
285 item
= g_file_new_for_uri (uri
);
286 parent
= g_file_get_parent (item
);
287 g_object_unref (item
);
292 result
= g_file_equal (desktop
, parent
);
293 g_object_unref (parent
);
295 g_object_unref (desktop
);
302 nautilus_drag_default_drop_action_for_netscape_url (GdkDragContext
*context
)
304 /* Mozilla defaults to copy, but unless thats the
305 only allowed thing (enforced by ctrl) we want to ASK */
306 if (context
->suggested_action
== GDK_ACTION_COPY
&&
307 context
->actions
!= GDK_ACTION_COPY
) {
308 return GDK_ACTION_ASK
;
309 } else if (context
->suggested_action
== GDK_ACTION_MOVE
) {
310 /* Don't support move */
311 return GDK_ACTION_COPY
;
314 return context
->suggested_action
;
318 check_same_fs (GFile
*file1
, GFile
*file2
)
320 GFileInfo
*info1
, *info2
;
321 const char *id1
, *id2
;
324 info1
= g_file_query_info (file1
,
325 G_FILE_ATTRIBUTE_ID_FILESYSTEM
,
332 id1
= g_file_info_get_attribute_string (info1
, G_FILE_ATTRIBUTE_ID_FILESYSTEM
);
334 g_object_unref (info1
);
338 info2
= g_file_query_info (file2
,
339 G_FILE_ATTRIBUTE_ID_FILESYSTEM
,
342 g_object_unref (info1
);
346 id2
= g_file_info_get_attribute_string (info2
, G_FILE_ATTRIBUTE_ID_FILESYSTEM
);
348 g_object_unref (info1
);
349 g_object_unref (info2
);
353 res
= strcmp (id1
, id2
) == 0;
355 g_object_unref (info1
);
356 g_object_unref (info2
);
362 nautilus_drag_default_drop_action_for_icons (GdkDragContext
*context
,
363 const char *target_uri_string
, const GList
*items
,
367 gboolean target_is_source_parent
;
368 const char *dropped_uri
;
369 GFile
*target
, *dropped
;
370 GdkDragAction actions
;
371 NautilusFile
*target_file
;
373 if (target_uri_string
== NULL
) {
378 actions
= context
->actions
& (GDK_ACTION_MOVE
| GDK_ACTION_COPY
);
380 /* We can't use copy or move, just go with the suggested action. */
381 *action
= context
->suggested_action
;
385 if (context
->suggested_action
== GDK_ACTION_ASK
) {
386 /* Don't override ask */
387 *action
= context
->suggested_action
;
391 dropped_uri
= ((NautilusDragSelectionItem
*)items
->data
)->uri
;
392 target_file
= nautilus_file_get_existing_by_uri (dropped_uri
);
395 * Check for trash URI. We do a find_directory for any Trash directory.
396 * Passing 0 permissions as gnome-vfs would override the permissions
397 * passed with 700 while creating .Trash directory
399 if (eel_uri_is_trash (target_uri_string
)) {
400 /* Only move to Trash */
401 if (actions
& GDK_ACTION_MOVE
) {
402 *action
= GDK_ACTION_MOVE
;
405 nautilus_file_unref (target_file
);
408 } else if (target_file
!= NULL
&& nautilus_file_is_launcher (target_file
)) {
409 if (actions
& GDK_ACTION_MOVE
) {
410 *action
= GDK_ACTION_MOVE
;
412 nautilus_file_unref (target_file
);
414 } else if (eel_uri_is_desktop (target_uri_string
)) {
415 target
= nautilus_get_desktop_location ();
416 if (eel_uri_is_desktop (dropped_uri
)) {
417 /* Only move to Desktop icons */
418 if (actions
& GDK_ACTION_MOVE
) {
419 *action
= GDK_ACTION_MOVE
;
422 nautilus_file_unref (target_file
);
426 target
= g_file_new_for_uri (target_uri_string
);
429 nautilus_file_unref (target_file
);
431 /* Compare the first dropped uri with the target uri for same fs match. */
432 dropped
= g_file_new_for_uri (dropped_uri
);
433 same_fs
= check_same_fs (target
, dropped
);
434 target_is_source_parent
= g_file_has_prefix (dropped
, target
);
436 if (same_fs
|| target_is_source_parent
||
437 g_file_has_uri_scheme (dropped
, "trash")) {
438 if (actions
& GDK_ACTION_MOVE
) {
439 *action
= GDK_ACTION_MOVE
;
441 *action
= context
->suggested_action
;
444 if (actions
& GDK_ACTION_COPY
) {
445 *action
= GDK_ACTION_COPY
;
447 *action
= context
->suggested_action
;
451 g_object_unref (target
);
452 g_object_unref (dropped
);
456 /* Encode a "x-special/gnome-icon-list" selection.
457 Along with the URIs of the dragged files, this encodes
458 the location and size of each icon relative to the cursor.
461 add_one_gnome_icon (const char *uri
, int x
, int y
, int w
, int h
,
466 result
= (GString
*) data
;
468 g_string_append_printf (result
, "%s\r%d:%d:%hu:%hu\r\n",
475 #ifdef THIS_WAS_REALLY_BROKEN
477 is_path_that_gnome_uri_list_extract_filenames_can_parse (const char *path
)
479 if (path
== NULL
|| path
[0] == '\0') {
483 /* It strips leading and trailing spaces. So it can't handle
484 * file names with leading and trailing spaces.
486 if (g_ascii_isspace (path
[0])) {
489 if (g_ascii_isspace (path
[strlen (path
) - 1])) {
493 /* # works as a comment delimiter, and \r and \n are used to
494 * separate the lines, so it can't handle file names with any
497 if (strchr (path
, '#') != NULL
498 || strchr (path
, '\r') != NULL
499 || strchr (path
, '\n') != NULL
) {
506 /* Encode a "text/plain" selection; this is a broken URL -- just
507 * "file:" with a path after it (no escaping or anything). We are
508 * trying to make the old gnome_uri_list_extract_filenames function
509 * happy, so this is coded to its idiosyncrasises.
512 add_one_compatible_uri (const char *uri
, int x
, int y
, int w
, int h
, gpointer data
)
517 result
= (GString
*) data
;
519 /* For URLs that do not have a file: scheme, there's no harm
520 * in passing the real URL. But for URLs that do have a file:
521 * scheme, we have to send a URL that will work with the old
522 * gnome-libs function or nothing will be able to understand
525 if (!eel_istr_has_prefix (uri
, "file:")) {
526 g_string_append (result
, uri
);
527 g_string_append (result
, "\r\n");
529 local_path
= g_filename_from_uri (uri
, NULL
, NULL
);
531 /* Check for characters that confuse the old
532 * gnome_uri_list_extract_filenames implementation, and just leave
533 * out any paths with those in them.
535 if (is_path_that_gnome_uri_list_extract_filenames_can_parse (local_path
)) {
536 g_string_append (result
, "file:");
537 g_string_append (result
, local_path
);
538 g_string_append (result
, "\r\n");
547 add_one_uri (const char *uri
, int x
, int y
, int w
, int h
, gpointer data
)
551 result
= (GString
*) data
;
553 g_string_append (result
, uri
);
554 g_string_append (result
, "\r\n");
557 /* Common function for drag_data_get_callback calls.
558 * Returns FALSE if it doesn't handle drag data */
560 nautilus_drag_drag_data_get (GtkWidget
*widget
,
561 GdkDragContext
*context
,
562 GtkSelectionData
*selection_data
,
565 gpointer container_context
,
566 NautilusDragEachSelectedItemIterator each_selected_item_iterator
)
571 case NAUTILUS_ICON_DND_GNOME_ICON_LIST
:
572 result
= g_string_new (NULL
);
573 (* each_selected_item_iterator
) (add_one_gnome_icon
, container_context
, result
);
576 case NAUTILUS_ICON_DND_URI_LIST
:
577 case NAUTILUS_ICON_DND_TEXT
:
578 result
= g_string_new (NULL
);
579 (* each_selected_item_iterator
) (add_one_uri
, container_context
, result
);
586 gtk_selection_data_set (selection_data
,
587 selection_data
->target
,
588 8, result
->str
, result
->len
);
596 GdkDragAction chosen
;
597 } DropActionMenuData
;
600 menu_deactivate_callback (GtkWidget
*menu
,
603 DropActionMenuData
*damd
;
607 if (g_main_loop_is_running (damd
->loop
))
608 g_main_loop_quit (damd
->loop
);
612 drop_action_activated_callback (GtkWidget
*menu_item
,
615 DropActionMenuData
*damd
;
619 damd
->chosen
= GPOINTER_TO_INT (g_object_get_data (G_OBJECT (menu_item
),
622 if (g_main_loop_is_running (damd
->loop
))
623 g_main_loop_quit (damd
->loop
);
627 append_drop_action_menu_item (GtkWidget
*menu
,
629 GdkDragAction action
,
631 DropActionMenuData
*damd
)
633 GtkWidget
*menu_item
;
635 menu_item
= gtk_menu_item_new_with_mnemonic (text
);
636 gtk_widget_set_sensitive (menu_item
, sensitive
);
637 gtk_menu_shell_append (GTK_MENU_SHELL (menu
), menu_item
);
639 g_object_set_data (G_OBJECT (menu_item
),
641 GINT_TO_POINTER (action
));
643 g_signal_connect (menu_item
, "activate",
644 G_CALLBACK (drop_action_activated_callback
),
647 gtk_widget_show (menu_item
);
650 /* Pops up a menu of actions to perform on dropped files */
652 nautilus_drag_drop_action_ask (GtkWidget
*widget
,
653 GdkDragAction actions
)
656 GtkWidget
*menu_item
;
657 DropActionMenuData damd
;
659 /* Create the menu and set the sensitivity of the items based on the
662 menu
= gtk_menu_new ();
663 gtk_menu_set_screen (GTK_MENU (menu
), gtk_widget_get_screen (widget
));
665 append_drop_action_menu_item (menu
, _("_Move Here"),
667 (actions
& GDK_ACTION_MOVE
) != 0,
670 append_drop_action_menu_item (menu
, _("_Copy Here"),
672 (actions
& GDK_ACTION_COPY
) != 0,
675 append_drop_action_menu_item (menu
, _("_Link Here"),
677 (actions
& GDK_ACTION_LINK
) != 0,
680 append_drop_action_menu_item (menu
, _("Set as _Background"),
681 NAUTILUS_DND_ACTION_SET_AS_BACKGROUND
,
682 (actions
& NAUTILUS_DND_ACTION_SET_AS_BACKGROUND
) != 0,
685 eel_gtk_menu_append_separator (GTK_MENU (menu
));
687 menu_item
= gtk_menu_item_new_with_mnemonic (_("Cancel"));
688 gtk_menu_shell_append (GTK_MENU_SHELL (menu
), menu_item
);
689 gtk_widget_show (menu_item
);
692 damd
.loop
= g_main_loop_new (NULL
, FALSE
);
694 g_signal_connect (menu
, "deactivate",
695 G_CALLBACK (menu_deactivate_callback
),
700 gtk_menu_popup (GTK_MENU (menu
), NULL
, NULL
,
701 NULL
, NULL
, 0, GDK_CURRENT_TIME
);
703 g_main_loop_run (damd
.loop
);
705 gtk_grab_remove (menu
);
707 g_main_loop_unref (damd
.loop
);
709 gtk_object_sink (GTK_OBJECT (menu
));
715 nautilus_drag_drop_background_ask (GtkWidget
*widget
,
716 GdkDragAction actions
)
719 GtkWidget
*menu_item
;
720 DropActionMenuData damd
;
722 /* Create the menu and set the sensitivity of the items based on the
725 menu
= gtk_menu_new ();
726 gtk_menu_set_screen (GTK_MENU (menu
), gtk_widget_get_screen (widget
));
728 append_drop_action_menu_item (menu
, _("Set as background for _all folders"),
729 NAUTILUS_DND_ACTION_SET_AS_GLOBAL_BACKGROUND
,
730 (actions
& NAUTILUS_DND_ACTION_SET_AS_GLOBAL_BACKGROUND
) != 0,
733 append_drop_action_menu_item (menu
, _("Set as background for _this folder"),
734 NAUTILUS_DND_ACTION_SET_AS_FOLDER_BACKGROUND
,
735 (actions
& NAUTILUS_DND_ACTION_SET_AS_FOLDER_BACKGROUND
) != 0,
738 eel_gtk_menu_append_separator (GTK_MENU (menu
));
740 menu_item
= gtk_menu_item_new_with_mnemonic (_("Cancel"));
741 gtk_menu_shell_append (GTK_MENU_SHELL (menu
), menu_item
);
742 gtk_widget_show (menu_item
);
745 damd
.loop
= g_main_loop_new (NULL
, FALSE
);
747 g_signal_connect (menu
, "deactivate",
748 G_CALLBACK (menu_deactivate_callback
),
753 gtk_menu_popup (GTK_MENU (menu
), NULL
, NULL
,
754 NULL
, NULL
, 0, GDK_CURRENT_TIME
);
756 g_main_loop_run (damd
.loop
);
758 gtk_grab_remove (menu
);
760 g_main_loop_unref (damd
.loop
);
762 gtk_object_sink (GTK_OBJECT (menu
));
768 nautilus_drag_autoscroll_in_scroll_region (GtkWidget
*widget
)
770 float x_scroll_delta
, y_scroll_delta
;
772 nautilus_drag_autoscroll_calculate_delta (widget
, &x_scroll_delta
, &y_scroll_delta
);
774 return x_scroll_delta
!= 0 || y_scroll_delta
!= 0;
779 nautilus_drag_autoscroll_calculate_delta (GtkWidget
*widget
, float *x_scroll_delta
, float *y_scroll_delta
)
783 g_assert (GTK_IS_WIDGET (widget
));
785 gdk_window_get_pointer (widget
->window
, &x
, &y
, NULL
);
787 /* Find out if we are anywhere close to the tree view edges
788 * to see if we need to autoscroll.
793 if (x
< AUTO_SCROLL_MARGIN
) {
794 *x_scroll_delta
= (float)(x
- AUTO_SCROLL_MARGIN
);
797 if (x
> widget
->allocation
.width
- AUTO_SCROLL_MARGIN
) {
798 if (*x_scroll_delta
!= 0) {
799 /* Already trying to scroll because of being too close to
800 * the top edge -- must be the window is really short,
805 *x_scroll_delta
= (float)(x
- (widget
->allocation
.width
- AUTO_SCROLL_MARGIN
));
808 if (y
< AUTO_SCROLL_MARGIN
) {
809 *y_scroll_delta
= (float)(y
- AUTO_SCROLL_MARGIN
);
812 if (y
> widget
->allocation
.height
- AUTO_SCROLL_MARGIN
) {
813 if (*y_scroll_delta
!= 0) {
814 /* Already trying to scroll because of being too close to
815 * the top edge -- must be the window is really narrow,
820 *y_scroll_delta
= (float)(y
- (widget
->allocation
.height
- AUTO_SCROLL_MARGIN
));
823 if (*x_scroll_delta
== 0 && *y_scroll_delta
== 0) {
828 /* Adjust the scroll delta to the proper acceleration values depending on how far
829 * into the sroll margins we are.
830 * FIXME bugzilla.eazel.com 2486:
831 * we could use an exponential acceleration factor here for better feel
833 if (*x_scroll_delta
!= 0) {
834 *x_scroll_delta
/= AUTO_SCROLL_MARGIN
;
835 *x_scroll_delta
*= (MAX_AUTOSCROLL_DELTA
- MIN_AUTOSCROLL_DELTA
);
836 *x_scroll_delta
+= MIN_AUTOSCROLL_DELTA
;
839 if (*y_scroll_delta
!= 0) {
840 *y_scroll_delta
/= AUTO_SCROLL_MARGIN
;
841 *y_scroll_delta
*= (MAX_AUTOSCROLL_DELTA
- MIN_AUTOSCROLL_DELTA
);
842 *y_scroll_delta
+= MIN_AUTOSCROLL_DELTA
;
850 nautilus_drag_autoscroll_start (NautilusDragInfo
*drag_info
,
852 GtkFunction callback
,
855 if (nautilus_drag_autoscroll_in_scroll_region (widget
)) {
856 if (drag_info
->auto_scroll_timeout_id
== 0) {
857 drag_info
->waiting_to_autoscroll
= TRUE
;
858 drag_info
->start_auto_scroll_in
= eel_get_system_time()
859 + AUTOSCROLL_INITIAL_DELAY
;
861 drag_info
->auto_scroll_timeout_id
= g_timeout_add
862 (AUTOSCROLL_TIMEOUT_INTERVAL
,
867 if (drag_info
->auto_scroll_timeout_id
!= 0) {
868 g_source_remove (drag_info
->auto_scroll_timeout_id
);
869 drag_info
->auto_scroll_timeout_id
= 0;
875 nautilus_drag_autoscroll_stop (NautilusDragInfo
*drag_info
)
877 if (drag_info
->auto_scroll_timeout_id
!= 0) {
878 g_source_remove (drag_info
->auto_scroll_timeout_id
);
879 drag_info
->auto_scroll_timeout_id
= 0;
884 nautilus_drag_selection_includes_special_link (GList
*selection_list
)
889 for (node
= selection_list
; node
!= NULL
; node
= node
->next
) {
890 uri
= ((NautilusDragSelectionItem
*) node
->data
)->uri
;
892 if (eel_uri_is_desktop (uri
)) {