4 * ROX-Filer, filer for the ROX desktop project
5 * Copyright (C) 2000, Thomas Leonard, <tal197@users.sourceforge.net>.
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>
36 #include "collection.h"
45 #include "gui_support.h"
52 #define MAXURILEN 4096 /* Longest URI to allow */
54 /* Static prototypes */
55 static void set_xds_prop(GdkDragContext
*context
, char *text
);
56 static gboolean
drag_motion(GtkWidget
*widget
,
57 GdkDragContext
*context
,
61 FilerWindow
*filer_window
);
62 static void drag_leave(GtkWidget
*widget
,
63 GdkDragContext
*context
,
65 FilerWindow
*filer_window
);
66 static void desktop_drag_data_received(GtkWidget
*widget
,
67 GdkDragContext
*context
,
70 GtkSelectionData
*selection_data
,
73 FilerWindow
*filer_window
);
74 static void got_data_xds_reply(GtkWidget
*widget
,
75 GdkDragContext
*context
,
76 GtkSelectionData
*selection_data
,
78 static void got_data_raw(GtkWidget
*widget
,
79 GdkDragContext
*context
,
80 GtkSelectionData
*selection_data
,
82 static GSList
*uri_list_to_gslist(char *uri_list
);
83 static void got_uri_list(GtkWidget
*widget
,
84 GdkDragContext
*context
,
85 GtkSelectionData
*selection_data
,
87 static GtkWidget
*create_options();
88 static void update_options();
89 static void set_options();
90 static void save_options();
91 static char *load_no_hostnames(char *data
);
92 static char *drag_to_icons(char *data
);
93 static char *spring_open(char *data
);
94 static void drag_end(GtkWidget
*widget
,
95 GdkDragContext
*context
,
96 FilerWindow
*filer_window
);
97 static gboolean
drag_drop(GtkWidget
*widget
,
98 GdkDragContext
*context
,
103 static void drag_data_received(GtkWidget
*widget
,
104 GdkDragContext
*context
,
107 GtkSelectionData
*selection_data
,
111 static gboolean
spring_now(gpointer data
);
112 static void spring_win_destroyed(GtkWidget
*widget
, gpointer data
);
114 /* The handler of the signal handler for scroll events.
115 * This is used to cancel spring loading when autoscrolling is used.
117 static gint scrolled_signal
= -1;
118 static GtkObject
*scrolled_adj
= NULL
; /* The object watched */
120 /* Possible values for drop_dest_type (can also be NULL).
121 * In either case, drop_dest_path is the app/file/dir to use.
123 char *drop_dest_prog
= "drop_dest_prog"; /* Run a program */
124 char *drop_dest_dir
= "drop_dest_dir"; /* Save to path */
126 static OptionsSection options
=
128 N_("Drag and Drop options"),
143 GdkAtom XdndDirectSave0
;
144 GdkAtom _rox_run_action
;
145 GdkAtom xa_text_plain
;
146 GdkAtom text_uri_list
;
147 GdkAtom application_octet_stream
;
151 XdndDirectSave0
= gdk_atom_intern("XdndDirectSave0", FALSE
);
152 _rox_run_action
= gdk_atom_intern("_ROX_RUN_ACTION", FALSE
);
153 xa_text_plain
= gdk_atom_intern("text/plain", FALSE
);
154 text_uri_list
= gdk_atom_intern("text/uri-list", FALSE
);
155 application_octet_stream
= gdk_atom_intern("application/octet-stream",
158 options_sections
= g_slist_prepend(options_sections
, &options
);
159 option_register("dnd_no_hostnames", load_no_hostnames
);
160 option_register("dnd_drag_to_icons", drag_to_icons
);
161 option_register("dnd_spring_open", spring_open
);
166 gboolean o_no_hostnames
= FALSE
;
167 static gboolean o_drag_to_icons
= TRUE
;
168 static gboolean o_spring_open
= FALSE
;
170 static GtkWidget
*toggle_no_hostnames
;
171 static GtkWidget
*toggle_drag_to_icons
;
172 static GtkWidget
*toggle_spring_open
;
174 /* Build up some option widgets to go in the options dialog, but don't
177 static GtkWidget
*create_options()
181 vbox
= gtk_vbox_new(FALSE
, 0);
182 gtk_container_set_border_width(GTK_CONTAINER(vbox
), 4);
184 toggle_no_hostnames
=
185 gtk_check_button_new_with_label(_("Don't use hostnames"));
186 OPTION_TIP(toggle_no_hostnames
,
187 "Some older applications don't support XDND "
188 "fully and may need to have this option turned on. "
189 "Use this if dragging files to an application shows "
190 "a + sign on the pointer but the drop doesn't work.");
191 gtk_box_pack_start(GTK_BOX(vbox
), toggle_no_hostnames
, FALSE
, TRUE
, 0);
193 toggle_drag_to_icons
=
194 gtk_check_button_new_with_label(_("Allow dragging to icons in "
196 OPTION_TIP(toggle_drag_to_icons
,
197 "When this is on you can drag a file over a sub-directory "
198 "or program in a filer window. The item will highlight when "
199 "you do this and dropping the file will put it into that "
200 "directory, or load it into the program.");
201 gtk_box_pack_start(GTK_BOX(vbox
), toggle_drag_to_icons
, FALSE
, TRUE
, 0);
204 gtk_check_button_new_with_label(_("Directories spring open"));
205 OPTION_TIP(toggle_spring_open
,
206 "This option, which requires the above option to be turned "
207 "on too, causes the highlighted directory to 'spring open' "
208 "after the file is held over it for a short while.");
209 gtk_box_pack_start(GTK_BOX(vbox
), toggle_spring_open
, FALSE
, TRUE
, 0);
214 static void update_options()
216 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(toggle_no_hostnames
),
218 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(toggle_drag_to_icons
),
220 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(toggle_spring_open
),
224 static void set_options()
226 o_no_hostnames
= gtk_toggle_button_get_active(
227 GTK_TOGGLE_BUTTON(toggle_no_hostnames
));
228 o_drag_to_icons
= gtk_toggle_button_get_active(
229 GTK_TOGGLE_BUTTON(toggle_drag_to_icons
));
230 o_spring_open
= gtk_toggle_button_get_active(
231 GTK_TOGGLE_BUTTON(toggle_spring_open
));
234 static void save_options()
236 option_write("dnd_no_hostnames", o_no_hostnames
? "1" : "0");
237 option_write("dnd_drag_to_icons", o_drag_to_icons
? "1" : "0");
238 option_write("dnd_spring_open", o_spring_open
? "1" : "0");
241 static char *load_no_hostnames(char *data
)
243 o_no_hostnames
= atoi(data
) != 0;
247 static char *drag_to_icons(char *data
)
249 o_drag_to_icons
= atoi(data
) != 0;
253 static char *spring_open(char *data
)
255 o_spring_open
= atoi(data
) != 0;
259 /* SUPPORT FUNCTIONS */
261 /* Set the XdndDirectSave0 property on the source window for this context */
262 static void set_xds_prop(GdkDragContext
*context
, char *text
)
264 gdk_property_change(context
->source_window
,
267 GDK_PROP_MODE_REPLACE
,
272 static char *get_xds_prop(GdkDragContext
*context
)
277 if (gdk_property_get(context
->source_window
,
283 &length
, &prop_text
) && prop_text
)
285 /* Terminate the string */
286 prop_text
= g_realloc(prop_text
, length
+ 1);
287 prop_text
[length
] = '\0';
294 /* Is the sender willing to supply this target type? */
295 gboolean
provides(GdkDragContext
*context
, GdkAtom target
)
297 GList
*targets
= context
->targets
;
299 while (targets
&& ((GdkAtom
) targets
->data
!= target
))
300 targets
= targets
->next
;
302 return targets
!= NULL
;
305 /* Convert a list of URIs into a list of strings.
306 * Lines beginning with # are skipped.
307 * The text block passed in is zero terminated (after the final CRLF)
309 static GSList
*uri_list_to_gslist(char *uri_list
)
319 linebreak
= strchr(uri_list
, 13);
321 if (!linebreak
|| linebreak
[1] != 10)
323 delayed_error("uri_list_to_gslist",
324 _("Incorrect or missing line "
325 "break in text/uri-list data"));
329 length
= linebreak
- uri_list
;
331 if (length
&& uri_list
[0] != '#')
333 uri
= g_malloc(sizeof(char) * (length
+ 1));
334 strncpy(uri
, uri_list
, length
);
336 list
= g_slist_append(list
, uri
);
339 uri_list
= linebreak
+ 2;
345 /* DRAGGING FROM US */
347 /* The user has held the mouse button down over a group of item and moved -
348 * start a drag. 'uri_list' is copied, so you can delete it straight away.
350 void drag_selection(GtkWidget
*widget
, GdkEventMotion
*event
, guchar
*uri_list
)
352 GdkDragContext
*context
;
353 GdkDragAction actions
;
354 GtkTargetList
*target_list
;
355 GtkTargetEntry target_table
[] = {
356 {"text/uri-list", 0, TARGET_URI_LIST
},
359 if (event
->state
& GDK_BUTTON1_MASK
)
360 actions
= GDK_ACTION_COPY
| GDK_ACTION_MOVE
| GDK_ACTION_LINK
;
362 actions
= GDK_ACTION_MOVE
;
364 target_list
= gtk_target_list_new(target_table
, 1);
366 context
= gtk_drag_begin(widget
,
369 (event
->state
& GDK_BUTTON1_MASK
) ? 1 :
370 (event
->state
& GDK_BUTTON2_MASK
) ? 2 : 3,
373 g_dataset_set_data_full(context
, "uri_list",
374 g_strdup(uri_list
), g_free
);
376 gtk_drag_set_icon_pixmap(context
,
377 gtk_widget_get_colormap(widget
),
383 /* Copy/Load this item into another directory/application.
385 * If set_run_action is TRUE then the user may drag this item to an exectuable
386 * and set the default run action for its type.
388 void drag_one_item(GtkWidget
*widget
,
389 GdkEventMotion
*event
,
392 gboolean set_run_action
)
395 GdkDragContext
*context
;
396 GdkDragAction actions
;
397 GtkTargetList
*target_list
;
398 GtkTargetEntry target_table
[] = {
399 {"text/uri-list", 0, TARGET_URI_LIST
},
400 {"application/octet-stream", 0, TARGET_RAW
},
404 g_return_if_fail(full_path
!= NULL
);
405 g_return_if_fail(item
!= NULL
);
409 GtkTargetEntry target_table
[] = {
410 {"_ROX_RUN_ACTION", 0, TARGET_RUN_ACTION
},
413 target_list
= gtk_target_list_new(target_table
, 1);
415 else if (item
->base_type
== TYPE_FILE
)
417 MIME_type
*t
= item
->mime_type
;
419 target_table
[2].target
= g_strconcat(t
->media_type
, "/",
421 target_list
= gtk_target_list_new(target_table
, 3);
422 g_free(target_table
[2].target
);
425 target_list
= gtk_target_list_new(target_table
, 1);
427 if (event
->state
& GDK_BUTTON1_MASK
)
428 actions
= GDK_ACTION_COPY
| GDK_ACTION_MOVE
| GDK_ACTION_LINK
;
430 actions
= GDK_ACTION_MOVE
;
432 context
= gtk_drag_begin(widget
,
435 (event
->state
& GDK_BUTTON1_MASK
) ? 1 :
436 (event
->state
& GDK_BUTTON2_MASK
) ? 2 : 3,
441 MIME_type
*type
= item
->mime_type
;
443 g_dataset_set_data_full(context
, "mime_type",
444 g_strconcat(type
->media_type
, "/",
445 type
->subtype
, NULL
),
450 g_dataset_set_data_full(context
, "full_path",
451 g_strdup(full_path
), g_free
);
452 uri
= g_strconcat("file://",
453 o_no_hostnames
? "" : our_host_name(),
454 full_path
, "\r\n", NULL
);
455 g_dataset_set_data_full(context
, "uri_list",
459 gtk_drag_set_icon_pixmap(context
,
460 gtk_widget_get_colormap(widget
),
466 static void drag_end(GtkWidget
*widget
,
467 GdkDragContext
*context
,
468 FilerWindow
*filer_window
)
470 collection_clear_selection(filer_window
->collection
);
471 if (filer_window
->mini_type
== MINI_RUN_ACTION
)
472 minibuffer_hide(filer_window
);
475 /* Called when a remote app wants us to send it some data.
476 * TODO: Maybe we should handle errors better (ie, let the remote app know
477 * the drag has failed)?
479 void drag_data_get(GtkWidget
*widget
,
480 GdkDragContext
*context
,
481 GtkSelectionData
*selection_data
,
486 char *to_send
= "E"; /* Default to sending an error */
487 long to_send_length
= 1;
488 gboolean delete_once_sent
= FALSE
;
489 GdkAtom type
= XA_STRING
;
495 path
= g_dataset_get_data(context
, "full_path");
496 if (path
&& load_file(path
, &to_send
, &to_send_length
))
498 delete_once_sent
= TRUE
;
499 type
= selection_data
->target
;
502 g_warning("drag_data_get: Can't find path!\n");
504 case TARGET_URI_LIST
:
505 to_send
= g_dataset_get_data(context
, "uri_list");
506 to_send_length
= strlen(to_send
);
507 delete_once_sent
= FALSE
;
509 case TARGET_RUN_ACTION
:
510 to_send
= g_dataset_get_data(context
, "mime_type");
512 to_send_length
= strlen(to_send
);
515 delete_once_sent
= FALSE
;
518 delayed_error("drag_data_get",
519 _("Internal error - bad info type"));
523 gtk_selection_data_set(selection_data
,
529 if (delete_once_sent
)
535 /* Set up this widget as a drop-target.
536 * Does not attach any motion handlers.
538 void make_drop_target(GtkWidget
*widget
)
540 GtkTargetEntry target_table
[] =
542 {"text/uri-list", 0, TARGET_URI_LIST
},
543 {"XdndDirectSave0", 0, TARGET_XDS
},
544 {"application/octet-stream", 0, TARGET_RAW
},
545 {"_ROX_RUN_ACTION", 0, TARGET_RUN_ACTION
},
548 gtk_drag_dest_set(widget
,
551 sizeof(target_table
) / sizeof(*target_table
),
552 GDK_ACTION_COPY
| GDK_ACTION_MOVE
553 | GDK_ACTION_LINK
| GDK_ACTION_PRIVATE
);
555 gtk_signal_connect(GTK_OBJECT(widget
), "drag_drop",
556 GTK_SIGNAL_FUNC(drag_drop
), NULL
);
557 gtk_signal_connect(GTK_OBJECT(widget
), "drag_data_received",
558 GTK_SIGNAL_FUNC(drag_data_received
), NULL
);
561 /* Set up this filer window as a drop target. Called once, when the
562 * filer window is first created.
564 void drag_set_dest(FilerWindow
*filer_window
)
566 GtkWidget
*widget
= GTK_WIDGET(filer_window
->collection
);
568 make_drop_target(widget
);
570 gtk_signal_connect(GTK_OBJECT(widget
), "drag_motion",
571 GTK_SIGNAL_FUNC(drag_motion
), filer_window
);
572 gtk_signal_connect(GTK_OBJECT(widget
), "drag_leave",
573 GTK_SIGNAL_FUNC(drag_leave
), filer_window
);
574 gtk_signal_connect(GTK_OBJECT(widget
), "drag_end",
575 GTK_SIGNAL_FUNC(drag_end
), filer_window
);
578 /* Like drag_set_dest, but for a pinboard-type widget.
579 * You must ensure that dnd events reach this widget (eg with
580 * setup_xdnd_proxy() for the root window).
582 void drag_set_pinboard_dest(GtkWidget
*widget
)
584 GtkTargetEntry target_table
[] = {
585 {"text/uri-list", 0, TARGET_URI_LIST
},
588 gtk_drag_dest_set(widget
,
589 GTK_DEST_DEFAULT_DROP
,
591 sizeof(target_table
) / sizeof(*target_table
),
593 gtk_signal_connect(GTK_OBJECT(widget
), "drag_data_received",
594 (GtkSignalFunc
) desktop_drag_data_received
,
598 static void scrolled(GtkAdjustment
*adj
, Collection
*collection
)
600 collection_set_cursor_item(collection
, -1);
604 /* Called during the drag when the mouse is in a widget registered
605 * as a drop target. Returns TRUE if we can accept the drop.
607 static gboolean
drag_motion(GtkWidget
*widget
,
608 GdkDragContext
*context
,
612 FilerWindow
*filer_window
)
616 GdkDragAction action
= context
->suggested_action
;
617 char *new_path
= NULL
;
619 gboolean retval
= FALSE
;
621 if (filer_window
->collection
->auto_scroll
== -1)
622 collection_set_autoscroll(filer_window
->collection
, TRUE
);
624 if (o_drag_to_icons
|| filer_window
->panel_type
!= PANEL_NO
)
625 item_number
= collection_get_item(filer_window
->collection
,
630 item
= item_number
>= 0
631 ? (DirItem
*) filer_window
->collection
->items
[item_number
].data
634 if (provides(context
, _rox_run_action
))
636 /* This is a special internal type. The user is dragging
637 * to an executable item to set the run action.
642 if (item
->flags
& (ITEM_FLAG_APPDIR
| ITEM_FLAG_EXEC_FILE
))
644 type
= drop_dest_prog
;
645 new_path
= make_path(filer_window
->path
,
646 item
->leafname
)->str
;
654 /* If we didn't drop onto a directory, application or
655 * executable file then act as though the drop is to the
658 if (item
->base_type
!= TYPE_DIRECTORY
659 && !(item
->flags
& ITEM_FLAG_EXEC_FILE
))
665 /* Drop onto the window background */
666 collection_set_cursor_item(filer_window
->collection
,
669 if (gtk_drag_get_source_widget(context
) == widget
)
672 if (filer_window
->panel_type
!= PANEL_NO
)
674 if (context
->actions
& GDK_ACTION_LINK
)
676 action
= GDK_ACTION_LINK
;
677 type
= drop_dest_dir
;
681 type
= drop_dest_dir
;
684 new_path
= filer_window
->path
;
688 /* Drop onto a program/directory of some sort */
690 if (gtk_drag_get_source_widget(context
) == widget
)
692 Collection
*collection
= filer_window
->collection
;
694 if (collection
->items
[item_number
].selected
)
698 if (item
->base_type
== TYPE_DIRECTORY
&&
699 !(item
->flags
& ITEM_FLAG_APPDIR
))
701 if (provides(context
, text_uri_list
) ||
702 provides(context
, XdndDirectSave0
))
703 type
= drop_dest_dir
;
707 if (provides(context
, text_uri_list
) ||
708 provides(context
, application_octet_stream
))
709 type
= drop_dest_prog
;
713 new_path
= make_path(filer_window
->path
,
714 item
->leafname
)->str
;
718 /* Don't allow drops to non-writeable directories. BUT, still
719 * allow drops on non-writeable SUBdirectories so that we can
720 * do the spring-open thing.
722 if (type
== drop_dest_dir
&& item
)
724 GtkObject
*vadj
= GTK_OBJECT(filer_window
->collection
->vadj
);
726 /* Subdir: prepare for spring-open */
727 if (scrolled_adj
!= vadj
)
730 gtk_signal_disconnect(scrolled_adj
,
733 scrolled_signal
= gtk_signal_connect(
736 GTK_SIGNAL_FUNC(scrolled
),
737 filer_window
->collection
);
739 dnd_spring_load(context
);
745 collection_set_cursor_item(filer_window
->collection
,
748 g_dataset_set_data(context
, "drop_dest_type", type
);
751 gdk_drag_status(context
, action
, time
);
752 g_dataset_set_data_full(context
, "drop_dest_path",
753 g_strdup(new_path
), g_free
);
760 /* Remove highlights */
761 static void drag_leave(GtkWidget
*widget
,
762 GdkDragContext
*context
,
764 FilerWindow
*filer_window
)
766 collection_set_autoscroll(filer_window
->collection
, FALSE
);
767 collection_set_cursor_item(filer_window
->collection
, -1);
771 gtk_signal_disconnect(scrolled_adj
,
777 /* User has tried to drop some data on us. Decide what format we would
780 static gboolean
drag_drop(GtkWidget
*widget
,
781 GdkDragContext
*context
,
788 char *leafname
= NULL
;
789 GdkAtom target
= GDK_NONE
;
791 char *dest_type
= NULL
;
793 dest_path
= g_dataset_get_data(context
, "drop_dest_path");
794 dest_type
= g_dataset_get_data(context
, "drop_dest_type");
796 g_return_val_if_fail(dest_path
!= NULL
, TRUE
);
798 if (dest_type
== drop_dest_dir
&& provides(context
, XdndDirectSave0
))
800 leafname
= get_xds_prop(context
);
803 if (strchr(leafname
, '/'))
805 error
= _("XDS protocol error: "
806 "leafname may not contain '/'\n");
815 uri
= g_string_new(NULL
);
816 g_string_sprintf(uri
, "file://%s%s",
820 set_xds_prop(context
, uri
->str
);
821 g_string_free(uri
, TRUE
);
823 target
= XdndDirectSave0
;
824 g_dataset_set_data_full(context
, "leafname",
830 "XdndDirectSave0 target provided, but the atom "
831 "XdndDirectSave0 (type text/plain) did not "
832 "contain a leafname\n");
834 else if (provides(context
, text_uri_list
))
835 target
= text_uri_list
;
836 else if (provides(context
, application_octet_stream
))
837 target
= application_octet_stream
;
838 else if (dest_type
== drop_dest_prog
&&
839 provides(context
, _rox_run_action
))
840 target
= _rox_run_action
;
843 if (dest_type
== drop_dest_dir
)
844 error
= _("Sorry - I require a target type of "
845 "text/uri-list or XdndDirectSave0.");
847 error
= _("Sorry - I require a target type of "
848 "text/uri-list or application/octet-stream.");
853 gtk_drag_finish(context
, FALSE
, FALSE
, time
); /* Failure */
855 delayed_error(PROJECT
, error
);
858 gtk_drag_get_data(widget
, context
, target
, time
);
863 static void got_run_action(GtkWidget
*widget
,
864 GdkDragContext
*context
,
865 GtkSelectionData
*selection_data
,
868 char *type
= selection_data
->data
;
870 char *dest_path
, *link
;
871 char buffer
[MAXPATHLEN
+ 1];
874 g_return_if_fail(type
!= NULL
);
876 dest_path
= g_dataset_get_data(context
, "drop_dest_path");
878 /* dest_path might be a symlink - dereference it if so */
879 len
= readlink(dest_path
, buffer
, sizeof(buffer
) - 1);
886 gtk_drag_finish(context
, TRUE
, FALSE
, time
); /* Success! */
888 sub
= strchr(type
, '/');
889 g_return_if_fail(sub
!= NULL
);
891 media
= g_strndup(type
, sub
- type
);
894 link
= type_ask_which_action(media
, sub
);
899 if (symlink(dest_path
, link
))
900 delayed_error(PROJECT
, g_strerror(errno
));
903 /* Called when a text/uri-list arrives */
904 static void desktop_drag_data_received(GtkWidget
*widget
,
905 GdkDragContext
*context
,
908 GtkSelectionData
*selection_data
,
911 FilerWindow
*filer_window
)
916 if (!selection_data
->data
)
922 gdk_window_get_position(widget
->window
, &dx
, &dy
);
926 uris
= uri_list_to_gslist(selection_data
->data
);
928 for (next
= uris
; next
; next
= next
->next
)
932 path
= get_local_path((gchar
*) next
->data
);
935 pinboard_pin(path
, NULL
, x
, y
);
946 /* Called when some data arrives from the remote app (which we asked for
949 static void drag_data_received(GtkWidget
*widget
,
950 GdkDragContext
*context
,
953 GtkSelectionData
*selection_data
,
958 if (!selection_data
->data
)
961 gtk_drag_finish(context
, FALSE
, FALSE
, time
); /* Failure */
968 got_data_xds_reply(widget
, context
,
969 selection_data
, time
);
972 got_data_raw(widget
, context
, selection_data
, time
);
974 case TARGET_URI_LIST
:
975 got_uri_list(widget
, context
, selection_data
, time
);
977 case TARGET_RUN_ACTION
:
978 got_run_action(widget
, context
, selection_data
, time
);
981 gtk_drag_finish(context
, FALSE
, FALSE
, time
);
982 delayed_error("drag_data_received",
983 _("Unknown target"));
988 static void got_data_xds_reply(GtkWidget
*widget
,
989 GdkDragContext
*context
,
990 GtkSelectionData
*selection_data
,
993 gboolean mark_unsafe
= TRUE
;
994 char response
= *selection_data
->data
;
998 dest_path
= g_dataset_get_data(context
, "drop_dest_path");
1000 if (selection_data
->length
!= 1)
1003 if (response
== 'F')
1005 /* Sender couldn't save there - ask for another
1008 if (provides(context
, application_octet_stream
))
1010 mark_unsafe
= FALSE
; /* Wait and see */
1012 gtk_drag_get_data(widget
, context
,
1013 application_octet_stream
, time
);
1016 error
= _("Remote app can't or won't send me "
1017 "the data - sorry");
1019 else if (response
== 'S')
1021 /* Success - data is saved */
1022 mark_unsafe
= FALSE
; /* It really is safe */
1023 gtk_drag_finish(context
, TRUE
, FALSE
, time
);
1025 refresh_dirs(dest_path
);
1027 else if (response
!= 'E')
1029 error
= _("XDS protocol error: "
1030 "return code should be 'S', 'F' or 'E'\n");
1032 /* else: error has been reported by the sender */
1036 set_xds_prop(context
, "");
1037 /* Unsave also implies that the drag failed */
1038 gtk_drag_finish(context
, FALSE
, FALSE
, time
);
1042 delayed_error(PROJECT
, error
);
1045 static void got_data_raw(GtkWidget
*widget
,
1046 GdkDragContext
*context
,
1047 GtkSelectionData
*selection_data
,
1055 g_return_if_fail(selection_data
->data
!= NULL
);
1057 dest_path
= g_dataset_get_data(context
, "drop_dest_path");
1059 if (g_dataset_get_data(context
, "drop_dest_type") == drop_dest_prog
)
1061 /* The data needs to be sent to an application */
1062 run_with_data(dest_path
,
1063 selection_data
->data
, selection_data
->length
);
1064 gtk_drag_finish(context
, TRUE
, FALSE
, time
); /* Success! */
1068 leafname
= g_dataset_get_data(context
, "leafname");
1070 leafname
= _("UntitledData");
1072 fd
= open(make_path(dest_path
, leafname
)->str
,
1073 O_WRONLY
| O_CREAT
| O_EXCL
| O_NOCTTY
,
1074 S_IRUSR
| S_IRGRP
| S_IROTH
|
1075 S_IWUSR
| S_IWGRP
| S_IWOTH
);
1078 error
= g_strerror(errno
);
1082 selection_data
->data
,
1083 selection_data
->length
) == -1)
1084 error
= g_strerror(errno
);
1086 if (close(fd
) == -1 && !error
)
1087 error
= g_strerror(errno
);
1089 refresh_dirs(dest_path
);
1094 if (provides(context
, XdndDirectSave0
))
1095 set_xds_prop(context
, "");
1096 gtk_drag_finish(context
, FALSE
, FALSE
, time
); /* Failure */
1097 delayed_error(_("Error saving file"), error
);
1100 gtk_drag_finish(context
, TRUE
, FALSE
, time
); /* Success! */
1103 /* We've got a list of URIs from somewhere (probably another filer window).
1104 * If the files are on the local machine then try to copy them ourselves,
1105 * otherwise, if there was only one file and application/octet-stream was
1106 * provided, get the data via the X server.
1108 static void got_uri_list(GtkWidget
*widget
,
1109 GdkDragContext
*context
,
1110 GtkSelectionData
*selection_data
,
1116 gboolean send_reply
= TRUE
;
1120 dest_path
= g_dataset_get_data(context
, "drop_dest_path");
1121 type
= g_dataset_get_data(context
, "drop_dest_type");
1123 g_return_if_fail(dest_path
!= NULL
);
1125 uri_list
= uri_list_to_gslist(selection_data
->data
);
1128 error
= _("No URIs in the text/uri-list (nothing to do!)");
1129 else if (type
== drop_dest_prog
)
1130 run_with_files(dest_path
, uri_list
);
1131 else if ((!uri_list
->next
) && (!get_local_path(uri_list
->data
)))
1133 /* There is one URI in the list, and it's not on the local
1134 * machine. Get it via the X server if possible.
1137 if (provides(context
, application_octet_stream
))
1140 leaf
= strrchr(uri_list
->data
, '/');
1144 leaf
= uri_list
->data
;
1145 g_dataset_set_data_full(context
, "leafname",
1146 g_strdup(leaf
), g_free
);
1147 gtk_drag_get_data(widget
, context
,
1148 application_octet_stream
, time
);
1152 error
= _("Can't get data from remote machine "
1153 "(application/octet-stream not provided)");
1157 GSList
*local_paths
= NULL
;
1160 /* Either one local URI, or a list. If everything in the list
1161 * isn't local then we are stuck.
1164 for (next_uri
= uri_list
; next_uri
; next_uri
= next_uri
->next
)
1168 path
= get_local_path((char *) next_uri
->data
);
1171 local_paths
= g_slist_append(local_paths
,
1174 error
= _("Some of these files are on a "
1175 "different machine - they will be "
1181 error
= _("None of these files are on the local "
1182 "machine - I can't operate on multiple "
1183 "remote files - sorry.");
1185 else if (context
->action
== GDK_ACTION_MOVE
)
1186 action_move(local_paths
, dest_path
, NULL
);
1187 else if (context
->action
== GDK_ACTION_COPY
)
1188 action_copy(local_paths
, dest_path
, NULL
);
1189 else if (context
->action
== GDK_ACTION_LINK
)
1190 action_link(local_paths
, dest_path
);
1192 error
= _("Unknown action requested");
1194 for (next
= local_paths
; next
; next
= next
->next
)
1196 g_slist_free(local_paths
);
1201 gtk_drag_finish(context
, FALSE
, FALSE
, time
); /* Failure */
1202 delayed_error(_("Error getting file list"), error
);
1204 else if (send_reply
)
1205 gtk_drag_finish(context
, TRUE
, FALSE
, time
); /* Success! */
1207 next_uri
= uri_list
;
1210 g_free(next_uri
->data
);
1211 next_uri
= next_uri
->next
;
1213 g_slist_free(uri_list
);
1217 /* SPRING-LOADING */
1219 /* This is the code that makes directories pop open if you hold a
1222 * First, call dnd_spring_load(context) to arm the system.
1223 * After a timeout (1/2 a second) the dest_path directory will be
1224 * opened in a new window, unless dnd_spring_abort is called first.
1227 static gint spring_timeout
= -1;
1228 static GdkDragContext
*spring_context
= NULL
;
1229 static FilerWindow
*spring_window
= NULL
;
1231 void dnd_spring_load(GdkDragContext
*context
)
1233 g_return_if_fail(context
!= NULL
);
1241 spring_context
= context
;
1242 gdk_drag_context_ref(spring_context
);
1243 spring_timeout
= gtk_timeout_add(500, spring_now
, NULL
);
1246 void dnd_spring_abort(void)
1248 if (!spring_context
)
1251 gdk_drag_context_unref(spring_context
);
1252 spring_context
= NULL
;
1253 gtk_timeout_remove(spring_timeout
);
1256 /* If all mod keys are released, no buttons are pressed, and the
1257 * mouse is outside the spring window, then close it.
1259 static gboolean
spring_check_idle(gpointer data
)
1266 if (!get_pointer_xy(&p_x
, &p_y
))
1269 GdkWindow *win = spring_window->window->window;
1273 gdk_window_get_position(win, &x, &y);
1274 gdk_window_get_size(win, &w, &h);
1276 if (p_x < x || p_x > x + w || p_y < y || p_y > y + h)
1280 gtk_widget_destroy(spring_window
->window
);
1281 return FALSE
; /* Got it! */
1284 return TRUE
; /* Try again later */
1287 static gboolean
spring_now(gpointer data
)
1292 g_return_val_if_fail(spring_context
!= NULL
, FALSE
);
1294 dest_path
= g_dataset_get_data(spring_context
, "drop_dest_path");
1295 g_return_val_if_fail(dest_path
!= NULL
, FALSE
);
1298 * XXX: Due to a bug in gtk, if a window disappears during
1299 * a drag and the pointer moves over where the window was,
1300 * the sender crashes! Therefore, do not close any windows
1301 * while dragging! (fixed in later versions)
1305 gtk_widget_destroy(spring_window->window);
1308 get_pointer_xy(&x
, &y
);
1312 collection_set_cursor_item(spring_window
->collection
, -1);
1313 filer_change_to(spring_window
, dest_path
, NULL
);
1314 /* DON'T move the window. Gtk+ sometimes doesn't
1320 spring_window
= filer_opendir(dest_path
);
1321 gtk_timeout_add(500, spring_check_idle
, NULL
);
1322 gtk_signal_connect(GTK_OBJECT(spring_window
->window
), "destroy",
1323 GTK_SIGNAL_FUNC(spring_win_destroyed
), NULL
);
1325 centre_window(spring_window
->window
->window
, x
, y
);
1333 static void spring_win_destroyed(GtkWidget
*widget
, gpointer data
)
1335 spring_window
= NULL
;