r33: Changed to using cursor to highlight panel items instead of selecting them.
[rox-filer.git] / ROX-Filer / src / dnd.c
blob42b6b9768101ad52ddeb37658ea60a934ef64498
1 /* vi: set cindent:
2 * $Id$
4 * ROX-Filer, filer for the ROX desktop project
5 * By Thomas Leonard, <tal197@ecs.soton.ac.uk>.
6 */
8 /* dnd.c - code for handling drag and drop */
10 #include <stdlib.h>
11 #include <stdio.h>
12 #include <unistd.h>
13 #include <string.h>
14 #include <sys/stat.h>
15 #include <fcntl.h>
16 #include <errno.h>
18 #include <X11/Xlib.h>
19 #include <X11/Xatom.h>
20 #include <gtk/gtk.h>
21 #include <collection.h>
23 #include "filer.h"
24 #include "pixmaps.h"
25 #include "gui_support.h"
26 #include "support.h"
28 #define MAXURILEN 4096 /* Longest URI to allow */
30 enum
32 TARGET_RAW,
33 TARGET_URI_LIST,
34 TARGET_XDS,
37 GdkAtom XdndDirectSave0;
38 GdkAtom text_plain;
39 GdkAtom text_uri_list;
40 GdkAtom application_octet_stream;
42 /* Static prototypes */
43 static char *get_dest_path(FilerWindow *filer_window, GdkDragContext *context);
44 static void create_uri_list(GString *string,
45 Collection *collection,
46 FilerWindow *filer_window);
47 static FileItem *selected_item(Collection *collection);
48 static gboolean drag_drop(GtkWidget *widget,
49 GdkDragContext *context,
50 gint x,
51 gint y,
52 guint time);
53 static gboolean provides(GdkDragContext *context, GdkAtom target);
54 static void set_xds_prop(GdkDragContext *context, char *text);
55 static gboolean drag_motion(GtkWidget *widget,
56 GdkDragContext *context,
57 gint x,
58 gint y,
59 guint time);
60 static void drag_leave(GtkWidget *widget,
61 GdkDragContext *context);
62 static void drag_data_received(GtkWidget *widget,
63 GdkDragContext *context,
64 gint x,
65 gint y,
66 GtkSelectionData *selection_data,
67 guint info,
68 guint32 time);
69 static void got_data_xds_reply(GtkWidget *widget,
70 GdkDragContext *context,
71 GtkSelectionData *selection_data,
72 guint32 time);
73 static void got_data_raw(GtkWidget *widget,
74 GdkDragContext *context,
75 GtkSelectionData *selection_data,
76 guint32 time);
77 static gboolean load_file(char *pathname, char **data_out, long *length_out);
78 static GSList *uri_list_to_gslist(char *uri_list);
79 static void got_uri_list(GtkWidget *widget,
80 GdkDragContext *context,
81 GtkSelectionData *selection_data,
82 guint32 time);
83 char *get_local_path(char *uri);
85 void dnd_init()
87 XdndDirectSave0 = gdk_atom_intern("XdndDirectSave0", FALSE);
88 text_plain = gdk_atom_intern("text/plain", FALSE);
89 text_uri_list = gdk_atom_intern("text/uri-list", FALSE);
90 application_octet_stream = gdk_atom_intern("application/octet-stream",
91 FALSE);
94 static char *get_dest_path(FilerWindow *filer_window, GdkDragContext *context)
96 char *dest_path;
98 dest_path = g_dataset_get_data(context, "drop_dest_path");
100 return dest_path ? dest_path : filer_window->path;
103 /* Set the XdndDirectSave0 property on the source window for this context */
104 static void set_xds_prop(GdkDragContext *context, char *text)
106 gdk_property_change(context->source_window,
107 XdndDirectSave0,
108 text_plain, 8,
109 GDK_PROP_MODE_REPLACE,
110 text,
111 strlen(text));
114 static char *get_xds_prop(GdkDragContext *context)
116 guchar *prop_text;
117 gint length;
119 if (gdk_property_get(context->source_window,
120 XdndDirectSave0,
121 text_plain,
122 0, MAXURILEN,
123 FALSE,
124 NULL, NULL,
125 &length, &prop_text) && prop_text)
127 /* Terminate the string */
128 prop_text = g_realloc(prop_text, length + 1);
129 prop_text[length] = '\0';
130 return prop_text;
133 return NULL;
136 /* Is the sender willing to supply this target type? */
137 static gboolean provides(GdkDragContext *context, GdkAtom target)
139 GList *targets = context->targets;
141 while (targets && ((GdkAtom) targets->data != target))
142 targets = targets->next;
144 return targets != NULL;
147 /* Convert a URI to a local pathname (or NULL if it isn't local).
148 * The returned pointer points inside the input string.
149 * Possible formats:
150 * /path
151 * ///path
152 * //host/path
153 * file://host/path
155 char *get_local_path(char *uri)
157 char *host;
159 host = our_host_name();
161 if (*uri == '/')
163 char *path;
165 if (uri[1] != '/')
166 return uri; /* Just a local path - no host part */
168 path = strchr(uri + 2, '/');
169 if (!path)
170 return NULL; /* //something */
172 if (path - uri == 2)
173 return path; /* ///path */
174 if (strlen(host) == path - uri - 2 &&
175 strncmp(uri + 2, host, path - uri - 2) == 0)
176 return path; /* //myhost/path */
178 return NULL; /* From a different host */
180 else
182 if (strncasecmp(uri, "file:", 5))
183 return NULL; /* Don't know this format */
185 uri += 5;
187 if (*uri == '/')
188 return get_local_path(uri);
190 return NULL;
194 /* Convert a list of URIs into a list of strings.
195 * Lines beginning with # are skipped.
196 * The text block passed in is zero terminated (after the final CRLF)
198 static GSList *uri_list_to_gslist(char *uri_list)
200 GSList *list = NULL;
202 while (*uri_list)
204 char *linebreak;
205 char *uri;
206 int length;
208 linebreak = strchr(uri_list, 13);
210 if (!linebreak || linebreak[1] != 10)
212 delayed_error("uri_list_to_gslist",
213 "Incorrect or missing line break "
214 "in text/uri-list data");
215 return list;
218 length = linebreak - uri_list;
220 if (length && uri_list[0] != '#')
222 uri = g_malloc(sizeof(char) * (length + 1));
223 strncpy(uri, uri_list, length);
224 uri[length] = 0;
225 list = g_slist_append(list, uri);
228 uri_list = linebreak + 2;
231 return list;
234 /* Append all the URIs in the selection to the string */
235 static void create_uri_list(GString *string,
236 Collection *collection,
237 FilerWindow *filer_window)
239 GString *leader;
240 int i, num_selected;
242 leader = g_string_new("file://");
243 g_string_append(leader, our_host_name());
244 g_string_append(leader, filer_window->path);
245 if (leader->str[leader->len - 1] != '/')
246 g_string_append_c(leader, '/');
248 num_selected = collection->number_selected;
250 for (i = 0; num_selected > 0; i++)
252 if (collection->items[i].selected)
254 FileItem *item = (FileItem *) collection->items[i].data;
256 g_string_append(string, leader->str);
257 g_string_append(string, item->leafname);
258 g_string_append(string, "\r\n");
259 num_selected--;
263 g_string_free(leader, TRUE);
266 static FileItem *selected_item(Collection *collection)
268 int i;
270 g_return_val_if_fail(collection != NULL, NULL);
271 g_return_val_if_fail(IS_COLLECTION(collection), NULL);
272 g_return_val_if_fail(collection->number_selected == 1, NULL);
274 for (i = 0; i < collection->number_of_items; i++)
275 if (collection->items[i].selected)
276 return (FileItem *) collection->items[i].data;
278 g_warning("selected_item: number_selected is wrong\n");
280 return NULL;
283 /* DRAGGING FROM US */
285 /* The user has held the mouse button down over an item and moved -
286 * start a drag.
288 * We always provide text/uri-list. If we are dragging a single, regular file
289 * then we also offer application/octet-stream.
291 void drag_selection(Collection *collection,
292 GdkEventMotion *event,
293 gint number_selected,
294 gpointer user_data)
296 FilerWindow *filer_window = (FilerWindow *) user_data;
297 GtkWidget *widget;
298 MaskedPixmap *image;
299 GdkDragContext *context;
300 GtkTargetList *target_list;
301 GtkTargetEntry target_table[] =
303 {"text/uri-list", 0, TARGET_URI_LIST},
304 {"application/octet-stream", 0, TARGET_RAW},
306 FileItem *item;
308 if (number_selected == 1)
309 item = selected_item(collection);
310 else
311 item = NULL;
313 widget = GTK_WIDGET(collection);
315 target_list = gtk_target_list_new(target_table,
316 item && item->base_type == TYPE_FILE ? 2 : 1);
318 context = gtk_drag_begin(widget,
319 target_list,
320 GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK,
321 (event->state & GDK_BUTTON1_MASK) ? 1 : 2,
322 (GdkEvent *) event);
323 g_dataset_set_data(context, "filer_window", filer_window);
325 image = item ? item->image : &default_pixmap[TYPE_MULTIPLE];
327 gtk_drag_set_icon_pixmap(context,
328 gtk_widget_get_colormap(widget),
329 image->pixmap,
330 image->mask,
331 0, 0);
334 /* Called when a remote app wants us to send it some data.
335 * TODO: Maybe we should handle errors better (ie, let the remote app know
336 * the drag has failed)?
338 void drag_data_get(GtkWidget *widget,
339 GdkDragContext *context,
340 GtkSelectionData *selection_data,
341 guint info,
342 guint32 time)
344 char *to_send = "E"; /* Default to sending an error */
345 long to_send_length = 1;
346 gboolean delete_once_sent = FALSE;
347 GdkAtom type = XA_STRING;
348 GString *string;
349 FilerWindow *filer_window;
350 FileItem *item;
352 filer_window = g_dataset_get_data(context, "filer_window");
353 g_return_if_fail(filer_window != NULL);
355 switch (info)
357 case TARGET_RAW:
358 item = selected_item(filer_window->collection);
359 if (item && load_file(make_path(filer_window->path,
360 item->leafname)->str,
361 &to_send, &to_send_length))
363 delete_once_sent = TRUE;
364 type = application_octet_stream; /* XXX */
365 break;
367 return;
368 case TARGET_URI_LIST:
369 string = g_string_new(NULL);
370 create_uri_list(string,
371 COLLECTION(widget),
372 filer_window);
373 to_send = string->str;
374 to_send_length = string->len;
375 delete_once_sent = TRUE;
376 g_string_free(string, FALSE);
377 break;
378 default:
379 delayed_error("drag_data_get",
380 "Internal error - bad info type\n");
381 break;
384 gtk_selection_data_set(selection_data,
385 type,
387 to_send,
388 to_send_length);
390 if (delete_once_sent)
391 g_free(to_send);
394 /* Load the file into memory. Return TRUE on success. */
395 static gboolean load_file(char *pathname, char **data_out, long *length_out)
397 FILE *file;
398 long length;
399 char *buffer;
400 gboolean retval = FALSE;
402 file = fopen(pathname, "r");
404 if (!file)
406 delayed_error("Opening file for DND", g_strerror(errno));
407 return FALSE;
410 fseek(file, 0, SEEK_END);
411 length = ftell(file);
413 buffer = malloc(length);
414 if (buffer)
416 fseek(file, 0, SEEK_SET);
417 fread(buffer, 1, length, file);
419 if (ferror(file))
421 delayed_error("Loading file for DND",
422 g_strerror(errno));
423 g_free(buffer);
425 else
427 *data_out = buffer;
428 *length_out = length;
429 retval = TRUE;
432 else
433 delayed_error("Loading file for DND",
434 "Can't allocate memory for buffer to "
435 "transfer this file");
437 fclose(file);
439 return retval;
442 /* DRAGGING TO US */
444 /* Set up this filer window as a drop target. Called once, when the
445 * filer window is first created.
447 void drag_set_dest(GtkWidget *widget, FilerWindow *filer_window)
449 GtkTargetEntry target_table[] =
451 {"text/uri-list", 0, TARGET_URI_LIST},
452 {"XdndDirectSave0", 0, TARGET_XDS},
453 {"application/octet-stream", 0, TARGET_RAW},
456 gtk_drag_dest_set(widget,
457 0, /* GTK_DEST_DEFAULT_MOTION, */
458 target_table,
459 sizeof(target_table) / sizeof(*target_table),
460 GDK_ACTION_COPY | GDK_ACTION_MOVE
461 | GDK_ACTION_LINK | GDK_ACTION_PRIVATE);
463 gtk_signal_connect(GTK_OBJECT(widget), "drag_motion",
464 GTK_SIGNAL_FUNC(drag_motion), filer_window);
465 gtk_signal_connect(GTK_OBJECT(widget), "drag_leave",
466 GTK_SIGNAL_FUNC(drag_leave), filer_window);
467 gtk_signal_connect(GTK_OBJECT(widget), "drag_drop",
468 GTK_SIGNAL_FUNC(drag_drop), filer_window);
469 gtk_signal_connect(GTK_OBJECT(widget), "drag_data_received",
470 GTK_SIGNAL_FUNC(drag_data_received), filer_window);
473 /* Called during the drag when the mouse is in a widget registered
474 * as a drop target. Returns TRUE if we can accept the drop.
476 static gboolean drag_motion(GtkWidget *widget,
477 GdkDragContext *context,
478 gint x,
479 gint y,
480 guint time)
482 FilerWindow *filer_window;
483 int item;
485 filer_window = gtk_object_get_data(GTK_OBJECT(widget), "filer_window");
486 g_return_val_if_fail(filer_window != NULL, TRUE);
488 if (gtk_drag_get_source_widget(context) == widget)
490 /* Ignore drags within a single window */
491 return FALSE;
494 if (filer_window->panel == FALSE)
496 gdk_drag_status(context, context->suggested_action, time);
497 return TRUE;
500 item = collection_get_item(filer_window->collection, x, y);
502 if (item != filer_window->collection->cursor_item && item != -1)
504 FileItem *fileitem = (FileItem *)
505 filer_window->collection->items[item].data;
507 panel_set_timeout(NULL, 0);
509 g_dataset_set_data_full(context, "drop_dest_path",
510 g_strdup(make_path(filer_window->path,
511 fileitem->leafname)->str),
512 g_free);
514 collection_set_cursor_item(filer_window->collection, item);
516 gdk_drag_status(context, context->suggested_action, time);
517 return TRUE;
520 /* Remove panel highlights */
521 static void drag_leave(GtkWidget *widget,
522 GdkDragContext *context)
524 FilerWindow *filer_window;
526 filer_window = gtk_object_get_data(GTK_OBJECT(widget), "filer_window");
527 g_return_if_fail(filer_window != NULL);
529 panel_set_timeout(NULL, 0);
530 collection_set_cursor_item(filer_window->collection, -1);
533 /* User has tried to drop some data on us. Decide what format we would
534 * like the data in.
536 static gboolean drag_drop(GtkWidget *widget,
537 GdkDragContext *context,
538 gint x,
539 gint y,
540 guint time)
542 char *error = NULL;
543 char *leafname = NULL;
544 FilerWindow *filer_window;
545 GdkAtom target;
546 char *dest_path;
548 filer_window = gtk_object_get_data(GTK_OBJECT(widget), "filer_window");
549 g_return_val_if_fail(filer_window != NULL, TRUE);
551 if (gtk_drag_get_source_widget(context) == widget)
553 /* Ignore drags within a single window */
554 gtk_drag_finish(context, FALSE, FALSE, time); /* Failure */
555 return TRUE;
558 dest_path = g_dataset_get_data(context, "drop_dest_path");
560 if (((!dest_path) && filer_window->panel)
561 || !((dest_path = filer_window->path)))
563 error = "Bad drop on panel";
565 else if (provides(context, XdndDirectSave0))
567 leafname = get_xds_prop(context);
568 if (leafname)
570 if (strchr(leafname, '/'))
572 error = "XDS protocol error: "
573 "leafname may not contain '/'\n";
574 g_free(leafname);
576 leafname = NULL;
578 else
580 GString *uri;
582 uri = g_string_new(NULL);
583 g_string_sprintf(uri, "file://%s%s",
584 our_host_name(),
585 make_path(dest_path,
586 leafname)->str);
587 set_xds_prop(context, uri->str);
588 g_string_free(uri, TRUE);
590 target = XdndDirectSave0;
591 g_dataset_set_data_full(context, "leafname",
592 leafname, g_free);
595 else
596 error = "XdndDirectSave0 target provided, but the atom "
597 "XdndDirectSave0 (type text/plain) did not "
598 "contain a leafname\n";
600 else if (provides(context, text_uri_list))
601 target = text_uri_list;
602 else
603 error = "Sorry - I require a target type of text/uri-list or "
604 "XdndDirectSave0.";
606 if (error)
608 gtk_drag_finish(context, FALSE, FALSE, time); /* Failure */
610 delayed_error("ROX-Filer", error);
612 else
613 gtk_drag_get_data(widget, context, target, time);
615 return TRUE;
618 /* Called when some data arrives from the remote app (which we asked for
619 * in drag_drop).
621 static void drag_data_received(GtkWidget *widget,
622 GdkDragContext *context,
623 gint x,
624 gint y,
625 GtkSelectionData *selection_data,
626 guint info,
627 guint32 time)
629 if (!selection_data->data)
631 /* Timeout? */
632 gtk_drag_finish(context, FALSE, FALSE, time); /* Failure */
633 return;
636 switch (info)
638 case TARGET_XDS:
639 got_data_xds_reply(widget, context,
640 selection_data, time);
641 break;
642 case TARGET_RAW:
643 got_data_raw(widget, context, selection_data, time);
644 break;
645 case TARGET_URI_LIST:
646 got_uri_list(widget, context, selection_data, time);
647 break;
648 default:
649 gtk_drag_finish(context, FALSE, FALSE, time);
650 delayed_error("drag_data_received", "Unknown target");
651 break;
655 static void got_data_xds_reply(GtkWidget *widget,
656 GdkDragContext *context,
657 GtkSelectionData *selection_data,
658 guint32 time)
660 gboolean mark_unsafe = TRUE;
661 char response = *selection_data->data;
662 char *error = NULL;
664 if (selection_data->length != 1)
665 response = '?';
667 if (response == 'F')
669 /* Sender couldn't save there - ask for another
670 * type if possible.
672 if (provides(context, application_octet_stream))
674 mark_unsafe = FALSE; /* Wait and see */
676 gtk_drag_get_data(widget, context,
677 application_octet_stream, time);
679 else
680 error = "Remote app can't or won't send me "
681 "the data - sorry";
683 else if (response == 'S')
685 FilerWindow *filer_window;
687 /* Success - data is saved */
688 mark_unsafe = FALSE; /* It really is safe */
689 gtk_drag_finish(context, TRUE, FALSE, time);
691 filer_window = gtk_object_get_data(GTK_OBJECT(widget),
692 "filer_window");
693 g_return_if_fail(filer_window != NULL);
695 scan_dir(filer_window);
697 else if (response != 'E')
699 error = "XDS protocol error: "
700 "return code should be 'S', 'F' or 'E'\n";
702 /* else: error has been reported by the sender */
704 if (mark_unsafe)
706 set_xds_prop(context, "");
707 /* Unsave also implies that the drag failed */
708 gtk_drag_finish(context, FALSE, FALSE, time);
711 if (error)
712 delayed_error("ROX-Filer", error);
715 static void got_data_raw(GtkWidget *widget,
716 GdkDragContext *context,
717 GtkSelectionData *selection_data,
718 guint32 time)
720 FilerWindow *filer_window;
721 char *leafname;
722 int fd;
723 char *error = NULL;
724 char *dest_path;
726 filer_window = gtk_object_get_data(GTK_OBJECT(widget), "filer_window");
727 g_return_if_fail(filer_window != NULL);
729 leafname = g_dataset_get_data(context, "leafname");
730 if (!leafname)
731 leafname = "UntitledData";
733 dest_path = get_dest_path(filer_window, context);
735 fd = open(make_path(dest_path, leafname)->str,
736 O_WRONLY | O_CREAT | O_EXCL | O_NOCTTY,
737 S_IRUSR | S_IRGRP | S_IROTH |
738 S_IWUSR | S_IWGRP | S_IWOTH);
740 if (fd == -1)
741 error = g_strerror(errno);
742 else
744 if (write(fd,
745 selection_data->data,
746 selection_data->length) == -1)
747 error = g_strerror(errno);
749 if (close(fd) == -1 && !error)
750 error = g_strerror(errno);
752 scan_dir(filer_window);
755 if (error)
757 if (provides(context, XdndDirectSave0))
758 set_xds_prop(context, "");
759 gtk_drag_finish(context, FALSE, FALSE, time); /* Failure */
760 delayed_error("Error saving file", error);
762 else
763 gtk_drag_finish(context, TRUE, FALSE, time); /* Success! */
766 /* We've got a list of URIs from somewhere (probably another filer).
767 * If the files are on the local machine then try to copy them ourselves,
768 * otherwise, if there was only one file and application/octet-stream was
769 * provided, get the data via the X server.
771 static void got_uri_list(GtkWidget *widget,
772 GdkDragContext *context,
773 GtkSelectionData *selection_data,
774 guint32 time)
776 FilerWindow *filer_window;
777 GSList *uri_list;
778 char *error = NULL;
779 char **argv = NULL; /* Command to exec, or NULL */
780 GSList *next_uri;
781 gboolean send_reply = TRUE;
782 char *dest_path;
784 filer_window = gtk_object_get_data(GTK_OBJECT(widget), "filer_window");
785 g_return_if_fail(filer_window != NULL);
787 dest_path = get_dest_path(filer_window, context);
789 uri_list = uri_list_to_gslist(selection_data->data);
791 if (!uri_list)
792 error = "No URIs in the text/uri-list (nothing to do!)";
793 else if ((!uri_list->next) && (!get_local_path(uri_list->data)))
795 /* There is one URI in the list, and it's not on the local
796 * machine. Get it via the X server if possible.
799 if (provides(context, application_octet_stream))
801 char *leaf;
802 leaf = strrchr(uri_list->data, '/');
803 if (leaf)
804 leaf++;
805 else
806 leaf = uri_list->data;
807 g_dataset_set_data_full(context, "leafname",
808 g_strdup(leaf), g_free);
809 gtk_drag_get_data(widget, context,
810 application_octet_stream, time);
811 send_reply = FALSE;
813 else
814 error = "Can't get data from remote machine "
815 "(application/octet-stream not provided)";
817 else
819 int local_files = 0;
820 const char *start_args[] = {"xterm", "-wf", "-e"};
821 int argc = sizeof(start_args) / sizeof(char *);
823 next_uri = uri_list;
825 argv = g_malloc(sizeof(start_args) +
826 sizeof(char *) * (g_slist_length(uri_list) + 4));
827 memcpy(argv, start_args, sizeof(start_args));
829 if (context->action == GDK_ACTION_MOVE)
831 argv[argc++] = "mv";
832 argv[argc++] = "-iv";
834 else if (context->action == GDK_ACTION_LINK)
836 argv[argc++] = "ln";
837 argv[argc++] = "-vis";
839 else
841 argv[argc++] = "cp";
842 argv[argc++] = "-Riva";
845 /* Either one local URI, or a list. If anything in the list
846 * isn't local then we are stuck.
849 while (next_uri)
851 char *path;
853 path = get_local_path((char *) next_uri->data);
855 if (path)
857 argv[argc++] = path;
858 local_files++;
860 else
861 error = "Some of these files are on a "
862 "different machine - they will be "
863 "ignored - sorry";
865 next_uri = next_uri->next;
868 if (local_files < 1)
870 error = "None of these files are on the local machine "
871 "- I can't operate on multiple remote files - "
872 "sorry.";
873 g_free(argv);
874 argv = NULL;
876 else
878 argv[argc++] = dest_path;
879 argv[argc++] = NULL;
883 if (error)
885 gtk_drag_finish(context, FALSE, FALSE, time); /* Failure */
886 delayed_error("Error getting file list", error);
888 else if (send_reply)
890 gtk_drag_finish(context, TRUE, FALSE, time); /* Success! */
891 if (argv)
893 int child; /* Child process ID */
895 child = spawn(argv);
896 if (child)
897 g_hash_table_insert(child_to_filer,
898 (gpointer) child, filer_window);
899 else
900 delayed_error("ROX-Filer",
901 "Failed to fork() child process");
902 g_free(argv);
906 next_uri = uri_list;
907 while (next_uri)
909 g_free(next_uri->data);
910 next_uri = next_uri->next;
912 g_slist_free(uri_list);