Converted README to markdown
[rox-filer.git] / ROX-Filer / src / dnd.c
blob7d2d602337c41e3d4838469c2915439c93451d3f
1 /*
2 * ROX-Filer, filer for the ROX desktop project
3 * Copyright (C) 2006, Thomas Leonard and others (see changelog for details).
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License as published by the Free
7 * Software Foundation; either version 2 of the License, or (at your option)
8 * any later version.
10 * This program is distributed in the hope that it will be useful, but WITHOUT
11 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
13 * more details.
15 * You should have received a copy of the GNU General Public License along with
16 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
17 * Place, Suite 330, Boston, MA 02111-1307 USA
20 /* dnd.c - code for handling drag and drop */
22 #include "config.h"
24 #include <stdlib.h>
25 #include <stdio.h>
26 #include <fcntl.h>
27 #include <errno.h>
28 #include <string.h>
29 #include <sys/param.h>
31 #include <X11/Xlib.h>
32 #include <X11/Xatom.h>
33 #include <gtk/gtk.h>
34 #include <gdk/gdkx.h>
36 #include "global.h"
38 #include "view_iface.h"
39 #include "dnd.h"
40 #include "type.h"
41 #include "filer.h"
42 #include "action.h"
43 #include "pixmaps.h"
44 #include "gui_support.h"
45 #include "support.h"
46 #include "options.h"
47 #include "run.h"
48 #include "pinboard.h"
49 #include "dir.h"
50 #include "diritem.h"
51 #include "usericons.h"
52 #include "menu.h"
53 #include "bookmarks.h"
55 #define MAXURILEN 4096 /* Longest URI to allow */
57 gint drag_start_x, drag_start_y;
58 MotionType motion_state = MOTION_NONE;
60 static GList *prompt_local_paths = NULL;
61 static gchar *prompt_dest_path = NULL;
63 /* This keeps track of how many mouse buttons are currently down.
64 * We add a grab when it does 0->1 and release it on 1<-0.
66 * It may also be set to zero to disable the motion system (eg,
67 * when popping up a menu).
69 gint motion_buttons_pressed = 0;
71 /* Static prototypes */
72 static void set_xds_prop(GdkDragContext *context, const char *text);
73 static void desktop_drag_data_received(GtkWidget *widget,
74 GdkDragContext *context,
75 gint x,
76 gint y,
77 GtkSelectionData *selection_data,
78 guint info,
79 guint32 time,
80 FilerWindow *filer_window);
81 static void got_data_xds_reply(GtkWidget *widget,
82 GdkDragContext *context,
83 GtkSelectionData *selection_data,
84 guint32 time);
85 static void got_data_raw(GtkWidget *widget,
86 GdkDragContext *context,
87 GtkSelectionData *selection_data,
88 guint32 time);
89 static void got_uri_list(GtkWidget *widget,
90 GdkDragContext *context,
91 const char *selection_data,
92 guint32 time);
93 static gboolean drag_drop(GtkWidget *widget,
94 GdkDragContext *context,
95 gint x,
96 gint y,
97 guint time,
98 gpointer data);
99 static void drag_data_received(GtkWidget *widget,
100 GdkDragContext *context,
101 gint x,
102 gint y,
103 GtkSelectionData *selection_data,
104 guint info,
105 guint32 time,
106 gpointer user_data);
107 static gboolean spring_now(gpointer data);
108 static void spring_win_destroyed(GtkWidget *widget, gpointer data);
109 static void menuitem_response(gpointer data, guint action, GtkWidget *widget);
110 static void prompt_action(GList *paths, gchar *dest);
112 typedef enum {
113 MENU_COPY,
114 MENU_MOVE,
115 MENU_LINK_REL,
116 MENU_LINK_ABS,
117 } MenuActionType;
119 #undef N_
120 #define N_(x) x
121 static GtkItemFactoryEntry menu_def[] = {
122 {N_("Copy"), NULL, menuitem_response, MENU_COPY, NULL},
123 {N_("Move"), NULL, menuitem_response, MENU_MOVE, NULL},
124 {N_("Link (relative)"), NULL, menuitem_response, MENU_LINK_REL, NULL},
125 {N_("Link (absolute)"), NULL, menuitem_response, MENU_LINK_ABS, NULL},
127 static GtkWidget *dnd_menu = NULL;
129 /* Possible values for drop_dest_type (can also be NULL).
130 * In either case, drop_dest_path is the app/file/dir to use.
132 const char *drop_dest_prog = "drop_dest_prog"; /* Run a program */
133 const char *drop_dest_dir = "drop_dest_dir"; /* Save to path */
134 const char *drop_dest_pass_through = "drop_dest_pass"; /* Pass to parent */
135 const char *drop_dest_bookmark = "drop_dest_bookmark"; /* Add to bookmarks */
137 GdkAtom XdndDirectSave0;
138 GdkAtom xa_text_plain;
139 GdkAtom text_uri_list;
140 GdkAtom text_x_moz_url;
141 GdkAtom xa_application_octet_stream;
142 GdkAtom xa_string; /* Not actually used for DnD, but the others are here! */
144 int spring_in_progress = 0; /* Non-zero changes filer_opendir slightly */
146 Option o_dnd_drag_to_icons;
147 Option o_dnd_spring_open;
148 static Option o_dnd_spring_delay;
149 static Option o_dnd_middle_menu;
150 Option o_dnd_left_menu;
151 static Option o_dnd_uri_handler;
153 void dnd_init(void)
155 XdndDirectSave0 = gdk_atom_intern("XdndDirectSave0", FALSE);
156 xa_text_plain = gdk_atom_intern("text/plain", FALSE);
157 text_uri_list = gdk_atom_intern("text/uri-list", FALSE);
158 text_x_moz_url = gdk_atom_intern("text/x-moz-url", FALSE);
159 xa_application_octet_stream = gdk_atom_intern("application/octet-stream",
160 FALSE);
161 xa_string = gdk_atom_intern("STRING", FALSE);
163 option_add_int(&o_dnd_drag_to_icons, "dnd_drag_to_icons", 1);
164 option_add_int(&o_dnd_spring_open, "dnd_spring_open", 0);
165 option_add_int(&o_dnd_spring_delay, "dnd_spring_delay", 400);
166 option_add_int(&o_dnd_left_menu, "dnd_left_menu", TRUE);
167 option_add_int(&o_dnd_middle_menu, "dnd_middle_menu", TRUE);
169 option_add_string(&o_dnd_uri_handler, "dnd_uri_handler",
170 "xterm -e wget $1");
173 /* SUPPORT FUNCTIONS */
175 /* Set the XdndDirectSave0 property on the source window for this context */
176 static void set_xds_prop(GdkDragContext *context, const char *text)
178 gdk_property_change(context->source_window,
179 XdndDirectSave0,
180 xa_text_plain, 8,
181 GDK_PROP_MODE_REPLACE,
182 text,
183 strlen(text));
186 static char *get_xds_prop(GdkDragContext *context)
188 guchar *prop_text;
189 gint length;
191 if (gdk_property_get(context->source_window,
192 XdndDirectSave0,
193 xa_text_plain,
194 0, MAXURILEN,
195 FALSE,
196 NULL, NULL,
197 &length, &prop_text) && prop_text)
199 /* Terminate the string */
200 prop_text = g_realloc(prop_text, length + 1);
201 prop_text[length] = '\0';
202 /* Note: assuming UTF-8 (should convert here) */
203 return prop_text;
206 return NULL;
209 /* Is the sender willing to supply this target type? */
210 gboolean provides(GdkDragContext *context, GdkAtom target)
212 GList *targets = context->targets;
214 while (targets && ((GdkAtom) targets->data != target))
215 targets = targets->next;
217 return targets != NULL;
220 /* DRAGGING FROM US */
222 /* The user has held the mouse button down over a group of item and moved -
223 * start a drag. 'uri_list' is copied, so you can delete it straight away.
225 void drag_selection(GtkWidget *widget, GdkEventMotion *event, guchar *uri_list)
227 GdkPixbuf *pixbuf;
228 GdkDragContext *context;
229 GdkDragAction actions;
230 GtkTargetList *target_list;
231 GtkTargetEntry target_table[] = {
232 {"text/uri-list", 0, TARGET_URI_LIST},
233 {"UTF8_STRING", 0, TARGET_UTF8},
236 if (event->state & GDK_BUTTON1_MASK)
237 actions = GDK_ACTION_COPY | GDK_ACTION_MOVE
238 | GDK_ACTION_LINK | GDK_ACTION_ASK;
239 else
241 if (o_dnd_middle_menu.int_value)
242 actions = GDK_ACTION_ASK;
243 else
244 actions = GDK_ACTION_MOVE;
247 target_list = gtk_target_list_new(target_table,
248 G_N_ELEMENTS(target_table));
250 context = gtk_drag_begin(widget,
251 target_list,
252 actions,
253 (event->state & GDK_BUTTON1_MASK) ? 1 :
254 (event->state & GDK_BUTTON2_MASK) ? 2 : 3,
255 (GdkEvent *) event);
257 g_dataset_set_data_full(context, "uri_list",
258 g_strdup(uri_list), g_free);
260 pixbuf = gtk_widget_render_icon(widget, GTK_STOCK_DND_MULTIPLE,
261 GTK_ICON_SIZE_DIALOG, NULL);
262 gtk_drag_set_icon_pixbuf(context, pixbuf, 0, 0);
263 g_object_unref(pixbuf);
266 /* Copy/Load this item into another directory/application */
267 void drag_one_item(GtkWidget *widget,
268 GdkEventMotion *event,
269 const guchar *full_path,
270 DirItem *item,
271 MaskedPixmap *image)
273 guchar *uri, *tmp;
274 GdkDragContext *context;
275 GdkDragAction actions;
276 GtkTargetList *target_list;
277 GtkTargetEntry target_table[] = {
278 {"text/uri-list", 0, TARGET_URI_LIST},
279 {"UTF8_STRING", 0, TARGET_UTF8},
280 {"application/octet-stream", 0, TARGET_RAW},
281 {"", 0, TARGET_RAW},
284 g_return_if_fail(full_path != NULL);
285 g_return_if_fail(item != NULL);
287 if (!image)
288 image = di_image(item);
290 if (item->base_type == TYPE_FILE)
292 MIME_type *t = item->mime_type;
294 target_table[3].target = g_strconcat(t->media_type, "/",
295 t->subtype, NULL);
296 target_list = gtk_target_list_new(target_table,
297 G_N_ELEMENTS(target_table));
298 g_free(target_table[3].target);
300 else
301 target_list = gtk_target_list_new(target_table, 2);
303 if (event->state & GDK_BUTTON1_MASK)
304 actions = GDK_ACTION_COPY | GDK_ACTION_ASK
305 | GDK_ACTION_MOVE | GDK_ACTION_LINK;
306 else
308 if (o_dnd_middle_menu.int_value)
309 actions = GDK_ACTION_ASK;
310 else
311 actions = GDK_ACTION_MOVE;
314 context = gtk_drag_begin(widget,
315 target_list,
316 actions,
317 (event->state & GDK_BUTTON1_MASK) ? 1 :
318 (event->state & GDK_BUTTON2_MASK) ? 2 : 3,
319 (GdkEvent *) event);
321 g_dataset_set_data_full(context, "full_path",
322 g_strdup(full_path), g_free);
323 tmp = (char *) encode_path_as_uri(full_path);
324 uri = g_strconcat(tmp, "\r\n", NULL);
325 /*printf("%s\n", tmp);*/
326 g_free(tmp);
327 g_dataset_set_data_full(context, "uri_list", uri, g_free);
329 g_return_if_fail(image != NULL);
331 gtk_drag_set_icon_pixbuf(context, image->pixbuf, 0, 0);
334 /* Convert text/uri-list data to UTF8_STRING.
335 * g_free() the result.
337 static gchar *uri_list_to_utf8(const char *uri_list)
339 GString *new;
340 GList *uris, *next_uri;
341 char *string;
343 new = g_string_new(NULL);
345 uris = uri_list_to_glist(uri_list);
347 for (next_uri = uris; next_uri; next_uri = next_uri->next)
349 EscapedPath *uri = next_uri->data;
350 char *local;
352 local = get_local_path(uri);
354 if (new->len)
355 g_string_append_c(new, ' ');
357 if (local)
359 g_string_append(new, local);
360 g_free(local);
362 else
363 g_warning("Not local!\n");
365 g_free(uri);
368 if (uris)
369 g_list_free(uris);
371 string = new->str;
372 g_string_free(new, FALSE);
374 return string;
377 /* Called when a remote app wants us to send it some data.
378 * TODO: Maybe we should handle errors better (ie, let the remote app know
379 * the drag has failed)?
381 void drag_data_get(GtkWidget *widget,
382 GdkDragContext *context,
383 GtkSelectionData *selection_data,
384 guint info,
385 guint32 time,
386 gpointer data)
388 char *to_send = "E"; /* Default to sending an error */
389 long to_send_length = 1;
390 gboolean delete_once_sent = FALSE;
391 GdkAtom type;
392 guchar *path;
394 type = selection_data->target;
396 switch (info)
398 case TARGET_RAW:
399 path = g_dataset_get_data(context, "full_path");
400 if (path && load_file(path, &to_send, &to_send_length))
402 delete_once_sent = TRUE;
403 break;
405 g_warning("drag_data_get: Can't find path!\n");
406 return;
407 case TARGET_UTF8:
409 char *uri_list;
410 uri_list = g_dataset_get_data(context, "uri_list");
411 to_send = uri_list_to_utf8(uri_list);
412 to_send_length = strlen(to_send);
413 delete_once_sent = TRUE;
414 break;
416 case TARGET_URI_LIST:
417 to_send = g_dataset_get_data(context, "uri_list");
418 to_send_length = strlen(to_send);
419 type = text_uri_list; /* (needed for xine) */
420 delete_once_sent = FALSE;
421 break;
422 default:
423 delayed_error("drag_data_get: %s",
424 _("Internal error - bad info type"));
425 break;
428 gtk_selection_data_set(selection_data,
429 type,
431 to_send,
432 to_send_length);
434 if (delete_once_sent)
435 g_free(to_send);
438 /* DRAGGING TO US */
440 /* Set up this widget as a drop-target.
441 * Does not attach any motion handlers.
443 void make_drop_target(GtkWidget *widget, GtkDestDefaults defaults)
445 GtkTargetEntry target_table[] =
447 {"text/uri-list", 0, TARGET_URI_LIST},
448 {"text/x-moz-url", 0, TARGET_MOZ_URL},
449 {"XdndDirectSave0", 0, TARGET_XDS},
450 {"application/octet-stream", 0, TARGET_RAW},
453 gtk_drag_dest_set(widget,
454 defaults,
455 target_table,
456 sizeof(target_table) / sizeof(*target_table),
457 GDK_ACTION_COPY | GDK_ACTION_ASK | GDK_ACTION_MOVE
458 | GDK_ACTION_LINK | GDK_ACTION_PRIVATE);
460 g_signal_connect(widget, "drag_drop", G_CALLBACK(drag_drop), NULL);
461 g_signal_connect(widget, "drag_data_received",
462 G_CALLBACK(drag_data_received), NULL);
465 /* Like drag_set_dest, but for a pinboard-type widget */
466 void drag_set_pinboard_dest(GtkWidget *widget)
468 GtkTargetEntry target_table[] = {
469 {"text/uri-list", 0, TARGET_URI_LIST},
472 gtk_drag_dest_set(widget,
473 GTK_DEST_DEFAULT_DROP,
474 target_table,
475 sizeof(target_table) / sizeof(*target_table),
476 GDK_ACTION_LINK);
477 g_signal_connect(widget, "drag_data_received",
478 G_CALLBACK(desktop_drag_data_received), NULL);
481 /* item is the item the file is held over, NULL for directory background.
482 * 'item' may be NULL on exit if the drop should be treated as onto the
483 * background. Disallow drags to a selected icon before calling this.
485 * Returns NULL to reject the drop, or drop_dest_prog/drop_dest_dir to
486 * accept. Build the path based on item.
488 const guchar *dnd_motion_item(GdkDragContext *context, DirItem **item_p)
490 DirItem *item = *item_p;
492 if (item)
494 /* If we didn't drop onto a directory, application or
495 * executable file then act as though the drop is to the
496 * window background.
498 if (item->base_type != TYPE_DIRECTORY && !EXECUTABLE_FILE(item))
500 item = NULL;
501 *item_p = NULL;
505 if (!item)
507 /* Drop onto the window background */
509 return drop_dest_dir;
512 /* Drop onto a program/directory of some sort */
514 if (item->base_type == TYPE_DIRECTORY &&
515 !(item->flags & ITEM_FLAG_APPDIR))
517 /* A normal directory */
518 if (provides(context, text_uri_list) ||
519 provides(context, text_x_moz_url) ||
520 provides(context, XdndDirectSave0))
521 return drop_dest_dir;
523 else
525 if (provides(context, text_uri_list) ||
526 provides(context, text_x_moz_url) ||
527 provides(context, xa_application_octet_stream))
528 return drop_dest_prog;
531 return NULL;
534 /* User has tried to drop some data on us. Decide what format we would
535 * like the data in.
537 static gboolean drag_drop(GtkWidget *widget,
538 GdkDragContext *context,
539 gint x,
540 gint y,
541 guint time,
542 gpointer data)
544 const char *error = NULL;
545 char *leafname = NULL;
546 GdkAtom target = GDK_NONE;
547 char *dest_path;
548 char *dest_type = NULL;
550 dest_path = g_dataset_get_data(context, "drop_dest_path");
551 dest_type = g_dataset_get_data(context, "drop_dest_type");
553 if (dest_type == drop_dest_pass_through)
554 return FALSE; /* Let the parent widget handle it */
556 if (dest_type == drop_dest_bookmark)
558 if (provides(context, text_uri_list))
559 gtk_drag_get_data(widget, context, text_uri_list, time);
560 else
562 gtk_drag_finish(context, FALSE, FALSE, time);
563 delayed_error(_("Drag a directory here to "
564 "bookmark it."));
566 return TRUE;
569 g_return_val_if_fail(dest_path != NULL, TRUE);
571 if (dest_type == drop_dest_dir && provides(context, XdndDirectSave0))
573 leafname = get_xds_prop(context);
574 if (leafname)
576 if (strchr(leafname, '/'))
578 error = _("XDS protocol error: "
579 "leafname may not contain '/'\n");
580 null_g_free(&leafname);
582 else
584 char *dest_uri;
586 /* Not escaped. */
587 dest_uri = g_strconcat("file://",
588 our_host_name_for_dnd(),
589 dest_path, NULL);
591 set_xds_prop(context,
592 make_path(dest_uri, leafname));
594 g_free(dest_uri);
596 target = XdndDirectSave0;
597 g_dataset_set_data_full(context, "leafname",
598 leafname, g_free);
601 else
602 error = _(
603 "XdndDirectSave0 target provided, but the atom "
604 "XdndDirectSave0 (type text/plain) did not "
605 "contain a leafname\n");
607 else if (provides(context, text_uri_list))
608 target = text_uri_list;
609 else if (provides(context, text_x_moz_url))
610 target = text_x_moz_url;
611 else if (provides(context, xa_application_octet_stream))
612 target = xa_application_octet_stream;
613 else
615 if (dest_type == drop_dest_dir)
616 error = _("Sorry - I require a target type of "
617 "text/uri-list or XdndDirectSave0.");
618 else
619 error = _("Sorry - I require a target type of "
620 "text/uri-list or application/octet-stream.");
623 if (error)
625 gtk_drag_finish(context, FALSE, FALSE, time); /* Failure */
627 delayed_error("%s", error);
629 else
630 gtk_drag_get_data(widget, context, target, time);
632 return TRUE;
635 /* Called when a text/uri-list arrives */
636 static void desktop_drag_data_received(GtkWidget *widget,
637 GdkDragContext *context,
638 gint x,
639 gint y,
640 GtkSelectionData *selection_data,
641 guint info,
642 guint32 time,
643 FilerWindow *filer_window)
645 GList *uris, *next;
646 char *error_example = NULL;
647 gint dx, dy;
649 if (!selection_data->data)
651 /* Timeout? */
652 return;
655 if (pinboard_drag_in_progress)
657 pinboard_move_icons();
658 return;
661 gdk_window_get_position(widget->window, &dx, &dy);
662 x += dx;
663 y += dy;
665 uris = uri_list_to_glist(selection_data->data);
667 for (next = uris; next; next = next->next)
669 guchar *path;
671 path = get_local_path((EscapedPath *) next->data);
672 if (path)
674 pinboard_pin(path, NULL, x, y, NULL);
675 x += 64;
676 g_free(path);
678 else if (!error_example)
679 error_example = g_strdup(next->data);
681 g_free(next->data);
684 if (uris)
685 g_list_free(uris);
687 if (error_example)
689 delayed_error(_("Failed to add some items to the pinboard, "
690 "because they are on a remote machine. For example:\n"
691 "\n%s"), error_example);
692 g_free(error_example);
696 /* Convert Mozilla's text/x-moz-uri into a text/uri-list */
697 static void got_moz_uri(GtkWidget *widget,
698 GdkDragContext *context,
699 GtkSelectionData *selection_data,
700 guint32 time)
702 gchar *utf8, *uri_list, *eol;
704 utf8 = g_utf16_to_utf8((gunichar2 *) selection_data->data,
705 (glong) selection_data->length,
706 NULL, NULL, NULL);
708 eol = utf8 ? strchr(utf8, '\n') : NULL;
709 if (!eol)
711 delayed_error("Invalid UTF16 from text/x-moz-url target");
712 g_free(utf8);
713 gtk_drag_finish(context, FALSE, FALSE, time);
714 return;
717 *eol = '\0';
718 uri_list = g_strconcat(utf8, "\r\n", NULL);
719 g_free(utf8);
721 got_uri_list(widget, context, uri_list, time);
723 g_free(uri_list);
726 /* Called when some data arrives from the remote app (which we asked for
727 * in drag_drop).
729 static void drag_data_received(GtkWidget *widget,
730 GdkDragContext *context,
731 gint x,
732 gint y,
733 GtkSelectionData *selection_data,
734 guint info,
735 guint32 time,
736 gpointer user_data)
738 if (!selection_data->data)
740 /* Timeout? */
741 gtk_drag_finish(context, FALSE, FALSE, time); /* Failure */
742 return;
745 switch (info)
747 case TARGET_XDS:
748 got_data_xds_reply(widget, context,
749 selection_data, time);
750 break;
751 case TARGET_RAW:
752 got_data_raw(widget, context, selection_data, time);
753 break;
754 case TARGET_URI_LIST:
755 got_uri_list(widget, context, selection_data->data,
756 time);
757 break;
758 case TARGET_MOZ_URL:
759 got_moz_uri(widget, context, selection_data, time);
760 break;
761 default:
762 gtk_drag_finish(context, FALSE, FALSE, time);
763 delayed_error("drag_data_received: %s",
764 _("Unknown target"));
765 break;
769 static void got_data_xds_reply(GtkWidget *widget,
770 GdkDragContext *context,
771 GtkSelectionData *selection_data,
772 guint32 time)
774 gboolean mark_unsafe = TRUE;
775 char response = *selection_data->data;
776 const char *error = NULL;
777 char *dest_path;
779 dest_path = g_dataset_get_data(context, "drop_dest_path");
781 if (selection_data->length != 1)
782 response = '?';
784 if (response == 'F')
786 /* Sender couldn't save there - ask for another
787 * type if possible.
789 if (provides(context, xa_application_octet_stream))
791 mark_unsafe = FALSE; /* Wait and see */
793 gtk_drag_get_data(widget, context,
794 xa_application_octet_stream, time);
796 else
797 error = _("Remote app can't or won't send me "
798 "the data - sorry");
800 else if (response == 'S')
802 /* Success - data is saved */
803 mark_unsafe = FALSE; /* It really is safe */
804 gtk_drag_finish(context, TRUE, FALSE, time);
806 refresh_dirs(dest_path);
808 else if (response != 'E')
810 error = _("XDS protocol error: "
811 "return code should be 'S', 'F' or 'E'\n");
813 /* else: error has been reported by the sender */
815 if (mark_unsafe)
817 set_xds_prop(context, "");
818 /* Unsave also implies that the drag failed */
819 gtk_drag_finish(context, FALSE, FALSE, time);
822 if (error)
823 delayed_error("%s", error);
826 static void got_data_raw(GtkWidget *widget,
827 GdkDragContext *context,
828 GtkSelectionData *selection_data,
829 guint32 time)
831 const char *leafname;
832 int fd;
833 const char *error = NULL;
834 const char *dest_path;
836 g_return_if_fail(selection_data->data != NULL);
838 dest_path = g_dataset_get_data(context, "drop_dest_path");
840 if (context->action == GDK_ACTION_ASK)
842 gtk_drag_finish(context, FALSE, FALSE, time); /* Failure */
843 delayed_error(_("Sorry, can't display a menu of actions "
844 "for a remote file / raw data."));
845 return;
848 if (g_dataset_get_data(context, "drop_dest_type") == drop_dest_prog)
850 /* The data needs to be sent to an application */
851 run_with_data(dest_path,
852 selection_data->data, selection_data->length);
853 gtk_drag_finish(context, TRUE, FALSE, time); /* Success! */
854 return;
857 leafname = g_dataset_get_data(context, "leafname");
858 if (!leafname)
859 leafname = _("UntitledData");
861 fd = open(make_path(dest_path, leafname),
862 O_WRONLY | O_CREAT | O_EXCL | O_NOCTTY,
863 S_IRUSR | S_IRGRP | S_IROTH |
864 S_IWUSR | S_IWGRP | S_IWOTH);
866 if (fd == -1)
867 error = g_strerror(errno);
868 else
870 if (write(fd,
871 selection_data->data,
872 selection_data->length) == -1)
873 error = g_strerror(errno);
875 if (close(fd) == -1 && !error)
876 error = g_strerror(errno);
878 refresh_dirs(dest_path);
881 if (error)
883 if (provides(context, XdndDirectSave0))
884 set_xds_prop(context, "");
885 gtk_drag_finish(context, FALSE, FALSE, time); /* Failure */
886 delayed_error(_("Error saving file: %s"), error);
888 else
889 gtk_drag_finish(context, TRUE, FALSE, time); /* Success! */
892 static gboolean uri_is_local(const EscapedPath *uri)
894 char *path;
895 path = get_local_path(uri);
896 if (!path)
897 return FALSE;
898 g_free(path);
899 return TRUE;
902 /* Run the shell command 'command', replacing $1 with 'arg' */
903 static void run_with_argument(const char *dir,
904 const char *command,
905 const char *arg)
907 GPtrArray *argv;
909 argv = g_ptr_array_new();
911 g_ptr_array_add(argv, "sh");
912 g_ptr_array_add(argv, "-c");
913 g_ptr_array_add(argv, (char *) command);
914 g_ptr_array_add(argv, "sh");
915 g_ptr_array_add(argv, (char *) arg);
916 g_ptr_array_add(argv, NULL);
918 rox_spawn(dir, (const gchar **) argv->pdata);
920 g_ptr_array_free(argv, TRUE);
923 /* We've got a list of URIs from somewhere (probably another filer window).
924 * If the files are on the local machine then try to copy them ourselves,
925 * otherwise, if there was only one file and application/octet-stream was
926 * provided, get the data via the X server.
927 * For http:, https: or ftp: schemes, use the download handler.
929 static void got_uri_list(GtkWidget *widget,
930 GdkDragContext *context,
931 const char *selection_data,
932 guint32 time)
934 GList *uri_list;
935 const char *error = NULL;
936 GList *next_uri;
937 gboolean send_reply = TRUE;
938 char *dest_path;
939 char *type;
941 dest_path = g_dataset_get_data(context, "drop_dest_path");
942 type = g_dataset_get_data(context, "drop_dest_type");
944 uri_list = uri_list_to_glist(selection_data);
946 if (type == drop_dest_bookmark)
948 GList *next;
949 for (next = uri_list; next; next = next->next)
950 bookmarks_add_uri((EscapedPath *) next->data);
951 destroy_glist(&uri_list);
952 gtk_drag_finish(context, TRUE, FALSE, time); /* Success! */
953 return;
956 g_return_if_fail(dest_path != NULL);
958 if (!uri_list)
959 error = _("No URIs in the text/uri-list (nothing to do!)");
960 else if (context->action != GDK_ACTION_ASK && type == drop_dest_prog)
961 run_with_files(dest_path, uri_list);
962 else if ((!uri_list->next) && !uri_is_local(uri_list->data))
964 /* There is one URI in the list, and it's not on the local
965 * machine. Get it via the X server if possible.
968 if (provides(context, xa_application_octet_stream))
970 char *leaf;
971 leaf = strrchr(uri_list->data, '/');
972 if (leaf)
973 leaf++;
974 else
975 leaf = uri_list->data;
976 g_dataset_set_data_full(context, "leafname",
977 unescape_uri((EscapedPath *) leaf), g_free);
978 gtk_drag_get_data(widget, context,
979 xa_application_octet_stream, time);
980 send_reply = FALSE;
982 else if ((strncasecmp(uri_list->data, "http:", 5) == 0) ||
983 (strncasecmp(uri_list->data, "https:", 6) == 0) ||
984 (strncasecmp(uri_list->data, "ftp:", 4) == 0))
986 run_with_argument(dest_path,
987 o_dnd_uri_handler.value,
988 (char *) uri_list->data);
990 else
991 error = _("Can't get data from remote machine "
992 "(application/octet-stream not provided)");
994 else
996 GList *local_paths = NULL;
998 /* Either one local URI, or a list. If everything in the list
999 * isn't local then we are stuck.
1002 for (next_uri = uri_list; next_uri; next_uri = next_uri->next)
1004 char *path;
1006 path = get_local_path((EscapedPath *) next_uri->data);
1007 /*printf("%s -> %s\n", (char *) next_uri->data,
1008 path? path: "NULL");*/
1010 if (path)
1011 local_paths = g_list_append(local_paths,
1012 path);
1013 else
1014 error = _("Some of these files are on a "
1015 "different machine - they will be "
1016 "ignored - sorry");
1019 if (!local_paths)
1021 error = _("None of these files are on the local "
1022 "machine - I can't operate on multiple "
1023 "remote files - sorry.");
1025 else if (context->action == GDK_ACTION_ASK)
1026 prompt_action(local_paths, dest_path);
1027 else if (context->action == GDK_ACTION_MOVE)
1028 action_move(local_paths, dest_path, NULL, -1);
1029 else if (context->action == GDK_ACTION_COPY)
1030 action_copy(local_paths, dest_path, NULL, -1);
1031 else if (context->action == GDK_ACTION_LINK)
1032 action_link(local_paths, dest_path, NULL, TRUE);
1033 else
1034 error = _("Unknown action requested");
1036 destroy_glist(&local_paths);
1039 if (error)
1041 gtk_drag_finish(context, FALSE, FALSE, time); /* Failure */
1042 delayed_error(_("Error getting file list: %s"), error);
1044 else if (send_reply)
1045 gtk_drag_finish(context, TRUE, FALSE, time); /* Success! */
1047 destroy_glist(&uri_list);
1050 /* Called when an item from the ACTION_ASK menu is chosen */
1051 static void menuitem_response(gpointer data, guint action, GtkWidget *widget)
1053 if (action == MENU_MOVE)
1054 action_move(prompt_local_paths, prompt_dest_path, NULL, -1);
1055 else if (action == MENU_COPY)
1056 action_copy(prompt_local_paths, prompt_dest_path, NULL, -1);
1057 else if (action == MENU_LINK_REL)
1058 action_link(prompt_local_paths, prompt_dest_path, NULL, TRUE);
1059 else if (action == MENU_LINK_ABS)
1060 action_link(prompt_local_paths, prompt_dest_path, NULL, FALSE);
1063 /* When some local files are dropped somewhere with ACTION_ASK, this
1064 * function is called to display the menu.
1066 static void prompt_action(GList *paths, gchar *dest)
1068 GList *next;
1069 GdkEvent *event;
1071 if (prompt_local_paths)
1073 destroy_glist(&prompt_local_paths);
1074 null_g_free(&prompt_dest_path);
1077 /* Make a copy of the arguments */
1078 for (next = paths; next; next = next->next)
1079 prompt_local_paths = g_list_append(prompt_local_paths,
1080 g_strdup((gchar *) next->data));
1081 prompt_dest_path = g_strdup(dest);
1083 if (!dnd_menu)
1085 GtkItemFactory *item_factory;
1087 item_factory = menu_create(menu_def,
1088 sizeof(menu_def) / sizeof(*menu_def),
1089 "<dnd>", NULL);
1090 dnd_menu = gtk_item_factory_get_widget(item_factory, "<dnd>");
1093 /* Shade 'Set Icon' if there are multiple files */
1094 menu_set_items_shaded(dnd_menu, g_list_length(paths) != 1, 4, 1);
1096 event = gtk_get_current_event();
1097 show_popup_menu(dnd_menu, event, 1);
1098 if (event)
1099 gdk_event_free(event);
1103 /* SPRING-LOADING */
1105 /* This is the code that makes directories pop open if you hold a
1106 * file over them...
1108 * First, call dnd_spring_load(context) to arm the system.
1109 * After a timeout (1/2 a second) the dest_path directory will be
1110 * opened in a new window, unless dnd_spring_abort is called first.
1113 static gint spring_timeout = -1;
1114 static GdkDragContext *spring_context = NULL;
1115 static FilerWindow *spring_window = NULL;
1116 static FilerWindow *spring_src_window = NULL;
1118 void dnd_spring_load(GdkDragContext *context, FilerWindow *src_win)
1120 g_return_if_fail(context != NULL);
1122 if (!o_dnd_spring_open.int_value)
1123 return;
1125 if (spring_context)
1126 dnd_spring_abort();
1128 spring_context = context;
1129 g_object_ref(spring_context);
1130 spring_src_window = src_win;
1131 spring_timeout = gtk_timeout_add(
1132 o_dnd_spring_delay.int_value, spring_now, NULL);
1135 void dnd_spring_abort(void)
1137 if (!spring_context)
1138 return;
1140 g_object_unref(spring_context);
1141 spring_context = NULL;
1142 gtk_timeout_remove(spring_timeout);
1145 /* If all mod keys are released, no buttons are pressed, and the
1146 * mouse is outside the spring window, then close it.
1148 static gboolean spring_check_idle(gpointer data)
1150 int p_x, p_y;
1152 if (!spring_window)
1153 return FALSE;
1155 if (!get_pointer_xy(&p_x, &p_y))
1158 GdkWindow *win = spring_window->window->window;
1159 int x, y;
1160 int w, h;
1162 gdk_window_get_position(win, &x, &y);
1163 gdk_window_get_size(win, &w, &h);
1165 if (p_x < x || p_x > x + w || p_y < y || p_y > y + h)
1169 gtk_widget_destroy(spring_window->window);
1170 return FALSE; /* Got it! */
1173 return TRUE; /* Try again later */
1176 static gboolean spring_now(gpointer data)
1178 const char *type;
1179 const guchar *dest_path;
1180 gint x, y;
1182 g_return_val_if_fail(spring_context != NULL, FALSE);
1183 g_return_val_if_fail(!spring_in_progress, FALSE);
1185 type = g_dataset_get_data(spring_context, "drop_dest_type");
1186 if (type == drop_dest_bookmark)
1188 bookmarks_edit();
1189 goto out;
1192 dest_path = g_dataset_get_data(spring_context, "drop_dest_path");
1193 g_return_val_if_fail(dest_path != NULL, FALSE);
1196 * Note: Due to a bug in gtk, if a window disappears during
1197 * a drag and the pointer moves over where the window was,
1198 * the sender crashes! Therefore, do not close any windows
1199 * while dragging! (fixed in later versions)
1202 if (spring_window)
1203 gtk_widget_destroy(spring_window->window);
1206 get_pointer_xy(&x, &y);
1208 spring_in_progress++;
1209 if (spring_window)
1211 view_cursor_to_iter(spring_window->view, NULL);
1212 filer_change_to(spring_window, dest_path, NULL);
1213 /* DON'T move the window. Gtk+ sometimes doesn't
1214 * notice :-(
1217 else
1219 spring_window = filer_opendir(dest_path,
1220 spring_src_window, NULL);
1221 if (spring_window)
1223 gtk_timeout_add(500, spring_check_idle, NULL);
1224 g_signal_connect(spring_window->window, "destroy",
1225 G_CALLBACK(spring_win_destroyed), NULL);
1226 centre_window(spring_window->window->window, x, y);
1229 spring_in_progress--;
1231 out:
1232 dnd_spring_abort();
1234 return FALSE;
1237 static void spring_win_destroyed(GtkWidget *widget, gpointer data)
1239 spring_window = NULL;
1242 /* HANDLING MOTION EVENTS */
1244 /* If not-NULL, then this widget has a grab */
1245 static GtkWidget *motion_widget = NULL;
1247 /* If TRUE, we must gdk_pointer_ungrab() too when finishing */
1248 static gboolean motion_pointer_grab = FALSE;
1250 /* Call this on a button press event. It stores the mouse position
1251 * as the start of the new drag and returns TRUE if all is well.
1252 * Further motions events are disabled at this point - you must
1253 * then call dnd_motion_start() to set the type of motion expected.
1254 * Grabs the widget on the first press.
1256 * If the system is not ready to handle a motion event (because a
1257 * button is already held down?) it does nothing and returns FALSE.
1259 * If the event is not a single click then it simply returns TRUE.
1261 gboolean dnd_motion_press(GtkWidget *widget, GdkEventButton *event)
1263 if (event->type != GDK_BUTTON_PRESS)
1264 return TRUE; /* Not a click event! */
1266 motion_buttons_pressed++;
1267 if (motion_buttons_pressed == 1)
1269 /* g_print("[ grab! ]\n"); */
1270 gtk_grab_add(widget);
1271 motion_widget = widget;
1274 if (motion_state != MOTION_NONE)
1275 return FALSE; /* Ignore clicks - we're busy! */
1277 motion_state = MOTION_DISABLED;
1278 drag_start_x = event->x_root;
1279 drag_start_y = event->y_root;
1281 return TRUE;
1284 /* After the button press event, decide what kind of motion is expected.
1285 * If you don't call this then the motion system is disabled - call
1286 * dnd_motion_release() to reset it.
1288 * Note: If you open a popup menu or start DND call dnd_motion_ungrab()
1289 * instead.
1291 void dnd_motion_start(MotionType motion)
1293 g_return_if_fail(motion_state == MOTION_DISABLED);
1295 motion_state = motion;
1298 /* Call this on a button release event. If some buttons are still pressed,
1299 * returns TRUE and does nothing.
1301 * Otherwise, it resets the motion system to be ready again and returns TRUE.
1303 * If the motion system wasn't being used (MOTION_NONE) then it does nothing
1304 * and returns FALSE - process the release event yourself as it isn't part
1305 * of a motion. This also happens if a motion was primed but never happened.
1307 gboolean dnd_motion_release(GdkEventButton *event)
1309 MotionType motion = motion_state;
1310 gint drag_threshold;
1311 int dx, dy;
1313 if (motion_buttons_pressed == 0)
1314 return TRUE; /* We were disabled */
1316 if (motion_buttons_pressed == 1)
1317 dnd_motion_ungrab();
1318 else
1320 motion_buttons_pressed--;
1321 return TRUE;
1324 if (motion == MOTION_REPOSITION || motion == MOTION_DISABLED)
1325 return TRUE; /* Already done something - eat the event */
1327 /* Eat release events that happen too far from the click
1328 * source. Otherwise, allow the caller to treat this as a click
1329 * that never became a motion.
1331 dx = event->x_root - drag_start_x;
1332 dy = event->y_root - drag_start_y;
1334 g_object_get(gtk_settings_get_default(),
1335 "gtk-dnd-drag-threshold", &drag_threshold,
1336 NULL);
1338 return ABS(dx) > drag_threshold || ABS(dy) > drag_threshold;
1341 /* Use this to disable the motion system. The system will be reset once
1342 * all mouse buttons are released.
1344 void dnd_motion_disable(void)
1346 g_return_if_fail(motion_state != MOTION_NONE &&
1347 motion_state != MOTION_DISABLED);
1349 motion_state = MOTION_DISABLED;
1352 /* Use this if something else is going to grab the pointer so that
1353 * we won't get any more motion or release events.
1355 void dnd_motion_ungrab(void)
1357 if (motion_buttons_pressed > 0)
1359 if (motion_pointer_grab)
1361 gdk_pointer_ungrab(GDK_CURRENT_TIME);
1362 motion_pointer_grab = FALSE;
1363 /* g_print("[ ungrab_pointer ]\n"); */
1365 gtk_grab_remove(motion_widget);
1366 motion_widget = NULL;
1367 motion_buttons_pressed = 0;
1368 /* g_print("[ ungrab ]\n"); */
1371 motion_state = MOTION_NONE;
1374 /* Call this on motion events. If the mouse position is far enough
1375 * from the click position, returns TRUE and does dnd_motion_ungrab().
1376 * You should then start regular drag-and-drop.
1378 * Otherwise, returns FALSE.
1380 gboolean dnd_motion_moved(GdkEventMotion *event)
1382 gint drag_threshold;
1383 int dx, dy;
1385 g_object_get(gtk_settings_get_default(),
1386 "gtk-dnd-drag-threshold", &drag_threshold,
1387 NULL);
1389 dx = event->x_root - drag_start_x;
1390 dy = event->y_root - drag_start_y;
1392 if (ABS(dx) <= drag_threshold && ABS(dy) <= drag_threshold)
1393 return FALSE; /* Not far enough */
1395 dnd_motion_ungrab();
1397 return TRUE;
1400 /* Normally, the X server will automatically grab the pointer on a
1401 * button press and ungrab on release. However, if the grab widget
1402 * is reparented then call this to re-aquire the grab.
1404 void dnd_motion_grab_pointer(void)
1406 g_return_if_fail(motion_widget != NULL);
1408 gdk_pointer_grab(motion_widget->window, FALSE,
1409 GDK_POINTER_MOTION_MASK |
1410 GDK_BUTTON_RELEASE_MASK,
1411 FALSE, NULL, GDK_CURRENT_TIME);
1413 motion_pointer_grab = TRUE;