r20: Drag and drop loading and saving works (locally and remotely).
[rox-filer.git] / ROX-Filer / src / dnd.c
blob63ee42c1f62af6766f40e666750f7e8ca779f9b8
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 application_octet_stream;
41 /* Static prototypes */
42 static void create_uri_list(GString *string,
43 Collection *collection,
44 FilerWindow *filer_window);
45 static FileItem *selected_item(Collection *collection);
46 static gboolean drag_drop(GtkWidget *widget,
47 GdkDragContext *context,
48 gint x,
49 gint y,
50 guint time);
51 static gboolean provides(GdkDragContext *context, GdkAtom target);
52 static void set_xds_prop(GdkDragContext *context, char *text);
53 static void drag_data_received(GtkWidget *widget,
54 GdkDragContext *context,
55 gint x,
56 gint y,
57 GtkSelectionData *selection_data,
58 guint info,
59 guint32 time);
60 static void got_data_xds_reply(GtkWidget *widget,
61 GdkDragContext *context,
62 GtkSelectionData *selection_data,
63 guint32 time);
64 static void got_data_raw(GtkWidget *widget,
65 GdkDragContext *context,
66 GtkSelectionData *selection_data,
67 guint32 time);
68 static gboolean load_file(char *pathname, char **data_out, long *length_out);
70 void dnd_init()
72 XdndDirectSave0 = gdk_atom_intern("XdndDirectSave0", FALSE);
73 text_plain = gdk_atom_intern("text/plain", FALSE);
74 application_octet_stream = gdk_atom_intern("application/octet-stream",
75 FALSE);
78 /* Set the XdndDirectSave0 property on the source window for this context */
79 static void set_xds_prop(GdkDragContext *context, char *text)
81 gdk_property_change(context->source_window,
82 XdndDirectSave0,
83 text_plain, 8,
84 GDK_PROP_MODE_REPLACE,
85 text,
86 strlen(text));
89 static char *get_xds_prop(GdkDragContext *context)
91 guchar *prop_text;
92 gint length;
94 if (gdk_property_get(context->source_window,
95 XdndDirectSave0,
96 text_plain,
97 0, MAXURILEN,
98 FALSE,
99 NULL, NULL,
100 &length, &prop_text) && prop_text)
102 /* Terminate the string */
103 prop_text = g_realloc(prop_text, length + 1);
104 prop_text[length] = '\0';
105 return prop_text;
108 return NULL;
111 /* Is the sender willing to supply this target type? */
112 static gboolean provides(GdkDragContext *context, GdkAtom target)
114 GList *targets = context->targets;
116 while (targets && ((GdkAtom) targets->data != target))
117 targets = targets->next;
119 return targets != NULL;
122 /* Append all the URIs in the selection to the string */
123 static void create_uri_list(GString *string,
124 Collection *collection,
125 FilerWindow *filer_window)
127 GString *leader;
128 int i, num_selected;
130 leader = g_string_new("file://");
131 g_string_append(leader, our_host_name());
132 g_string_append(leader, filer_window->path);
133 if (leader->str[leader->len - 1] != '/')
134 g_string_append_c(leader, '/');
136 num_selected = collection->number_selected;
138 for (i = 0; num_selected > 0; i++)
140 if (collection->items[i].selected)
142 FileItem *item = (FileItem *) collection->items[i].data;
144 g_string_append(string, leader->str);
145 g_string_append(string, item->leafname);
146 g_string_append(string, "\r\n");
147 num_selected--;
151 g_string_free(leader, TRUE);
154 static FileItem *selected_item(Collection *collection)
156 int i;
158 g_return_val_if_fail(collection != NULL, NULL);
159 g_return_val_if_fail(IS_COLLECTION(collection), NULL);
160 g_return_val_if_fail(collection->number_selected == 1, NULL);
162 for (i = 0; i < collection->number_of_items; i++)
163 if (collection->items[i].selected)
164 return (FileItem *) collection->items[i].data;
166 g_warning("selected_item: number_selected is wrong\n");
168 return NULL;
171 /* DRAGGING FROM US */
173 /* The user has held the mouse button down over an item and moved -
174 * start a drag.
176 * We always provide text/uri-list. If we are dragging a single, regular file
177 * then we also offer application/octet-stream.
179 void drag_selection(Collection *collection,
180 GdkEventMotion *event,
181 gint number_selected,
182 gpointer user_data)
184 FilerWindow *filer_window = (FilerWindow *) user_data;
185 GtkWidget *widget;
186 MaskedPixmap *image;
187 GdkDragContext *context;
188 GtkTargetList *target_list;
189 GtkTargetEntry target_table[] =
191 {"text/uri-list", 0, TARGET_URI_LIST},
192 {"application/octet-stream", 0, TARGET_RAW},
194 FileItem *item;
196 if (number_selected == 1)
197 item = selected_item(collection);
198 else
199 item = NULL;
201 widget = GTK_WIDGET(collection);
203 target_list = gtk_target_list_new(target_table,
204 item && item->base_type == TYPE_FILE ? 2 : 1);
206 context = gtk_drag_begin(widget,
207 target_list,
208 GDK_ACTION_COPY,
209 (event->state & GDK_BUTTON1_MASK) ? 1 : 2,
210 (GdkEvent *) event);
211 g_dataset_set_data(context, "filer_window", filer_window);
213 image = item ? item->image : &default_pixmap[TYPE_MULTIPLE];
215 gtk_drag_set_icon_pixmap(context,
216 gtk_widget_get_colormap(widget),
217 image->pixmap,
218 image->mask,
219 0, 0);
222 /* Called when a remote app wants us to send it some data.
223 * TODO: Maybe we should handle errors better (ie, let the remote app know
224 * the drag has failed)?
226 void drag_data_get(GtkWidget *widget,
227 GdkDragContext *context,
228 GtkSelectionData *selection_data,
229 guint info,
230 guint32 time)
232 char *to_send = "E"; /* Default to sending an error */
233 long to_send_length = 1;
234 gboolean delete_once_sent = FALSE;
235 GdkAtom type = XA_STRING;
236 GString *string;
237 FilerWindow *filer_window;
238 FileItem *item;
240 filer_window = g_dataset_get_data(context, "filer_window");
241 g_return_if_fail(filer_window != NULL);
243 switch (info)
245 case TARGET_RAW:
246 item = selected_item(filer_window->collection);
247 if (item && load_file(make_path(filer_window->path,
248 item->leafname)->str,
249 &to_send, &to_send_length))
251 delete_once_sent = TRUE;
252 type = application_octet_stream; /* XXX */
253 break;
255 return;
256 case TARGET_URI_LIST:
257 string = g_string_new(NULL);
258 create_uri_list(string,
259 COLLECTION(widget),
260 filer_window);
261 to_send = string->str;
262 to_send_length = string->len;
263 g_string_free(string, FALSE);
264 break;
265 default:
266 report_error("drag_data_get",
267 "Internal error - bad info type\n");
268 break;
271 gtk_selection_data_set(selection_data,
272 type,
274 to_send,
275 to_send_length);
277 if (delete_once_sent)
278 g_free(to_send);
281 /* Load the file into memory. Return TRUE on success. */
282 static gboolean load_file(char *pathname, char **data_out, long *length_out)
284 FILE *file;
285 long length;
286 char *buffer;
287 gboolean retval = FALSE;
289 file = fopen(pathname, "r");
291 if (!file)
293 report_error("Opening file for DND", g_strerror(errno));
294 return FALSE;
297 fseek(file, 0, SEEK_END);
298 length = ftell(file);
300 buffer = malloc(length);
301 if (buffer)
303 fseek(file, 0, SEEK_SET);
304 fread(buffer, 1, length, file);
306 if (ferror(file))
308 report_error("Loading file for DND", g_strerror(errno));
309 g_free(buffer);
311 else
313 *data_out = buffer;
314 *length_out = length;
315 retval = TRUE;
318 else
319 report_error("Loading file for DND",
320 "Can't allocate memory for buffer to "
321 "transfer this file");
323 fclose(file);
325 return retval;
328 /* DRAGGING TO US */
330 /* Set up this filer window as a drop target. Called once, when the
331 * filer window is first created.
333 void drag_set_dest(GtkWidget *widget, FilerWindow *filer_window)
335 GtkTargetEntry target_table[] =
337 {"text/uri-list", 0, TARGET_URI_LIST},
338 {"XdndDirectSave0", 0, TARGET_XDS},
339 {"application/octet-stream", 0, TARGET_RAW},
342 gtk_drag_dest_set(widget,
343 GTK_DEST_DEFAULT_MOTION,
344 target_table,
345 sizeof(target_table) / sizeof(*target_table),
346 GDK_ACTION_COPY | GDK_ACTION_MOVE
347 | GDK_ACTION_LINK | GDK_ACTION_PRIVATE);
349 gtk_signal_connect(GTK_OBJECT(widget), "drag_drop",
350 GTK_SIGNAL_FUNC(drag_drop), filer_window);
351 gtk_signal_connect(GTK_OBJECT(widget), "drag_data_received",
352 GTK_SIGNAL_FUNC(drag_data_received), filer_window);
355 /* User has tried to drop some data on us. Decide what format we would
356 * like the data in.
358 static gboolean drag_drop(GtkWidget *widget,
359 GdkDragContext *context,
360 gint x,
361 gint y,
362 guint time)
364 char *error = NULL;
365 char *leafname = NULL;
366 FilerWindow *filer_window;
367 GdkAtom target;
369 filer_window = gtk_object_get_data(GTK_OBJECT(widget), "filer_window");
370 g_return_val_if_fail(filer_window != NULL, TRUE);
372 if (gtk_drag_get_source_widget(context) == widget)
374 /* Ignore drags within a single window */
375 gtk_drag_finish(context, FALSE, FALSE, time); /* Failure */
376 return TRUE;
379 if (provides(context, XdndDirectSave0))
381 leafname = get_xds_prop(context);
382 if (leafname)
384 if (strchr(leafname, '/'))
386 error = "XDS protocol error: "
387 "leafname may not contain '/'\n";
388 g_free(leafname);
390 leafname = NULL;
392 else
394 GString *uri;
396 uri = g_string_new(NULL);
397 g_string_sprintf(uri, "file://%s%s",
398 our_host_name(),
399 make_path(filer_window->path,
400 leafname)->str);
401 set_xds_prop(context, uri->str);
402 g_string_free(uri, TRUE);
404 target = XdndDirectSave0;
405 g_dataset_set_data_full(context, "leafname",
406 leafname, g_free);
409 else
410 error = "XdndDirectSave0 target provided, but the atom "
411 "XdndDirectSave0 (type text/plain) did not "
412 "contain a leafname\n";
414 else
416 error = "Normal DND";
419 if (error)
421 gtk_drag_finish(context, FALSE, FALSE, time); /* Failure */
423 report_error("ROX-Filer", error);
425 else
426 gtk_drag_get_data(widget, context, target, time);
428 return TRUE;
431 /* Called when some data arrives from the remote app (which we asked for
432 * in drag_drop).
434 static void drag_data_received(GtkWidget *widget,
435 GdkDragContext *context,
436 gint x,
437 gint y,
438 GtkSelectionData *selection_data,
439 guint info,
440 guint32 time)
442 if (!selection_data->data)
444 /* Timeout? */
445 gtk_drag_finish(context, FALSE, FALSE, time); /* Failure */
446 return;
449 switch (info)
451 case TARGET_XDS:
452 got_data_xds_reply(widget, context,
453 selection_data, time);
454 break;
455 case TARGET_RAW:
456 got_data_raw(widget, context, selection_data, time);
457 break;
458 case TARGET_URI_LIST:
459 gtk_drag_finish(context, FALSE, FALSE, time);
460 report_error("drag_data_received", "got URI list");
461 break;
462 default:
463 gtk_drag_finish(context, FALSE, FALSE, time);
464 report_error("drag_data_received", "Unknown target");
465 break;
469 static void got_data_xds_reply(GtkWidget *widget,
470 GdkDragContext *context,
471 GtkSelectionData *selection_data,
472 guint32 time)
474 gboolean mark_unsafe = TRUE;
475 char response = *selection_data->data;
476 char *error = NULL;
478 if (selection_data->length != 1)
479 response = '?';
481 if (response == 'F')
483 /* Sender couldn't save there - ask for another
484 * type if possible.
486 if (provides(context, application_octet_stream))
488 mark_unsafe = FALSE; /* Wait and see */
490 gtk_drag_get_data(widget, context,
491 application_octet_stream, time);
493 else
494 error = "Remote app can't or won't send me "
495 "the data - sorry";
497 else if (response == 'S')
499 FilerWindow *filer_window;
501 /* Success - data is saved */
502 mark_unsafe = FALSE; /* It really is safe */
503 gtk_drag_finish(context, TRUE, FALSE, time);
505 filer_window = gtk_object_get_data(GTK_OBJECT(widget),
506 "filer_window");
507 g_return_if_fail(filer_window != NULL);
509 scan_dir(filer_window);
511 else if (response != 'E')
513 error = "XDS protocol error: "
514 "return code should be 'S', 'F' or 'E'\n";
516 /* else: error has been reported by the sender */
518 if (mark_unsafe)
520 set_xds_prop(context, "");
521 /* Unsave also implies that the drag failed */
522 gtk_drag_finish(context, FALSE, FALSE, time);
525 if (error)
527 report_error("ROX-Filer", error);
531 static void got_data_raw(GtkWidget *widget,
532 GdkDragContext *context,
533 GtkSelectionData *selection_data,
534 guint32 time)
536 FilerWindow *filer_window;
537 char *leafname;
538 int fd;
539 char *error = NULL;
540 gboolean using_XDS = TRUE;
542 filer_window = gtk_object_get_data(GTK_OBJECT(widget), "filer_window");
543 g_return_if_fail(filer_window != NULL);
545 leafname = g_dataset_get_data(context, "leafname");
547 if (!leafname)
549 using_XDS = FALSE;
550 leafname = "UntitledData"; /* TODO: Find a better name */
553 fd = open(make_path(filer_window->path, leafname)->str,
554 O_WRONLY | O_CREAT | O_EXCL | O_NOCTTY,
555 S_IRUSR | S_IRGRP | S_IROTH | S_IWUSR | S_IWGRP | S_IWOTH);
557 if (fd == -1)
558 error = g_strerror(errno);
559 else
561 if (write(fd,
562 selection_data->data,
563 selection_data->length) != -1)
564 error = g_strerror(errno);
566 if (close(fd) != -1 && !error)
567 error = g_strerror(errno);
569 scan_dir(filer_window);
572 if (error)
574 set_xds_prop(context, "");
575 gtk_drag_finish(context, FALSE, FALSE, time); /* Failure */
576 report_error("Error saving file", error);
578 else
579 gtk_drag_finish(context, TRUE, FALSE, time); /* Success! */