r345: Fixed a bug that prevented i18n from working.
[rox-filer/dt.git] / ROX-Filer / src / dnd.c
blobd9d763df1a10696574568bb4e262c6773477de76
1 /*
2 * $Id$
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)
10 * any later version.
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
15 * more details.
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 */
24 #include "config.h"
26 #include <stdlib.h>
27 #include <stdio.h>
28 #include <fcntl.h>
29 #include <errno.h>
30 #include <string.h>
31 #include <sys/param.h>
33 #include <X11/Xlib.h>
34 #include <X11/Xatom.h>
35 #include <gtk/gtk.h>
36 #include "collection.h"
38 #include "global.h"
40 #include "dnd.h"
41 #include "type.h"
42 #include "filer.h"
43 #include "action.h"
44 #include "pixmaps.h"
45 #include "gui_support.h"
46 #include "support.h"
47 #include "options.h"
48 #include "run.h"
49 #include "pinboard.h"
50 #include "dir.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,
58 gint x,
59 gint y,
60 guint time,
61 FilerWindow *filer_window);
62 static void drag_leave(GtkWidget *widget,
63 GdkDragContext *context,
64 guint32 time,
65 FilerWindow *filer_window);
66 static void desktop_drag_data_received(GtkWidget *widget,
67 GdkDragContext *context,
68 gint x,
69 gint y,
70 GtkSelectionData *selection_data,
71 guint info,
72 guint32 time,
73 FilerWindow *filer_window);
74 static void got_data_xds_reply(GtkWidget *widget,
75 GdkDragContext *context,
76 GtkSelectionData *selection_data,
77 guint32 time);
78 static void got_data_raw(GtkWidget *widget,
79 GdkDragContext *context,
80 GtkSelectionData *selection_data,
81 guint32 time);
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,
86 guint32 time);
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,
99 gint x,
100 gint y,
101 guint time,
102 gpointer data);
103 static void drag_data_received(GtkWidget *widget,
104 GdkDragContext *context,
105 gint x,
106 gint y,
107 GtkSelectionData *selection_data,
108 guint info,
109 guint32 time,
110 gpointer user_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"),
129 create_options,
130 update_options,
131 set_options,
132 save_options
135 enum
137 TARGET_RAW,
138 TARGET_URI_LIST,
139 TARGET_RUN_ACTION,
140 TARGET_XDS,
143 GdkAtom XdndDirectSave0;
144 GdkAtom _rox_run_action;
145 GdkAtom xa_text_plain;
146 GdkAtom text_uri_list;
147 GdkAtom application_octet_stream;
149 void dnd_init()
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",
156 FALSE);
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);
164 /* OPTIONS */
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
175 * fill them in yet.
177 static GtkWidget *create_options()
179 GtkWidget *vbox;
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 "
195 "filer windows"));
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);
203 toggle_spring_open =
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);
211 return vbox;
214 static void update_options()
216 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(toggle_no_hostnames),
217 o_no_hostnames);
218 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(toggle_drag_to_icons),
219 o_drag_to_icons);
220 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(toggle_spring_open),
221 o_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;
244 return NULL;
247 static char *drag_to_icons(char *data)
249 o_drag_to_icons = atoi(data) != 0;
250 return NULL;
253 static char *spring_open(char *data)
255 o_spring_open = atoi(data) != 0;
256 return NULL;
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,
265 XdndDirectSave0,
266 xa_text_plain, 8,
267 GDK_PROP_MODE_REPLACE,
268 text,
269 strlen(text));
272 static char *get_xds_prop(GdkDragContext *context)
274 guchar *prop_text;
275 gint length;
277 if (gdk_property_get(context->source_window,
278 XdndDirectSave0,
279 xa_text_plain,
280 0, MAXURILEN,
281 FALSE,
282 NULL, NULL,
283 &length, &prop_text) && prop_text)
285 /* Terminate the string */
286 prop_text = g_realloc(prop_text, length + 1);
287 prop_text[length] = '\0';
288 return prop_text;
291 return NULL;
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)
311 GSList *list = NULL;
313 while (*uri_list)
315 char *linebreak;
316 char *uri;
317 int length;
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"));
326 return list;
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);
335 uri[length] = 0;
336 list = g_slist_append(list, uri);
339 uri_list = linebreak + 2;
342 return list;
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;
361 else
362 actions = GDK_ACTION_MOVE;
364 target_list = gtk_target_list_new(target_table, 1);
366 context = gtk_drag_begin(widget,
367 target_list,
368 actions,
369 (event->state & GDK_BUTTON1_MASK) ? 1 :
370 (event->state & GDK_BUTTON2_MASK) ? 2 : 3,
371 (GdkEvent *) event);
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),
378 im_multiple->pixmap,
379 im_multiple->mask,
380 0, 0);
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,
390 guchar *full_path,
391 DirItem *item,
392 gboolean set_run_action)
394 guchar *uri;
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},
401 {"", 0, TARGET_RAW},
404 g_return_if_fail(full_path != NULL);
405 g_return_if_fail(item != NULL);
407 if (set_run_action)
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, "/",
420 t->subtype, NULL);
421 target_list = gtk_target_list_new(target_table, 3);
422 g_free(target_table[2].target);
424 else
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;
429 else
430 actions = GDK_ACTION_MOVE;
432 context = gtk_drag_begin(widget,
433 target_list,
434 actions,
435 (event->state & GDK_BUTTON1_MASK) ? 1 :
436 (event->state & GDK_BUTTON2_MASK) ? 2 : 3,
437 (GdkEvent *) event);
439 if (set_run_action)
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),
446 g_free);
448 else
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",
456 uri, g_free);
459 gtk_drag_set_icon_pixmap(context,
460 gtk_widget_get_colormap(widget),
461 item->image->pixmap,
462 item->image->mask,
463 0, 0);
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,
482 guint info,
483 guint32 time,
484 gpointer 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;
490 guchar *path;
492 switch (info)
494 case TARGET_RAW:
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;
500 break;
502 g_warning("drag_data_get: Can't find path!\n");
503 return;
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;
508 break;
509 case TARGET_RUN_ACTION:
510 to_send = g_dataset_get_data(context, "mime_type");
511 if (to_send)
512 to_send_length = strlen(to_send);
513 else
514 to_send = "E";
515 delete_once_sent = FALSE;
516 break;
517 default:
518 delayed_error("drag_data_get",
519 _("Internal error - bad info type"));
520 break;
523 gtk_selection_data_set(selection_data,
524 type,
526 to_send,
527 to_send_length);
529 if (delete_once_sent)
530 g_free(to_send);
533 /* DRAGGING TO US */
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,
550 target_table,
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,
590 target_table,
591 sizeof(target_table) / sizeof(*target_table),
592 GDK_ACTION_LINK);
593 gtk_signal_connect(GTK_OBJECT(widget), "drag_data_received",
594 (GtkSignalFunc) desktop_drag_data_received,
595 NULL);
598 static void scrolled(GtkAdjustment *adj, Collection *collection)
600 collection_set_cursor_item(collection, -1);
601 dnd_spring_abort();
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,
609 gint x,
610 gint y,
611 guint time,
612 FilerWindow *filer_window)
614 DirItem *item;
615 int item_number;
616 GdkDragAction action = context->suggested_action;
617 char *new_path = NULL;
618 char *type = 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,
626 x, y);
627 else
628 item_number = -1;
630 item = item_number >= 0
631 ? (DirItem *) filer_window->collection->items[item_number].data
632 : NULL;
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.
639 if (!item)
640 goto out;
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;
648 else
649 goto out;
652 else if (item)
654 /* If we didn't drop onto a directory, application or
655 * executable file then act as though the drop is to the
656 * window background.
658 if (item->base_type != TYPE_DIRECTORY
659 && !(item->flags & ITEM_FLAG_EXEC_FILE))
660 item = NULL;
663 if (!item)
665 /* Drop onto the window background */
666 collection_set_cursor_item(filer_window->collection,
667 -1);
669 if (gtk_drag_get_source_widget(context) == widget)
670 goto out;
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;
680 else
681 type = drop_dest_dir;
683 if (type)
684 new_path = filer_window->path;
686 else
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)
695 goto out;
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;
705 else
707 if (provides(context, text_uri_list) ||
708 provides(context, application_octet_stream))
709 type = drop_dest_prog;
712 if (type)
713 new_path = make_path(filer_window->path,
714 item->leafname)->str;
717 out:
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)
729 if (scrolled_adj)
730 gtk_signal_disconnect(scrolled_adj,
731 scrolled_signal);
732 scrolled_adj = vadj;
733 scrolled_signal = gtk_signal_connect(
734 scrolled_adj,
735 "value_changed",
736 GTK_SIGNAL_FUNC(scrolled),
737 filer_window->collection);
739 dnd_spring_load(context);
741 else
742 dnd_spring_abort();
744 if (type && item)
745 collection_set_cursor_item(filer_window->collection,
746 item_number);
748 g_dataset_set_data(context, "drop_dest_type", type);
749 if (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);
754 retval = TRUE;
757 return retval;
760 /* Remove highlights */
761 static void drag_leave(GtkWidget *widget,
762 GdkDragContext *context,
763 guint32 time,
764 FilerWindow *filer_window)
766 collection_set_autoscroll(filer_window->collection, FALSE);
767 collection_set_cursor_item(filer_window->collection, -1);
768 dnd_spring_abort();
769 if (scrolled_adj)
771 gtk_signal_disconnect(scrolled_adj,
772 scrolled_signal);
773 scrolled_adj = NULL;
777 /* User has tried to drop some data on us. Decide what format we would
778 * like the data in.
780 static gboolean drag_drop(GtkWidget *widget,
781 GdkDragContext *context,
782 gint x,
783 gint y,
784 guint time,
785 gpointer data)
787 char *error = NULL;
788 char *leafname = NULL;
789 GdkAtom target = GDK_NONE;
790 char *dest_path;
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);
801 if (leafname)
803 if (strchr(leafname, '/'))
805 error = _("XDS protocol error: "
806 "leafname may not contain '/'\n");
807 g_free(leafname);
809 leafname = NULL;
811 else
813 GString *uri;
815 uri = g_string_new(NULL);
816 g_string_sprintf(uri, "file://%s%s",
817 our_host_name(),
818 make_path(dest_path,
819 leafname)->str);
820 set_xds_prop(context, uri->str);
821 g_string_free(uri, TRUE);
823 target = XdndDirectSave0;
824 g_dataset_set_data_full(context, "leafname",
825 leafname, g_free);
828 else
829 error = _(
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;
841 else
843 if (dest_type == drop_dest_dir)
844 error = _("Sorry - I require a target type of "
845 "text/uri-list or XdndDirectSave0.");
846 else
847 error = _("Sorry - I require a target type of "
848 "text/uri-list or application/octet-stream.");
851 if (error)
853 gtk_drag_finish(context, FALSE, FALSE, time); /* Failure */
855 delayed_error(PROJECT, error);
857 else
858 gtk_drag_get_data(widget, context, target, time);
860 return TRUE;
863 static void got_run_action(GtkWidget *widget,
864 GdkDragContext *context,
865 GtkSelectionData *selection_data,
866 guint32 time)
868 char *type = selection_data->data;
869 char *media, *sub;
870 char *dest_path, *link;
871 char buffer[MAXPATHLEN + 1];
872 int len;
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);
880 if (len > 0)
882 buffer[len] = '\0';
883 dest_path = buffer;
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);
892 sub++;
894 link = type_ask_which_action(media, sub);
895 g_free(media);
896 if (!link)
897 return;
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,
906 gint x,
907 gint y,
908 GtkSelectionData *selection_data,
909 guint info,
910 guint32 time,
911 FilerWindow *filer_window)
913 GSList *uris, *next;
914 gint dx, dy;
916 if (!selection_data->data)
918 /* Timeout? */
919 return;
922 gdk_window_get_position(widget->window, &dx, &dy);
923 x += dx;
924 y += dy;
926 uris = uri_list_to_gslist(selection_data->data);
928 for (next = uris; next; next = next->next)
930 guchar *path;
932 path = get_local_path((gchar *) next->data);
933 if (path)
935 pinboard_pin(path, NULL, x, y);
936 x += 64;
939 g_free(next->data);
942 if (uris)
943 g_slist_free(uris);
946 /* Called when some data arrives from the remote app (which we asked for
947 * in drag_drop).
949 static void drag_data_received(GtkWidget *widget,
950 GdkDragContext *context,
951 gint x,
952 gint y,
953 GtkSelectionData *selection_data,
954 guint info,
955 guint32 time,
956 gpointer user_data)
958 if (!selection_data->data)
960 /* Timeout? */
961 gtk_drag_finish(context, FALSE, FALSE, time); /* Failure */
962 return;
965 switch (info)
967 case TARGET_XDS:
968 got_data_xds_reply(widget, context,
969 selection_data, time);
970 break;
971 case TARGET_RAW:
972 got_data_raw(widget, context, selection_data, time);
973 break;
974 case TARGET_URI_LIST:
975 got_uri_list(widget, context, selection_data, time);
976 break;
977 case TARGET_RUN_ACTION:
978 got_run_action(widget, context, selection_data, time);
979 break;
980 default:
981 gtk_drag_finish(context, FALSE, FALSE, time);
982 delayed_error("drag_data_received",
983 _("Unknown target"));
984 break;
988 static void got_data_xds_reply(GtkWidget *widget,
989 GdkDragContext *context,
990 GtkSelectionData *selection_data,
991 guint32 time)
993 gboolean mark_unsafe = TRUE;
994 char response = *selection_data->data;
995 char *error = NULL;
996 char *dest_path;
998 dest_path = g_dataset_get_data(context, "drop_dest_path");
1000 if (selection_data->length != 1)
1001 response = '?';
1003 if (response == 'F')
1005 /* Sender couldn't save there - ask for another
1006 * type if possible.
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);
1015 else
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 */
1034 if (mark_unsafe)
1036 set_xds_prop(context, "");
1037 /* Unsave also implies that the drag failed */
1038 gtk_drag_finish(context, FALSE, FALSE, time);
1041 if (error)
1042 delayed_error(PROJECT, error);
1045 static void got_data_raw(GtkWidget *widget,
1046 GdkDragContext *context,
1047 GtkSelectionData *selection_data,
1048 guint32 time)
1050 char *leafname;
1051 int fd;
1052 char *error = NULL;
1053 char *dest_path;
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! */
1065 return;
1068 leafname = g_dataset_get_data(context, "leafname");
1069 if (!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);
1077 if (fd == -1)
1078 error = g_strerror(errno);
1079 else
1081 if (write(fd,
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);
1092 if (error)
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);
1099 else
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,
1111 guint32 time)
1113 GSList *uri_list;
1114 char *error = NULL;
1115 GSList *next_uri;
1116 gboolean send_reply = TRUE;
1117 char *dest_path;
1118 char *type;
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);
1127 if (!uri_list)
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))
1139 char *leaf;
1140 leaf = strrchr(uri_list->data, '/');
1141 if (leaf)
1142 leaf++;
1143 else
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);
1149 send_reply = FALSE;
1151 else
1152 error = _("Can't get data from remote machine "
1153 "(application/octet-stream not provided)");
1155 else
1157 GSList *local_paths = NULL;
1158 GSList *next;
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)
1166 char *path;
1168 path = get_local_path((char *) next_uri->data);
1170 if (path)
1171 local_paths = g_slist_append(local_paths,
1172 g_strdup(path));
1173 else
1174 error = _("Some of these files are on a "
1175 "different machine - they will be "
1176 "ignored - sorry");
1179 if (!local_paths)
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);
1191 else
1192 error = _("Unknown action requested");
1194 for (next = local_paths; next; next = next->next)
1195 g_free(next->data);
1196 g_slist_free(local_paths);
1199 if (error)
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;
1208 while (next_uri)
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
1220 * file over them...
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);
1235 if (!o_spring_open)
1236 return;
1238 if (spring_context)
1239 dnd_spring_abort();
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)
1249 return;
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)
1261 int p_x, p_y;
1263 if (!spring_window)
1264 return FALSE;
1266 if (!get_pointer_xy(&p_x, &p_y))
1269 GdkWindow *win = spring_window->window->window;
1270 int x, y;
1271 int w, h;
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)
1289 guchar *dest_path;
1290 gint x, y;
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)
1304 if (spring_window)
1305 gtk_widget_destroy(spring_window->window);
1308 get_pointer_xy(&x, &y);
1310 if (spring_window)
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
1315 * notice :-(
1318 else
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);
1324 if (spring_window)
1325 centre_window(spring_window->window->window, x, y);
1328 dnd_spring_abort();
1330 return FALSE;
1333 static void spring_win_destroyed(GtkWidget *widget, gpointer data)
1335 spring_window = NULL;