4 * ROX-Filer, filer for the ROX desktop project
5 * Copyright (C) 2005, the ROX-Filer team.
7 * This program is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License as published by the Free
9 * Software Foundation; either version 2 of the License, or (at your option)
12 * This program is distributed in the hope that it will be useful, but WITHOUT
13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
17 * You should have received a copy of the GNU General Public License along with
18 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
19 * Place, Suite 330, Boston, MA 02111-1307 USA
22 /* dnd.c - code for handling drag and drop */
31 #include <sys/param.h>
34 #include <X11/Xatom.h>
40 #include "view_iface.h"
46 #include "gui_support.h"
53 #include "usericons.h"
55 #include "bookmarks.h"
57 #define MAXURILEN 4096 /* Longest URI to allow */
59 gint drag_start_x
, drag_start_y
;
60 MotionType motion_state
= MOTION_NONE
;
62 static GList
*prompt_local_paths
= NULL
;
63 static gchar
*prompt_dest_path
= NULL
;
65 /* This keeps track of how many mouse buttons are currently down.
66 * We add a grab when it does 0->1 and release it on 1<-0.
68 * It may also be set to zero to disable the motion system (eg,
69 * when popping up a menu).
71 gint motion_buttons_pressed
= 0;
73 /* Static prototypes */
74 static void set_xds_prop(GdkDragContext
*context
, const char *text
);
75 static void desktop_drag_data_received(GtkWidget
*widget
,
76 GdkDragContext
*context
,
79 GtkSelectionData
*selection_data
,
82 FilerWindow
*filer_window
);
83 static void got_data_xds_reply(GtkWidget
*widget
,
84 GdkDragContext
*context
,
85 GtkSelectionData
*selection_data
,
87 static void got_data_raw(GtkWidget
*widget
,
88 GdkDragContext
*context
,
89 GtkSelectionData
*selection_data
,
91 static void got_uri_list(GtkWidget
*widget
,
92 GdkDragContext
*context
,
93 const char *selection_data
,
95 static gboolean
drag_drop(GtkWidget
*widget
,
96 GdkDragContext
*context
,
101 static void drag_data_received(GtkWidget
*widget
,
102 GdkDragContext
*context
,
105 GtkSelectionData
*selection_data
,
109 static gboolean
spring_now(gpointer data
);
110 static void spring_win_destroyed(GtkWidget
*widget
, gpointer data
);
111 static void menuitem_response(gpointer data
, guint action
, GtkWidget
*widget
);
112 static void prompt_action(GList
*paths
, gchar
*dest
);
123 static GtkItemFactoryEntry menu_def
[] = {
124 {N_("Copy"), NULL
, menuitem_response
, MENU_COPY
, NULL
},
125 {N_("Move"), NULL
, menuitem_response
, MENU_MOVE
, NULL
},
126 {N_("Link (relative)"), NULL
, menuitem_response
, MENU_LINK_REL
, NULL
},
127 {N_("Link (absolute)"), NULL
, menuitem_response
, MENU_LINK_ABS
, NULL
},
129 static GtkWidget
*dnd_menu
= NULL
;
131 /* Possible values for drop_dest_type (can also be NULL).
132 * In either case, drop_dest_path is the app/file/dir to use.
134 const char *drop_dest_prog
= "drop_dest_prog"; /* Run a program */
135 const char *drop_dest_dir
= "drop_dest_dir"; /* Save to path */
136 const char *drop_dest_pass_through
= "drop_dest_pass"; /* Pass to parent */
137 const char *drop_dest_bookmark
= "drop_dest_bookmark"; /* Add to bookmarks */
139 GdkAtom XdndDirectSave0
;
140 GdkAtom xa_text_plain
;
141 GdkAtom text_uri_list
;
142 GdkAtom text_x_moz_url
;
143 GdkAtom xa_application_octet_stream
;
144 GdkAtom xa_string
; /* Not actually used for DnD, but the others are here! */
146 int spring_in_progress
= 0; /* Non-zero changes filer_opendir slightly */
148 Option o_dnd_drag_to_icons
;
149 Option o_dnd_spring_open
;
150 static Option o_dnd_spring_delay
;
151 static Option o_dnd_middle_menu
;
152 Option o_dnd_left_menu
;
153 static Option o_dnd_uri_handler
;
157 XdndDirectSave0
= gdk_atom_intern("XdndDirectSave0", FALSE
);
158 xa_text_plain
= gdk_atom_intern("text/plain", FALSE
);
159 text_uri_list
= gdk_atom_intern("text/uri-list", FALSE
);
160 text_x_moz_url
= gdk_atom_intern("text/x-moz-url", FALSE
);
161 xa_application_octet_stream
= gdk_atom_intern("application/octet-stream",
163 xa_string
= gdk_atom_intern("STRING", FALSE
);
165 option_add_int(&o_dnd_drag_to_icons
, "dnd_drag_to_icons", 1);
166 option_add_int(&o_dnd_spring_open
, "dnd_spring_open", 0);
167 option_add_int(&o_dnd_spring_delay
, "dnd_spring_delay", 400);
168 option_add_int(&o_dnd_left_menu
, "dnd_left_menu", TRUE
);
169 option_add_int(&o_dnd_middle_menu
, "dnd_middle_menu", TRUE
);
171 option_add_string(&o_dnd_uri_handler
, "dnd_uri_handler",
175 /* SUPPORT FUNCTIONS */
177 /* Set the XdndDirectSave0 property on the source window for this context */
178 static void set_xds_prop(GdkDragContext
*context
, const char *text
)
180 gdk_property_change(context
->source_window
,
183 GDK_PROP_MODE_REPLACE
,
188 static char *get_xds_prop(GdkDragContext
*context
)
193 if (gdk_property_get(context
->source_window
,
199 &length
, &prop_text
) && prop_text
)
201 /* Terminate the string */
202 prop_text
= g_realloc(prop_text
, length
+ 1);
203 prop_text
[length
] = '\0';
204 /* Note: assuming UTF-8 (should convert here) */
211 /* Is the sender willing to supply this target type? */
212 gboolean
provides(GdkDragContext
*context
, GdkAtom target
)
214 GList
*targets
= context
->targets
;
216 while (targets
&& ((GdkAtom
) targets
->data
!= target
))
217 targets
= targets
->next
;
219 return targets
!= NULL
;
222 /* DRAGGING FROM US */
224 /* The user has held the mouse button down over a group of item and moved -
225 * start a drag. 'uri_list' is copied, so you can delete it straight away.
227 void drag_selection(GtkWidget
*widget
, GdkEventMotion
*event
, guchar
*uri_list
)
230 GdkDragContext
*context
;
231 GdkDragAction actions
;
232 GtkTargetList
*target_list
;
233 GtkTargetEntry target_table
[] = {
234 {"text/uri-list", 0, TARGET_URI_LIST
},
235 {"UTF8_STRING", 0, TARGET_UTF8
},
238 if (event
->state
& GDK_BUTTON1_MASK
)
239 actions
= GDK_ACTION_COPY
| GDK_ACTION_MOVE
240 | GDK_ACTION_LINK
| GDK_ACTION_ASK
;
243 if (o_dnd_middle_menu
.int_value
)
244 actions
= GDK_ACTION_ASK
;
246 actions
= GDK_ACTION_MOVE
;
249 target_list
= gtk_target_list_new(target_table
,
250 G_N_ELEMENTS(target_table
));
252 context
= gtk_drag_begin(widget
,
255 (event
->state
& GDK_BUTTON1_MASK
) ? 1 :
256 (event
->state
& GDK_BUTTON2_MASK
) ? 2 : 3,
259 g_dataset_set_data_full(context
, "uri_list",
260 g_strdup(uri_list
), g_free
);
262 pixbuf
= gtk_widget_render_icon(widget
, GTK_STOCK_DND_MULTIPLE
,
263 GTK_ICON_SIZE_DIALOG
, NULL
);
264 gtk_drag_set_icon_pixbuf(context
, pixbuf
, 0, 0);
265 g_object_unref(pixbuf
);
268 /* Copy/Load this item into another directory/application */
269 void drag_one_item(GtkWidget
*widget
,
270 GdkEventMotion
*event
,
271 const guchar
*full_path
,
276 GdkDragContext
*context
;
277 GdkDragAction actions
;
278 GtkTargetList
*target_list
;
279 GtkTargetEntry target_table
[] = {
280 {"text/uri-list", 0, TARGET_URI_LIST
},
281 {"UTF8_STRING", 0, TARGET_UTF8
},
282 {"application/octet-stream", 0, TARGET_RAW
},
286 g_return_if_fail(full_path
!= NULL
);
287 g_return_if_fail(item
!= NULL
);
290 image
= di_image(item
);
292 if (item
->base_type
== TYPE_FILE
)
294 MIME_type
*t
= item
->mime_type
;
296 target_table
[3].target
= g_strconcat(t
->media_type
, "/",
298 target_list
= gtk_target_list_new(target_table
,
299 G_N_ELEMENTS(target_table
));
300 g_free(target_table
[3].target
);
303 target_list
= gtk_target_list_new(target_table
, 2);
305 if (event
->state
& GDK_BUTTON1_MASK
)
306 actions
= GDK_ACTION_COPY
| GDK_ACTION_ASK
307 | GDK_ACTION_MOVE
| GDK_ACTION_LINK
;
310 if (o_dnd_middle_menu
.int_value
)
311 actions
= GDK_ACTION_ASK
;
313 actions
= GDK_ACTION_MOVE
;
316 context
= gtk_drag_begin(widget
,
319 (event
->state
& GDK_BUTTON1_MASK
) ? 1 :
320 (event
->state
& GDK_BUTTON2_MASK
) ? 2 : 3,
323 g_dataset_set_data_full(context
, "full_path",
324 g_strdup(full_path
), g_free
);
325 tmp
= (char *) encode_path_as_uri(full_path
);
326 uri
= g_strconcat(tmp
, "\r\n", NULL
);
327 /*printf("%s\n", tmp);*/
329 g_dataset_set_data_full(context
, "uri_list", uri
, g_free
);
331 g_return_if_fail(image
!= NULL
);
333 gtk_drag_set_icon_pixbuf(context
, image
->pixbuf
, 0, 0);
336 /* Convert text/uri-list data to UTF8_STRING.
337 * g_free() the result.
339 static gchar
*uri_list_to_utf8(const char *uri_list
)
342 GList
*uris
, *next_uri
;
345 new = g_string_new(NULL
);
347 uris
= uri_list_to_glist(uri_list
);
349 for (next_uri
= uris
; next_uri
; next_uri
= next_uri
->next
)
351 EscapedPath
*uri
= next_uri
->data
;
354 local
= get_local_path(uri
);
357 g_string_append_c(new, ' ');
361 g_string_append(new, local
);
365 g_warning("Not local!\n");
374 g_string_free(new, FALSE
);
379 /* Called when a remote app wants us to send it some data.
380 * TODO: Maybe we should handle errors better (ie, let the remote app know
381 * the drag has failed)?
383 void drag_data_get(GtkWidget
*widget
,
384 GdkDragContext
*context
,
385 GtkSelectionData
*selection_data
,
390 char *to_send
= "E"; /* Default to sending an error */
391 long to_send_length
= 1;
392 gboolean delete_once_sent
= FALSE
;
396 type
= selection_data
->target
;
401 path
= g_dataset_get_data(context
, "full_path");
402 if (path
&& load_file(path
, &to_send
, &to_send_length
))
404 delete_once_sent
= TRUE
;
407 g_warning("drag_data_get: Can't find path!\n");
412 uri_list
= g_dataset_get_data(context
, "uri_list");
413 to_send
= uri_list_to_utf8(uri_list
);
414 to_send_length
= strlen(to_send
);
415 delete_once_sent
= TRUE
;
418 case TARGET_URI_LIST
:
419 to_send
= g_dataset_get_data(context
, "uri_list");
420 to_send_length
= strlen(to_send
);
421 type
= text_uri_list
; /* (needed for xine) */
422 delete_once_sent
= FALSE
;
425 delayed_error("drag_data_get: %s",
426 _("Internal error - bad info type"));
430 gtk_selection_data_set(selection_data
,
436 if (delete_once_sent
)
442 /* Set up this widget as a drop-target.
443 * Does not attach any motion handlers.
445 void make_drop_target(GtkWidget
*widget
, GtkDestDefaults defaults
)
447 GtkTargetEntry target_table
[] =
449 {"text/uri-list", 0, TARGET_URI_LIST
},
450 {"text/x-moz-url", 0, TARGET_MOZ_URL
},
451 {"XdndDirectSave0", 0, TARGET_XDS
},
452 {"application/octet-stream", 0, TARGET_RAW
},
455 gtk_drag_dest_set(widget
,
458 sizeof(target_table
) / sizeof(*target_table
),
459 GDK_ACTION_COPY
| GDK_ACTION_ASK
| GDK_ACTION_MOVE
460 | GDK_ACTION_LINK
| GDK_ACTION_PRIVATE
);
462 g_signal_connect(widget
, "drag_drop", G_CALLBACK(drag_drop
), NULL
);
463 g_signal_connect(widget
, "drag_data_received",
464 G_CALLBACK(drag_data_received
), NULL
);
467 /* Like drag_set_dest, but for a pinboard-type widget */
468 void drag_set_pinboard_dest(GtkWidget
*widget
)
470 GtkTargetEntry target_table
[] = {
471 {"text/uri-list", 0, TARGET_URI_LIST
},
474 gtk_drag_dest_set(widget
,
475 GTK_DEST_DEFAULT_DROP
,
477 sizeof(target_table
) / sizeof(*target_table
),
479 g_signal_connect(widget
, "drag_data_received",
480 G_CALLBACK(desktop_drag_data_received
), NULL
);
483 /* item is the item the file is held over, NULL for directory background.
484 * 'item' may be NULL on exit if the drop should be treated as onto the
485 * background. Disallow drags to a selected icon before calling this.
487 * Returns NULL to reject the drop, or drop_dest_prog/drop_dest_dir to
488 * accept. Build the path based on item.
490 const guchar
*dnd_motion_item(GdkDragContext
*context
, DirItem
**item_p
)
492 DirItem
*item
= *item_p
;
496 /* If we didn't drop onto a directory, application or
497 * executable file then act as though the drop is to the
500 if (item
->base_type
!= TYPE_DIRECTORY
&& !EXECUTABLE_FILE(item
))
509 /* Drop onto the window background */
511 return drop_dest_dir
;
514 /* Drop onto a program/directory of some sort */
516 if (item
->base_type
== TYPE_DIRECTORY
&&
517 !(item
->flags
& ITEM_FLAG_APPDIR
))
519 /* A normal directory */
520 if (provides(context
, text_uri_list
) ||
521 provides(context
, text_x_moz_url
) ||
522 provides(context
, XdndDirectSave0
))
523 return drop_dest_dir
;
527 if (provides(context
, text_uri_list
) ||
528 provides(context
, text_x_moz_url
) ||
529 provides(context
, xa_application_octet_stream
))
530 return drop_dest_prog
;
536 /* User has tried to drop some data on us. Decide what format we would
539 static gboolean
drag_drop(GtkWidget
*widget
,
540 GdkDragContext
*context
,
546 const char *error
= NULL
;
547 char *leafname
= NULL
;
548 GdkAtom target
= GDK_NONE
;
550 char *dest_type
= NULL
;
552 dest_path
= g_dataset_get_data(context
, "drop_dest_path");
553 dest_type
= g_dataset_get_data(context
, "drop_dest_type");
555 if (dest_type
== drop_dest_pass_through
)
556 return FALSE
; /* Let the parent widget handle it */
558 if (dest_type
== drop_dest_bookmark
)
560 if (provides(context
, text_uri_list
))
561 gtk_drag_get_data(widget
, context
, text_uri_list
, time
);
564 gtk_drag_finish(context
, FALSE
, FALSE
, time
);
565 delayed_error(_("Drag a directory here to "
571 g_return_val_if_fail(dest_path
!= NULL
, TRUE
);
573 if (dest_type
== drop_dest_dir
&& provides(context
, XdndDirectSave0
))
575 leafname
= get_xds_prop(context
);
578 if (strchr(leafname
, '/'))
580 error
= _("XDS protocol error: "
581 "leafname may not contain '/'\n");
582 null_g_free(&leafname
);
589 dest_uri
= g_strconcat("file://",
590 our_host_name_for_dnd(),
593 set_xds_prop(context
,
594 make_path(dest_uri
, leafname
));
598 target
= XdndDirectSave0
;
599 g_dataset_set_data_full(context
, "leafname",
605 "XdndDirectSave0 target provided, but the atom "
606 "XdndDirectSave0 (type text/plain) did not "
607 "contain a leafname\n");
609 else if (provides(context
, text_uri_list
))
610 target
= text_uri_list
;
611 else if (provides(context
, text_x_moz_url
))
612 target
= text_x_moz_url
;
613 else if (provides(context
, xa_application_octet_stream
))
614 target
= xa_application_octet_stream
;
617 if (dest_type
== drop_dest_dir
)
618 error
= _("Sorry - I require a target type of "
619 "text/uri-list or XdndDirectSave0.");
621 error
= _("Sorry - I require a target type of "
622 "text/uri-list or application/octet-stream.");
627 gtk_drag_finish(context
, FALSE
, FALSE
, time
); /* Failure */
629 delayed_error("%s", error
);
632 gtk_drag_get_data(widget
, context
, target
, time
);
637 /* Called when a text/uri-list arrives */
638 static void desktop_drag_data_received(GtkWidget
*widget
,
639 GdkDragContext
*context
,
642 GtkSelectionData
*selection_data
,
645 FilerWindow
*filer_window
)
648 char *error_example
= NULL
;
651 if (!selection_data
->data
)
657 if (pinboard_drag_in_progress
)
659 pinboard_move_icons();
663 gdk_window_get_position(widget
->window
, &dx
, &dy
);
667 uris
= uri_list_to_glist(selection_data
->data
);
669 for (next
= uris
; next
; next
= next
->next
)
673 path
= get_local_path((EscapedPath
*) next
->data
);
676 pinboard_pin(path
, NULL
, x
, y
, NULL
);
680 else if (!error_example
)
681 error_example
= g_strdup(next
->data
);
691 delayed_error(_("Failed to add some items to the pinboard, "
692 "because they are on a remote machine. For example:\n"
693 "\n%s"), error_example
);
694 g_free(error_example
);
698 /* Convert Mozilla's text/x-moz-uri into a text/uri-list */
699 static void got_moz_uri(GtkWidget
*widget
,
700 GdkDragContext
*context
,
701 GtkSelectionData
*selection_data
,
704 gchar
*utf8
, *uri_list
, *eol
;
706 utf8
= g_utf16_to_utf8((gunichar2
*) selection_data
->data
,
707 (glong
) selection_data
->length
,
710 eol
= utf8
? strchr(utf8
, '\n') : NULL
;
713 delayed_error("Invalid UTF16 from text/x-moz-url target");
715 gtk_drag_finish(context
, FALSE
, FALSE
, time
);
720 uri_list
= g_strconcat(utf8
, "\r\n", NULL
);
723 got_uri_list(widget
, context
, uri_list
, time
);
728 /* Called when some data arrives from the remote app (which we asked for
731 static void drag_data_received(GtkWidget
*widget
,
732 GdkDragContext
*context
,
735 GtkSelectionData
*selection_data
,
740 if (!selection_data
->data
)
743 gtk_drag_finish(context
, FALSE
, FALSE
, time
); /* Failure */
750 got_data_xds_reply(widget
, context
,
751 selection_data
, time
);
754 got_data_raw(widget
, context
, selection_data
, time
);
756 case TARGET_URI_LIST
:
757 got_uri_list(widget
, context
, selection_data
->data
,
761 got_moz_uri(widget
, context
, selection_data
, time
);
764 gtk_drag_finish(context
, FALSE
, FALSE
, time
);
765 delayed_error("drag_data_received: %s",
766 _("Unknown target"));
771 static void got_data_xds_reply(GtkWidget
*widget
,
772 GdkDragContext
*context
,
773 GtkSelectionData
*selection_data
,
776 gboolean mark_unsafe
= TRUE
;
777 char response
= *selection_data
->data
;
778 const char *error
= NULL
;
781 dest_path
= g_dataset_get_data(context
, "drop_dest_path");
783 if (selection_data
->length
!= 1)
788 /* Sender couldn't save there - ask for another
791 if (provides(context
, xa_application_octet_stream
))
793 mark_unsafe
= FALSE
; /* Wait and see */
795 gtk_drag_get_data(widget
, context
,
796 xa_application_octet_stream
, time
);
799 error
= _("Remote app can't or won't send me "
802 else if (response
== 'S')
804 /* Success - data is saved */
805 mark_unsafe
= FALSE
; /* It really is safe */
806 gtk_drag_finish(context
, TRUE
, FALSE
, time
);
808 refresh_dirs(dest_path
);
810 else if (response
!= 'E')
812 error
= _("XDS protocol error: "
813 "return code should be 'S', 'F' or 'E'\n");
815 /* else: error has been reported by the sender */
819 set_xds_prop(context
, "");
820 /* Unsave also implies that the drag failed */
821 gtk_drag_finish(context
, FALSE
, FALSE
, time
);
825 delayed_error("%s", error
);
828 static void got_data_raw(GtkWidget
*widget
,
829 GdkDragContext
*context
,
830 GtkSelectionData
*selection_data
,
833 const char *leafname
;
835 const char *error
= NULL
;
836 const char *dest_path
;
838 g_return_if_fail(selection_data
->data
!= NULL
);
840 dest_path
= g_dataset_get_data(context
, "drop_dest_path");
842 if (context
->action
== GDK_ACTION_ASK
)
844 gtk_drag_finish(context
, FALSE
, FALSE
, time
); /* Failure */
845 delayed_error(_("Sorry, can't display a menu of actions "
846 "for a remote file / raw data."));
850 if (g_dataset_get_data(context
, "drop_dest_type") == drop_dest_prog
)
852 /* The data needs to be sent to an application */
853 run_with_data(dest_path
,
854 selection_data
->data
, selection_data
->length
);
855 gtk_drag_finish(context
, TRUE
, FALSE
, time
); /* Success! */
859 leafname
= g_dataset_get_data(context
, "leafname");
861 leafname
= _("UntitledData");
863 fd
= open(make_path(dest_path
, leafname
),
864 O_WRONLY
| O_CREAT
| O_EXCL
| O_NOCTTY
,
865 S_IRUSR
| S_IRGRP
| S_IROTH
|
866 S_IWUSR
| S_IWGRP
| S_IWOTH
);
869 error
= g_strerror(errno
);
873 selection_data
->data
,
874 selection_data
->length
) == -1)
875 error
= g_strerror(errno
);
877 if (close(fd
) == -1 && !error
)
878 error
= g_strerror(errno
);
880 refresh_dirs(dest_path
);
885 if (provides(context
, XdndDirectSave0
))
886 set_xds_prop(context
, "");
887 gtk_drag_finish(context
, FALSE
, FALSE
, time
); /* Failure */
888 delayed_error(_("Error saving file: %s"), error
);
891 gtk_drag_finish(context
, TRUE
, FALSE
, time
); /* Success! */
894 static gboolean
uri_is_local(const EscapedPath
*uri
)
897 path
= get_local_path(uri
);
904 /* Run the shell command 'command', replacing $1 with 'arg' */
905 static void run_with_argument(const char *dir
,
911 argv
= g_ptr_array_new();
913 g_ptr_array_add(argv
, "sh");
914 g_ptr_array_add(argv
, "-c");
915 g_ptr_array_add(argv
, (char *) command
);
916 g_ptr_array_add(argv
, "sh");
917 g_ptr_array_add(argv
, (char *) arg
);
918 g_ptr_array_add(argv
, NULL
);
920 rox_spawn(dir
, (const gchar
**) argv
->pdata
);
922 g_ptr_array_free(argv
, TRUE
);
925 /* We've got a list of URIs from somewhere (probably another filer window).
926 * If the files are on the local machine then try to copy them ourselves,
927 * otherwise, if there was only one file and application/octet-stream was
928 * provided, get the data via the X server.
929 * For http:, https: or ftp: schemes, use the download handler.
931 static void got_uri_list(GtkWidget
*widget
,
932 GdkDragContext
*context
,
933 const char *selection_data
,
937 const char *error
= NULL
;
939 gboolean send_reply
= TRUE
;
943 dest_path
= g_dataset_get_data(context
, "drop_dest_path");
944 type
= g_dataset_get_data(context
, "drop_dest_type");
946 uri_list
= uri_list_to_glist(selection_data
);
948 if (type
== drop_dest_bookmark
)
951 for (next
= uri_list
; next
; next
= next
->next
)
952 bookmarks_add_uri((EscapedPath
*) next
->data
);
953 destroy_glist(&uri_list
);
954 gtk_drag_finish(context
, TRUE
, FALSE
, time
); /* Success! */
958 g_return_if_fail(dest_path
!= NULL
);
961 error
= _("No URIs in the text/uri-list (nothing to do!)");
962 else if (context
->action
!= GDK_ACTION_ASK
&& type
== drop_dest_prog
)
963 run_with_files(dest_path
, uri_list
);
964 else if ((!uri_list
->next
) && !uri_is_local(uri_list
->data
))
966 /* There is one URI in the list, and it's not on the local
967 * machine. Get it via the X server if possible.
970 if (provides(context
, xa_application_octet_stream
))
973 leaf
= strrchr(uri_list
->data
, '/');
977 leaf
= uri_list
->data
;
978 g_dataset_set_data_full(context
, "leafname",
979 unescape_uri((EscapedPath
*) leaf
), g_free
);
980 gtk_drag_get_data(widget
, context
,
981 xa_application_octet_stream
, time
);
984 else if ((strncasecmp(uri_list
->data
, "http:", 5) == 0) ||
985 (strncasecmp(uri_list
->data
, "https:", 6) == 0) ||
986 (strncasecmp(uri_list
->data
, "ftp:", 4) == 0))
988 run_with_argument(dest_path
,
989 o_dnd_uri_handler
.value
,
990 (char *) uri_list
->data
);
993 error
= _("Can't get data from remote machine "
994 "(application/octet-stream not provided)");
998 GList
*local_paths
= NULL
;
1000 /* Either one local URI, or a list. If everything in the list
1001 * isn't local then we are stuck.
1004 for (next_uri
= uri_list
; next_uri
; next_uri
= next_uri
->next
)
1008 path
= get_local_path((EscapedPath
*) next_uri
->data
);
1009 /*printf("%s -> %s\n", (char *) next_uri->data,
1010 path? path: "NULL");*/
1013 local_paths
= g_list_append(local_paths
,
1016 error
= _("Some of these files are on a "
1017 "different machine - they will be "
1023 error
= _("None of these files are on the local "
1024 "machine - I can't operate on multiple "
1025 "remote files - sorry.");
1027 else if (context
->action
== GDK_ACTION_ASK
)
1028 prompt_action(local_paths
, dest_path
);
1029 else if (context
->action
== GDK_ACTION_MOVE
)
1030 action_move(local_paths
, dest_path
, NULL
, -1);
1031 else if (context
->action
== GDK_ACTION_COPY
)
1032 action_copy(local_paths
, dest_path
, NULL
, -1);
1033 else if (context
->action
== GDK_ACTION_LINK
)
1034 action_link(local_paths
, dest_path
, NULL
, TRUE
);
1036 error
= _("Unknown action requested");
1038 destroy_glist(&local_paths
);
1043 gtk_drag_finish(context
, FALSE
, FALSE
, time
); /* Failure */
1044 delayed_error(_("Error getting file list: %s"), error
);
1046 else if (send_reply
)
1047 gtk_drag_finish(context
, TRUE
, FALSE
, time
); /* Success! */
1049 destroy_glist(&uri_list
);
1052 /* Called when an item from the ACTION_ASK menu is chosen */
1053 static void menuitem_response(gpointer data
, guint action
, GtkWidget
*widget
)
1055 if (action
== MENU_MOVE
)
1056 action_move(prompt_local_paths
, prompt_dest_path
, NULL
, -1);
1057 else if (action
== MENU_COPY
)
1058 action_copy(prompt_local_paths
, prompt_dest_path
, NULL
, -1);
1059 else if (action
== MENU_LINK_REL
)
1060 action_link(prompt_local_paths
, prompt_dest_path
, NULL
, TRUE
);
1061 else if (action
== MENU_LINK_ABS
)
1062 action_link(prompt_local_paths
, prompt_dest_path
, NULL
, FALSE
);
1065 /* When some local files are dropped somewhere with ACTION_ASK, this
1066 * function is called to display the menu.
1068 static void prompt_action(GList
*paths
, gchar
*dest
)
1073 if (prompt_local_paths
)
1075 destroy_glist(&prompt_local_paths
);
1076 null_g_free(&prompt_dest_path
);
1079 /* Make a copy of the arguments */
1080 for (next
= paths
; next
; next
= next
->next
)
1081 prompt_local_paths
= g_list_append(prompt_local_paths
,
1082 g_strdup((gchar
*) next
->data
));
1083 prompt_dest_path
= g_strdup(dest
);
1087 GtkItemFactory
*item_factory
;
1089 item_factory
= menu_create(menu_def
,
1090 sizeof(menu_def
) / sizeof(*menu_def
),
1092 dnd_menu
= gtk_item_factory_get_widget(item_factory
, "<dnd>");
1095 /* Shade 'Set Icon' if there are multiple files */
1096 menu_set_items_shaded(dnd_menu
, g_list_length(paths
) != 1, 4, 1);
1098 event
= gtk_get_current_event();
1099 show_popup_menu(dnd_menu
, event
, 1);
1101 gdk_event_free(event
);
1105 /* SPRING-LOADING */
1107 /* This is the code that makes directories pop open if you hold a
1110 * First, call dnd_spring_load(context) to arm the system.
1111 * After a timeout (1/2 a second) the dest_path directory will be
1112 * opened in a new window, unless dnd_spring_abort is called first.
1115 static gint spring_timeout
= -1;
1116 static GdkDragContext
*spring_context
= NULL
;
1117 static FilerWindow
*spring_window
= NULL
;
1118 static FilerWindow
*spring_src_window
= NULL
;
1120 void dnd_spring_load(GdkDragContext
*context
, FilerWindow
*src_win
)
1122 g_return_if_fail(context
!= NULL
);
1124 if (!o_dnd_spring_open
.int_value
)
1130 spring_context
= context
;
1131 g_object_ref(spring_context
);
1132 spring_src_window
= src_win
;
1133 spring_timeout
= gtk_timeout_add(
1134 o_dnd_spring_delay
.int_value
, spring_now
, NULL
);
1137 void dnd_spring_abort(void)
1139 if (!spring_context
)
1142 g_object_unref(spring_context
);
1143 spring_context
= NULL
;
1144 gtk_timeout_remove(spring_timeout
);
1147 /* If all mod keys are released, no buttons are pressed, and the
1148 * mouse is outside the spring window, then close it.
1150 static gboolean
spring_check_idle(gpointer data
)
1157 if (!get_pointer_xy(&p_x
, &p_y
))
1160 GdkWindow *win = spring_window->window->window;
1164 gdk_window_get_position(win, &x, &y);
1165 gdk_window_get_size(win, &w, &h);
1167 if (p_x < x || p_x > x + w || p_y < y || p_y > y + h)
1171 gtk_widget_destroy(spring_window
->window
);
1172 return FALSE
; /* Got it! */
1175 return TRUE
; /* Try again later */
1178 static gboolean
spring_now(gpointer data
)
1181 const guchar
*dest_path
;
1184 g_return_val_if_fail(spring_context
!= NULL
, FALSE
);
1185 g_return_val_if_fail(!spring_in_progress
, FALSE
);
1187 type
= g_dataset_get_data(spring_context
, "drop_dest_type");
1188 if (type
== drop_dest_bookmark
)
1194 dest_path
= g_dataset_get_data(spring_context
, "drop_dest_path");
1195 g_return_val_if_fail(dest_path
!= NULL
, FALSE
);
1198 * Note: Due to a bug in gtk, if a window disappears during
1199 * a drag and the pointer moves over where the window was,
1200 * the sender crashes! Therefore, do not close any windows
1201 * while dragging! (fixed in later versions)
1205 gtk_widget_destroy(spring_window->window);
1208 get_pointer_xy(&x
, &y
);
1210 spring_in_progress
++;
1213 view_cursor_to_iter(spring_window
->view
, NULL
);
1214 filer_change_to(spring_window
, dest_path
, NULL
);
1215 /* DON'T move the window. Gtk+ sometimes doesn't
1221 spring_window
= filer_opendir(dest_path
,
1222 spring_src_window
, NULL
);
1225 gtk_timeout_add(500, spring_check_idle
, NULL
);
1226 g_signal_connect(spring_window
->window
, "destroy",
1227 G_CALLBACK(spring_win_destroyed
), NULL
);
1228 centre_window(spring_window
->window
->window
, x
, y
);
1231 spring_in_progress
--;
1239 static void spring_win_destroyed(GtkWidget
*widget
, gpointer data
)
1241 spring_window
= NULL
;
1244 /* HANDLING MOTION EVENTS */
1246 /* If not-NULL, then this widget has a grab */
1247 static GtkWidget
*motion_widget
= NULL
;
1249 /* If TRUE, we must gdk_pointer_ungrab() too when finishing */
1250 static gboolean motion_pointer_grab
= FALSE
;
1252 /* Call this on a button press event. It stores the mouse position
1253 * as the start of the new drag and returns TRUE if all is well.
1254 * Further motions events are disabled at this point - you must
1255 * then call dnd_motion_start() to set the type of motion expected.
1256 * Grabs the widget on the first press.
1258 * If the system is not ready to handle a motion event (because a
1259 * button is already held down?) it does nothing and returns FALSE.
1261 * If the event is not a single click then it simply returns TRUE.
1263 gboolean
dnd_motion_press(GtkWidget
*widget
, GdkEventButton
*event
)
1265 if (event
->type
!= GDK_BUTTON_PRESS
)
1266 return TRUE
; /* Not a click event! */
1268 motion_buttons_pressed
++;
1269 if (motion_buttons_pressed
== 1)
1271 /* g_print("[ grab! ]\n"); */
1272 gtk_grab_add(widget
);
1273 motion_widget
= widget
;
1276 if (motion_state
!= MOTION_NONE
)
1277 return FALSE
; /* Ignore clicks - we're busy! */
1279 motion_state
= MOTION_DISABLED
;
1280 drag_start_x
= event
->x_root
;
1281 drag_start_y
= event
->y_root
;
1286 /* After the button press event, decide what kind of motion is expected.
1287 * If you don't call this then the motion system is disabled - call
1288 * dnd_motion_release() to reset it.
1290 * Note: If you open a popup menu or start DND call dnd_motion_ungrab()
1293 void dnd_motion_start(MotionType motion
)
1295 g_return_if_fail(motion_state
== MOTION_DISABLED
);
1297 motion_state
= motion
;
1300 /* Call this on a button release event. If some buttons are still pressed,
1301 * returns TRUE and does nothing.
1303 * Otherwise, it resets the motion system to be ready again and returns TRUE.
1305 * If the motion system wasn't being used (MOTION_NONE) then it does nothing
1306 * and returns FALSE - process the release event yourself as it isn't part
1307 * of a motion. This also happens if a motion was primed but never happened.
1309 gboolean
dnd_motion_release(GdkEventButton
*event
)
1311 MotionType motion
= motion_state
;
1312 gint drag_threshold
;
1315 if (motion_buttons_pressed
== 0)
1316 return TRUE
; /* We were disabled */
1318 if (motion_buttons_pressed
== 1)
1319 dnd_motion_ungrab();
1322 motion_buttons_pressed
--;
1326 if (motion
== MOTION_REPOSITION
|| motion
== MOTION_DISABLED
)
1327 return TRUE
; /* Already done something - eat the event */
1329 /* Eat release events that happen too far from the click
1330 * source. Otherwise, allow the caller to treat this as a click
1331 * that never became a motion.
1333 dx
= event
->x_root
- drag_start_x
;
1334 dy
= event
->y_root
- drag_start_y
;
1336 g_object_get(gtk_settings_get_default(),
1337 "gtk-dnd-drag-threshold", &drag_threshold
,
1340 return ABS(dx
) > drag_threshold
|| ABS(dy
) > drag_threshold
;
1343 /* Use this to disable the motion system. The system will be reset once
1344 * all mouse buttons are released.
1346 void dnd_motion_disable(void)
1348 g_return_if_fail(motion_state
!= MOTION_NONE
&&
1349 motion_state
!= MOTION_DISABLED
);
1351 motion_state
= MOTION_DISABLED
;
1354 /* Use this if something else is going to grab the pointer so that
1355 * we won't get any more motion or release events.
1357 void dnd_motion_ungrab(void)
1359 if (motion_buttons_pressed
> 0)
1361 if (motion_pointer_grab
)
1363 gdk_pointer_ungrab(GDK_CURRENT_TIME
);
1364 motion_pointer_grab
= FALSE
;
1365 /* g_print("[ ungrab_pointer ]\n"); */
1367 gtk_grab_remove(motion_widget
);
1368 motion_widget
= NULL
;
1369 motion_buttons_pressed
= 0;
1370 /* g_print("[ ungrab ]\n"); */
1373 motion_state
= MOTION_NONE
;
1376 /* Call this on motion events. If the mouse position is far enough
1377 * from the click position, returns TRUE and does dnd_motion_ungrab().
1378 * You should then start regular drag-and-drop.
1380 * Otherwise, returns FALSE.
1382 gboolean
dnd_motion_moved(GdkEventMotion
*event
)
1384 gint drag_threshold
;
1387 g_object_get(gtk_settings_get_default(),
1388 "gtk-dnd-drag-threshold", &drag_threshold
,
1391 dx
= event
->x_root
- drag_start_x
;
1392 dy
= event
->y_root
- drag_start_y
;
1394 if (ABS(dx
) <= drag_threshold
&& ABS(dy
) <= drag_threshold
)
1395 return FALSE
; /* Not far enough */
1397 dnd_motion_ungrab();
1402 /* Normally, the X server will automatically grab the pointer on a
1403 * button press and ungrab on release. However, if the grab widget
1404 * is reparented then call this to re-aquire the grab.
1406 void dnd_motion_grab_pointer(void)
1408 g_return_if_fail(motion_widget
!= NULL
);
1410 gdk_pointer_grab(motion_widget
->window
, FALSE
,
1411 GDK_POINTER_MOTION_MASK
|
1412 GDK_BUTTON_RELEASE_MASK
,
1413 FALSE
, NULL
, GDK_CURRENT_TIME
);
1415 motion_pointer_grab
= TRUE
;