4 * ROX-Filer, filer for the ROX desktop project
5 * Copyright (C) 1999, Thomas Leonard, <tal197@ecs.soton.ac.uk>.
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 */
33 #include <X11/Xatom.h>
35 #include "collection.h"
40 #include "gui_support.h"
45 #define MAXURILEN 4096 /* Longest URI to allow */
47 /* Static prototypes */
48 static char *get_dest_path(FilerWindow
*filer_window
, GdkDragContext
*context
);
49 static void create_uri_list(GString
*string
,
50 Collection
*collection
,
51 FilerWindow
*filer_window
);
52 static gboolean
drag_drop(GtkWidget
*widget
,
53 GdkDragContext
*context
,
57 static gboolean
provides(GdkDragContext
*context
, GdkAtom target
);
58 static void set_xds_prop(GdkDragContext
*context
, char *text
);
59 static gboolean
drag_motion(GtkWidget
*widget
,
60 GdkDragContext
*context
,
64 static void drag_leave(GtkWidget
*widget
,
65 GdkDragContext
*context
);
66 static void drag_data_received(GtkWidget
*widget
,
67 GdkDragContext
*context
,
70 GtkSelectionData
*selection_data
,
73 static void got_data_xds_reply(GtkWidget
*widget
,
74 GdkDragContext
*context
,
75 GtkSelectionData
*selection_data
,
77 static void got_data_raw(GtkWidget
*widget
,
78 GdkDragContext
*context
,
79 GtkSelectionData
*selection_data
,
81 static GSList
*uri_list_to_gslist(char *uri_list
);
82 static void got_uri_list(GtkWidget
*widget
,
83 GdkDragContext
*context
,
84 GtkSelectionData
*selection_data
,
86 static GtkWidget
*create_options();
87 static void update_options();
88 static void set_options();
89 static void save_options();
90 static char *load_no_hostnames(char *data
);
92 /* Possible values for drop_dest_type (can also be NULL).
93 * In either case, drop_dest_path is the app/file/dir to use.
95 static char *drop_dest_prog
= "drop_dest_prog"; /* Run a program */
96 static char *drop_dest_dir
= "drop_dest_dir"; /* Save to path */
98 static OptionsSection options
=
100 "Drag and Drop options",
114 GdkAtom XdndDirectSave0
;
115 GdkAtom xa_text_plain
;
116 GdkAtom text_uri_list
;
117 GdkAtom application_octet_stream
;
121 XdndDirectSave0
= gdk_atom_intern("XdndDirectSave0", FALSE
);
122 xa_text_plain
= gdk_atom_intern("text/plain", FALSE
);
123 text_uri_list
= gdk_atom_intern("text/uri-list", FALSE
);
124 application_octet_stream
= gdk_atom_intern("application/octet-stream",
127 options_sections
= g_slist_prepend(options_sections
, &options
);
128 option_register("dnd_no_hostnames", load_no_hostnames
);
133 static gboolean o_no_hostnames
= FALSE
;
134 static GtkWidget
*toggle_no_hostnames
;
136 /* Build up some option widgets to go in the options dialog, but don't
139 static GtkWidget
*create_options()
141 GtkWidget
*vbox
, *label
;
143 vbox
= gtk_vbox_new(FALSE
, 0);
144 gtk_container_set_border_width(GTK_CONTAINER(vbox
), 4);
146 label
= gtk_label_new("Some older applications don't support XDND "
147 "fully and may need to have this option turned on. "
148 "Use this if dragging files to an application shows "
149 "a + sign on the pointer but the drop doesn't work.");
150 gtk_label_set_line_wrap(GTK_LABEL(label
), TRUE
);
151 gtk_box_pack_start(GTK_BOX(vbox
), label
, FALSE
, TRUE
, 0);
153 toggle_no_hostnames
=
154 gtk_check_button_new_with_label("Don't use hostnames");
155 gtk_box_pack_start(GTK_BOX(vbox
), toggle_no_hostnames
, FALSE
, TRUE
, 0);
160 static void update_options()
162 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(toggle_no_hostnames
),
166 static void set_options()
168 o_no_hostnames
= gtk_toggle_button_get_active(
169 GTK_TOGGLE_BUTTON(toggle_no_hostnames
));
172 static void save_options()
174 option_write("dnd_no_hostnames", o_no_hostnames
? "1" : "0");
177 static char *load_no_hostnames(char *data
)
179 o_no_hostnames
= atoi(data
) != 0;
183 /* SUPPORT FUNCTIONS */
185 static char *get_dest_path(FilerWindow
*filer_window
, GdkDragContext
*context
)
189 path
= g_dataset_get_data(context
, "drop_dest_path");
191 return path
? path
: filer_window
->path
;
194 /* Set the XdndDirectSave0 property on the source window for this context */
195 static void set_xds_prop(GdkDragContext
*context
, char *text
)
197 gdk_property_change(context
->source_window
,
200 GDK_PROP_MODE_REPLACE
,
205 static char *get_xds_prop(GdkDragContext
*context
)
210 if (gdk_property_get(context
->source_window
,
216 &length
, &prop_text
) && prop_text
)
218 /* Terminate the string */
219 prop_text
= g_realloc(prop_text
, length
+ 1);
220 prop_text
[length
] = '\0';
227 /* Is the sender willing to supply this target type? */
228 static gboolean
provides(GdkDragContext
*context
, GdkAtom target
)
230 GList
*targets
= context
->targets
;
232 while (targets
&& ((GdkAtom
) targets
->data
!= target
))
233 targets
= targets
->next
;
235 return targets
!= NULL
;
238 /* Convert a list of URIs into a list of strings.
239 * Lines beginning with # are skipped.
240 * The text block passed in is zero terminated (after the final CRLF)
242 static GSList
*uri_list_to_gslist(char *uri_list
)
252 linebreak
= strchr(uri_list
, 13);
254 if (!linebreak
|| linebreak
[1] != 10)
256 delayed_error("uri_list_to_gslist",
257 "Incorrect or missing line break "
258 "in text/uri-list data");
262 length
= linebreak
- uri_list
;
264 if (length
&& uri_list
[0] != '#')
266 uri
= g_malloc(sizeof(char) * (length
+ 1));
267 strncpy(uri
, uri_list
, length
);
269 list
= g_slist_append(list
, uri
);
272 uri_list
= linebreak
+ 2;
278 /* Append all the URIs in the selection to the string */
279 static void create_uri_list(GString
*string
,
280 Collection
*collection
,
281 FilerWindow
*filer_window
)
286 leader
= g_string_new("file://");
288 g_string_append(leader
, our_host_name());
289 g_string_append(leader
, filer_window
->path
);
290 if (leader
->str
[leader
->len
- 1] != '/')
291 g_string_append_c(leader
, '/');
293 num_selected
= collection
->number_selected
;
295 for (i
= 0; num_selected
> 0; i
++)
297 if (collection
->items
[i
].selected
)
299 DirItem
*item
= (DirItem
*) collection
->items
[i
].data
;
301 g_string_append(string
, leader
->str
);
302 g_string_append(string
, item
->leafname
);
303 g_string_append(string
, "\r\n");
308 g_string_free(leader
, TRUE
);
311 /* DRAGGING FROM US */
313 /* The user has held the mouse button down over an item and moved -
316 * We always provide text/uri-list. If we are dragging a single, regular file
317 * then we also offer application/octet-stream and the type of the file.
319 void drag_selection(Collection
*collection
,
320 GdkEventMotion
*event
,
321 gint number_selected
,
324 FilerWindow
*filer_window
= (FilerWindow
*) user_data
;
327 GdkDragContext
*context
;
328 GtkTargetList
*target_list
;
329 GtkTargetEntry target_table
[] =
331 {"text/uri-list", 0, TARGET_URI_LIST
},
332 {"application/octet-stream", 0, TARGET_RAW
},
337 if (number_selected
== 1)
338 item
= selected_item(collection
);
342 widget
= GTK_WIDGET(collection
);
344 if (item
&& item
->mime_type
)
346 MIME_type
*t
= item
->mime_type
;
348 target_table
[2].target
= g_strconcat(t
->media_type
, "/",
350 target_list
= gtk_target_list_new(target_table
, 3);
351 g_free(target_table
[2].target
);
354 target_list
= gtk_target_list_new(target_table
, 1);
356 context
= gtk_drag_begin(widget
,
358 GDK_ACTION_COPY
| GDK_ACTION_MOVE
| GDK_ACTION_LINK
,
359 (event
->state
& GDK_BUTTON1_MASK
) ? 1 : 2,
361 g_dataset_set_data(context
, "filer_window", filer_window
);
363 image
= item
? item
->image
: &default_pixmap
[TYPE_MULTIPLE
];
365 gtk_drag_set_icon_pixmap(context
,
366 gtk_widget_get_colormap(widget
),
372 /* Called when a remote app wants us to send it some data.
373 * TODO: Maybe we should handle errors better (ie, let the remote app know
374 * the drag has failed)?
376 void drag_data_get(GtkWidget
*widget
,
377 GdkDragContext
*context
,
378 GtkSelectionData
*selection_data
,
382 char *to_send
= "E"; /* Default to sending an error */
383 long to_send_length
= 1;
384 gboolean delete_once_sent
= FALSE
;
385 GdkAtom type
= XA_STRING
;
387 FilerWindow
*filer_window
;
390 filer_window
= g_dataset_get_data(context
, "filer_window");
391 g_return_if_fail(filer_window
!= NULL
);
396 item
= selected_item(filer_window
->collection
);
397 if (item
&& load_file(make_path(filer_window
->path
,
398 item
->leafname
)->str
,
399 &to_send
, &to_send_length
))
401 delete_once_sent
= TRUE
;
402 type
= selection_data
->type
;
405 g_warning("drag_data_get: Can't find selected item\n");
407 case TARGET_URI_LIST
:
408 string
= g_string_new(NULL
);
409 create_uri_list(string
,
412 to_send
= string
->str
;
413 to_send_length
= string
->len
;
414 delete_once_sent
= TRUE
;
415 g_string_free(string
, FALSE
);
418 delayed_error("drag_data_get",
419 "Internal error - bad info type\n");
423 gtk_selection_data_set(selection_data
,
429 if (delete_once_sent
)
432 collection_clear_selection(filer_window
->collection
);
437 /* Set up this filer window as a drop target. Called once, when the
438 * filer window is first created.
440 void drag_set_dest(GtkWidget
*widget
, FilerWindow
*filer_window
)
442 GtkTargetEntry target_table
[] =
444 {"text/uri-list", 0, TARGET_URI_LIST
},
445 {"XdndDirectSave0", 0, TARGET_XDS
},
446 {"application/octet-stream", 0, TARGET_RAW
},
449 gtk_drag_dest_set(widget
,
450 0, /* GTK_DEST_DEFAULT_MOTION, */
452 sizeof(target_table
) / sizeof(*target_table
),
453 GDK_ACTION_COPY
| GDK_ACTION_MOVE
454 | GDK_ACTION_LINK
| GDK_ACTION_PRIVATE
);
456 gtk_signal_connect(GTK_OBJECT(widget
), "drag_motion",
457 GTK_SIGNAL_FUNC(drag_motion
), filer_window
);
458 gtk_signal_connect(GTK_OBJECT(widget
), "drag_leave",
459 GTK_SIGNAL_FUNC(drag_leave
), filer_window
);
460 gtk_signal_connect(GTK_OBJECT(widget
), "drag_drop",
461 GTK_SIGNAL_FUNC(drag_drop
), filer_window
);
462 gtk_signal_connect(GTK_OBJECT(widget
), "drag_data_received",
463 GTK_SIGNAL_FUNC(drag_data_received
), filer_window
);
466 /* Called during the drag when the mouse is in a widget registered
467 * as a drop target. Returns TRUE if we can accept the drop.
469 static gboolean
drag_motion(GtkWidget
*widget
,
470 GdkDragContext
*context
,
475 FilerWindow
*filer_window
;
478 GdkDragAction action
= context
->suggested_action
;
479 char *new_path
= NULL
;
482 filer_window
= gtk_object_get_data(GTK_OBJECT(widget
), "filer_window");
483 g_return_val_if_fail(filer_window
!= NULL
, TRUE
);
485 item_number
= collection_get_item(filer_window
->collection
, x
, y
);
487 item
= item_number
>= 0
488 ? (DirItem
*) filer_window
->collection
->items
[item_number
].data
491 if (item
&& !(item
->flags
& (ITEM_FLAG_APPDIR
| ITEM_FLAG_EXEC_FILE
)))
492 item
= NULL
; /* Drop onto non-executable == no item */
496 /* Drop onto the window background */
497 collection_set_cursor_item(filer_window
->collection
,
500 if (gtk_drag_get_source_widget(context
) == widget
)
503 if (access(filer_window
->path
, W_OK
) != 0)
504 goto out
; /* No write permission */
506 if (filer_window
->panel_type
!= PANEL_NO
)
508 if (context
->actions
& GDK_ACTION_LINK
)
510 action
= GDK_ACTION_LINK
;
511 type
= drop_dest_dir
;
515 type
= drop_dest_dir
;
518 new_path
= g_strdup(filer_window
->path
);
522 /* Drop onto a program of some sort */
523 if (!(provides(context
, text_uri_list
) ||
524 provides(context
, application_octet_stream
)))
527 if (gtk_drag_get_source_widget(context
) == widget
)
529 Collection
*collection
= filer_window
->collection
;
531 if (collection
->items
[item_number
].selected
)
535 /* Actually, we should probably allow any data type */
536 type
= drop_dest_prog
;
537 new_path
= make_path(filer_window
->path
,
538 item
->leafname
)->str
;
539 collection_set_cursor_item(filer_window
->collection
,
544 g_dataset_set_data(context
, "drop_dest_type", type
);
547 gdk_drag_status(context
, action
, time
);
548 g_dataset_set_data_full(context
, "drop_dest_path",
549 g_strdup(new_path
), g_free
);
557 /* Remove panel highlights */
558 static void drag_leave(GtkWidget
*widget
,
559 GdkDragContext
*context
)
561 FilerWindow
*filer_window
;
563 filer_window
= gtk_object_get_data(GTK_OBJECT(widget
), "filer_window");
564 g_return_if_fail(filer_window
!= NULL
);
566 collection_set_cursor_item(filer_window
->collection
, -1);
569 /* User has tried to drop some data on us. Decide what format we would
572 static gboolean
drag_drop(GtkWidget
*widget
,
573 GdkDragContext
*context
,
579 char *leafname
= NULL
;
580 FilerWindow
*filer_window
;
581 GdkAtom target
= GDK_NONE
;
583 char *dest_type
= NULL
;
585 filer_window
= gtk_object_get_data(GTK_OBJECT(widget
), "filer_window");
586 g_return_val_if_fail(filer_window
!= NULL
, TRUE
);
588 dest_path
= g_dataset_get_data(context
, "drop_dest_path");
589 dest_type
= g_dataset_get_data(context
, "drop_dest_type");
591 g_return_val_if_fail(dest_path
!= NULL
, TRUE
);
593 if (dest_type
== drop_dest_dir
&& provides(context
, XdndDirectSave0
))
595 leafname
= get_xds_prop(context
);
598 if (strchr(leafname
, '/'))
600 error
= "XDS protocol error: "
601 "leafname may not contain '/'\n";
610 uri
= g_string_new(NULL
);
611 g_string_sprintf(uri
, "file://%s%s",
615 set_xds_prop(context
, uri
->str
);
616 g_string_free(uri
, TRUE
);
618 target
= XdndDirectSave0
;
619 g_dataset_set_data_full(context
, "leafname",
624 error
= "XdndDirectSave0 target provided, but the atom "
625 "XdndDirectSave0 (type text/plain) did not "
626 "contain a leafname\n";
628 else if (provides(context
, text_uri_list
))
629 target
= text_uri_list
;
630 else if (provides(context
, application_octet_stream
))
631 target
= application_octet_stream
;
634 if (dest_type
== drop_dest_dir
)
635 error
= "Sorry - I require a target type of "
636 "text/uri-list or XdndDirectSave0.";
638 error
= "Sorry - I require a target type of "
639 "text/uri-list or application/octet-stream.";
644 gtk_drag_finish(context
, FALSE
, FALSE
, time
); /* Failure */
646 delayed_error("ROX-Filer", error
);
649 gtk_drag_get_data(widget
, context
, target
, time
);
654 /* Called when some data arrives from the remote app (which we asked for
657 static void drag_data_received(GtkWidget
*widget
,
658 GdkDragContext
*context
,
661 GtkSelectionData
*selection_data
,
665 if (!selection_data
->data
)
668 gtk_drag_finish(context
, FALSE
, FALSE
, time
); /* Failure */
675 got_data_xds_reply(widget
, context
,
676 selection_data
, time
);
679 got_data_raw(widget
, context
, selection_data
, time
);
681 case TARGET_URI_LIST
:
682 got_uri_list(widget
, context
, selection_data
, time
);
685 gtk_drag_finish(context
, FALSE
, FALSE
, time
);
686 delayed_error("drag_data_received", "Unknown target");
691 static void got_data_xds_reply(GtkWidget
*widget
,
692 GdkDragContext
*context
,
693 GtkSelectionData
*selection_data
,
696 gboolean mark_unsafe
= TRUE
;
697 char response
= *selection_data
->data
;
700 if (selection_data
->length
!= 1)
705 /* Sender couldn't save there - ask for another
708 if (provides(context
, application_octet_stream
))
710 mark_unsafe
= FALSE
; /* Wait and see */
712 gtk_drag_get_data(widget
, context
,
713 application_octet_stream
, time
);
716 error
= "Remote app can't or won't send me "
719 else if (response
== 'S')
721 FilerWindow
*filer_window
;
723 /* Success - data is saved */
724 mark_unsafe
= FALSE
; /* It really is safe */
725 gtk_drag_finish(context
, TRUE
, FALSE
, time
);
727 filer_window
= gtk_object_get_data(GTK_OBJECT(widget
),
729 g_return_if_fail(filer_window
!= NULL
);
731 update_dir(filer_window
, TRUE
);
733 else if (response
!= 'E')
735 error
= "XDS protocol error: "
736 "return code should be 'S', 'F' or 'E'\n";
738 /* else: error has been reported by the sender */
742 set_xds_prop(context
, "");
743 /* Unsave also implies that the drag failed */
744 gtk_drag_finish(context
, FALSE
, FALSE
, time
);
748 delayed_error("ROX-Filer", error
);
751 static void got_data_raw(GtkWidget
*widget
,
752 GdkDragContext
*context
,
753 GtkSelectionData
*selection_data
,
756 FilerWindow
*filer_window
;
762 g_return_if_fail(selection_data
->data
!= NULL
);
764 filer_window
= gtk_object_get_data(GTK_OBJECT(widget
), "filer_window");
765 g_return_if_fail(filer_window
!= NULL
);
766 dest_path
= get_dest_path(filer_window
, context
);
768 if (g_dataset_get_data(context
, "drop_dest_type") == drop_dest_prog
)
770 /* The data needs to be sent to an application */
771 run_with_data(dest_path
,
772 selection_data
->data
, selection_data
->length
);
773 gtk_drag_finish(context
, TRUE
, FALSE
, time
); /* Success! */
777 leafname
= g_dataset_get_data(context
, "leafname");
779 leafname
= "UntitledData";
781 fd
= open(make_path(dest_path
, leafname
)->str
,
782 O_WRONLY
| O_CREAT
| O_EXCL
| O_NOCTTY
,
783 S_IRUSR
| S_IRGRP
| S_IROTH
|
784 S_IWUSR
| S_IWGRP
| S_IWOTH
);
787 error
= g_strerror(errno
);
791 selection_data
->data
,
792 selection_data
->length
) == -1)
793 error
= g_strerror(errno
);
795 if (close(fd
) == -1 && !error
)
796 error
= g_strerror(errno
);
798 update_dir(filer_window
, TRUE
);
803 if (provides(context
, XdndDirectSave0
))
804 set_xds_prop(context
, "");
805 gtk_drag_finish(context
, FALSE
, FALSE
, time
); /* Failure */
806 delayed_error("Error saving file", error
);
809 gtk_drag_finish(context
, TRUE
, FALSE
, time
); /* Success! */
812 /* We've got a list of URIs from somewhere (probably another filer window).
813 * If the files are on the local machine then try to copy them ourselves,
814 * otherwise, if there was only one file and application/octet-stream was
815 * provided, get the data via the X server.
817 static void got_uri_list(GtkWidget
*widget
,
818 GdkDragContext
*context
,
819 GtkSelectionData
*selection_data
,
822 FilerWindow
*filer_window
;
826 gboolean send_reply
= TRUE
;
830 filer_window
= gtk_object_get_data(GTK_OBJECT(widget
), "filer_window");
831 g_return_if_fail(filer_window
!= NULL
);
833 dest_path
= get_dest_path(filer_window
, context
);
834 type
= g_dataset_get_data(context
, "drop_dest_type");
836 uri_list
= uri_list_to_gslist(selection_data
->data
);
839 error
= "No URIs in the text/uri-list (nothing to do!)";
840 else if (type
== drop_dest_prog
)
841 run_with_files(dest_path
, uri_list
);
842 else if ((!uri_list
->next
) && (!get_local_path(uri_list
->data
)))
844 /* There is one URI in the list, and it's not on the local
845 * machine. Get it via the X server if possible.
848 if (provides(context
, application_octet_stream
))
851 leaf
= strrchr(uri_list
->data
, '/');
855 leaf
= uri_list
->data
;
856 g_dataset_set_data_full(context
, "leafname",
857 g_strdup(leaf
), g_free
);
858 gtk_drag_get_data(widget
, context
,
859 application_octet_stream
, time
);
863 error
= "Can't get data from remote machine "
864 "(application/octet-stream not provided)";
868 GSList
*local_paths
= NULL
;
871 /* Either one local URI, or a list. If everything in the list
872 * isn't local then we are stuck.
875 for (next_uri
= uri_list
; next_uri
; next_uri
= next_uri
->next
)
879 path
= get_local_path((char *) next_uri
->data
);
882 local_paths
= g_slist_append(local_paths
,
885 error
= "Some of these files are on a "
886 "different machine - they will be "
892 error
= "None of these files are on the local machine "
893 "- I can't operate on multiple remote files - "
896 else if (context
->action
== GDK_ACTION_MOVE
)
897 action_move(local_paths
, dest_path
);
898 else if (context
->action
== GDK_ACTION_COPY
)
899 action_copy(local_paths
, dest_path
);
900 else if (context
->action
== GDK_ACTION_LINK
)
901 action_link(local_paths
, dest_path
);
903 error
= "Unknown action requested";
905 for (next
= local_paths
; next
; next
= next
->next
)
907 g_slist_free(local_paths
);
912 gtk_drag_finish(context
, FALSE
, FALSE
, time
); /* Failure */
913 delayed_error("Error getting file list", error
);
916 gtk_drag_finish(context
, TRUE
, FALSE
, time
); /* Success! */
921 g_free(next_uri
->data
);
922 next_uri
= next_uri
->next
;
924 g_slist_free(uri_list
);