4 * ROX-Filer, filer for the ROX desktop project
5 * By Thomas Leonard, <tal197@ecs.soton.ac.uk>.
8 /* dnd.c - code for handling drag and drop */
19 #include <X11/Xatom.h>
21 #include <collection.h>
25 #include "gui_support.h"
28 #define MAXURILEN 4096 /* Longest URI to allow */
37 GdkAtom XdndDirectSave0
;
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
,
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
,
57 GtkSelectionData
*selection_data
,
60 static void got_data_xds_reply(GtkWidget
*widget
,
61 GdkDragContext
*context
,
62 GtkSelectionData
*selection_data
,
64 static void got_data_raw(GtkWidget
*widget
,
65 GdkDragContext
*context
,
66 GtkSelectionData
*selection_data
,
68 static gboolean
load_file(char *pathname
, char **data_out
, long *length_out
);
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",
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
,
84 GDK_PROP_MODE_REPLACE
,
89 static char *get_xds_prop(GdkDragContext
*context
)
94 if (gdk_property_get(context
->source_window
,
100 &length
, &prop_text
) && prop_text
)
102 /* Terminate the string */
103 prop_text
= g_realloc(prop_text
, length
+ 1);
104 prop_text
[length
] = '\0';
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
)
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");
151 g_string_free(leader
, TRUE
);
154 static FileItem
*selected_item(Collection
*collection
)
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");
171 /* DRAGGING FROM US */
173 /* The user has held the mouse button down over an item and moved -
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
,
184 FilerWindow
*filer_window
= (FilerWindow
*) user_data
;
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
},
196 if (number_selected
== 1)
197 item
= selected_item(collection
);
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
,
209 (event
->state
& GDK_BUTTON1_MASK
) ? 1 : 2,
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
),
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
,
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
;
237 FilerWindow
*filer_window
;
240 filer_window
= g_dataset_get_data(context
, "filer_window");
241 g_return_if_fail(filer_window
!= NULL
);
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 */
256 case TARGET_URI_LIST
:
257 string
= g_string_new(NULL
);
258 create_uri_list(string
,
261 to_send
= string
->str
;
262 to_send_length
= string
->len
;
263 g_string_free(string
, FALSE
);
266 report_error("drag_data_get",
267 "Internal error - bad info type\n");
271 gtk_selection_data_set(selection_data
,
277 if (delete_once_sent
)
281 /* Load the file into memory. Return TRUE on success. */
282 static gboolean
load_file(char *pathname
, char **data_out
, long *length_out
)
287 gboolean retval
= FALSE
;
289 file
= fopen(pathname
, "r");
293 report_error("Opening file for DND", g_strerror(errno
));
297 fseek(file
, 0, SEEK_END
);
298 length
= ftell(file
);
300 buffer
= malloc(length
);
303 fseek(file
, 0, SEEK_SET
);
304 fread(buffer
, 1, length
, file
);
308 report_error("Loading file for DND", g_strerror(errno
));
314 *length_out
= length
;
319 report_error("Loading file for DND",
320 "Can't allocate memory for buffer to "
321 "transfer this file");
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
,
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
358 static gboolean
drag_drop(GtkWidget
*widget
,
359 GdkDragContext
*context
,
365 char *leafname
= NULL
;
366 FilerWindow
*filer_window
;
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 */
379 if (provides(context
, XdndDirectSave0
))
381 leafname
= get_xds_prop(context
);
384 if (strchr(leafname
, '/'))
386 error
= "XDS protocol error: "
387 "leafname may not contain '/'\n";
396 uri
= g_string_new(NULL
);
397 g_string_sprintf(uri
, "file://%s%s",
399 make_path(filer_window
->path
,
401 set_xds_prop(context
, uri
->str
);
402 g_string_free(uri
, TRUE
);
404 target
= XdndDirectSave0
;
405 g_dataset_set_data_full(context
, "leafname",
410 error
= "XdndDirectSave0 target provided, but the atom "
411 "XdndDirectSave0 (type text/plain) did not "
412 "contain a leafname\n";
416 error
= "Normal DND";
421 gtk_drag_finish(context
, FALSE
, FALSE
, time
); /* Failure */
423 report_error("ROX-Filer", error
);
426 gtk_drag_get_data(widget
, context
, target
, time
);
431 /* Called when some data arrives from the remote app (which we asked for
434 static void drag_data_received(GtkWidget
*widget
,
435 GdkDragContext
*context
,
438 GtkSelectionData
*selection_data
,
442 if (!selection_data
->data
)
445 gtk_drag_finish(context
, FALSE
, FALSE
, time
); /* Failure */
452 got_data_xds_reply(widget
, context
,
453 selection_data
, time
);
456 got_data_raw(widget
, context
, selection_data
, time
);
458 case TARGET_URI_LIST
:
459 gtk_drag_finish(context
, FALSE
, FALSE
, time
);
460 report_error("drag_data_received", "got URI list");
463 gtk_drag_finish(context
, FALSE
, FALSE
, time
);
464 report_error("drag_data_received", "Unknown target");
469 static void got_data_xds_reply(GtkWidget
*widget
,
470 GdkDragContext
*context
,
471 GtkSelectionData
*selection_data
,
474 gboolean mark_unsafe
= TRUE
;
475 char response
= *selection_data
->data
;
478 if (selection_data
->length
!= 1)
483 /* Sender couldn't save there - ask for another
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
);
494 error
= "Remote app can't or won't send me "
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
),
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 */
520 set_xds_prop(context
, "");
521 /* Unsave also implies that the drag failed */
522 gtk_drag_finish(context
, FALSE
, FALSE
, time
);
527 report_error("ROX-Filer", error
);
531 static void got_data_raw(GtkWidget
*widget
,
532 GdkDragContext
*context
,
533 GtkSelectionData
*selection_data
,
536 FilerWindow
*filer_window
;
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");
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
);
558 error
= g_strerror(errno
);
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
);
574 set_xds_prop(context
, "");
575 gtk_drag_finish(context
, FALSE
, FALSE
, time
); /* Failure */
576 report_error("Error saving file", error
);
579 gtk_drag_finish(context
, TRUE
, FALSE
, time
); /* Success! */