r3487: Added 'Download handler' to drag and drop options. This program is invoked
[rox-filer.git] / ROX-Filer / src / dnd.c
blob578474cfa7c573a6771dacb3cbfcfb72d1283d22
1 /*
2 * $Id$
4 * ROX-Filer, filer for the ROX desktop project
5 * Copyright (C) 2003, the ROX-Filer team.
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 <gdk/gdkx.h>
38 #include "global.h"
40 #include "view_iface.h"
41 #include "dnd.h"
42 #include "type.h"
43 #include "filer.h"
44 #include "action.h"
45 #include "pixmaps.h"
46 #include "gui_support.h"
47 #include "support.h"
48 #include "options.h"
49 #include "run.h"
50 #include "pinboard.h"
51 #include "dir.h"
52 #include "diritem.h"
53 #include "usericons.h"
54 #include "menu.h"
55 #include "bookmarks.h"
57 #define MAXURILEN 4096 /* Longest URI to allow */
59 gint drag_start_x, drag_start_y;
60 MotionType motion_state = MOTION_NONE;
62 static GList *prompt_local_paths = NULL;
63 static gchar *prompt_dest_path = NULL;
65 /* This keeps track of how many mouse buttons are currently down.
66 * We add a grab when it does 0->1 and release it on 1<-0.
68 * It may also be set to zero to disable the motion system (eg,
69 * when popping up a menu).
71 gint motion_buttons_pressed = 0;
73 /* Static prototypes */
74 static void set_xds_prop(GdkDragContext *context, const char *text);
75 static void desktop_drag_data_received(GtkWidget *widget,
76 GdkDragContext *context,
77 gint x,
78 gint y,
79 GtkSelectionData *selection_data,
80 guint info,
81 guint32 time,
82 FilerWindow *filer_window);
83 static void got_data_xds_reply(GtkWidget *widget,
84 GdkDragContext *context,
85 GtkSelectionData *selection_data,
86 guint32 time);
87 static void got_data_raw(GtkWidget *widget,
88 GdkDragContext *context,
89 GtkSelectionData *selection_data,
90 guint32 time);
91 static void got_uri_list(GtkWidget *widget,
92 GdkDragContext *context,
93 const char *selection_data,
94 guint32 time);
95 static gboolean drag_drop(GtkWidget *widget,
96 GdkDragContext *context,
97 gint x,
98 gint y,
99 guint time,
100 gpointer data);
101 static void drag_data_received(GtkWidget *widget,
102 GdkDragContext *context,
103 gint x,
104 gint y,
105 GtkSelectionData *selection_data,
106 guint info,
107 guint32 time,
108 gpointer user_data);
109 static gboolean spring_now(gpointer data);
110 static void spring_win_destroyed(GtkWidget *widget, gpointer data);
111 static void menuitem_response(gpointer data, guint action, GtkWidget *widget);
112 static void prompt_action(GList *paths, gchar *dest);
114 typedef enum {
115 MENU_COPY,
116 MENU_MOVE,
117 MENU_LINK,
118 } MenuActionType;
120 #undef N_
121 #define N_(x) x
122 static GtkItemFactoryEntry menu_def[] = {
123 {N_("Copy"), NULL, menuitem_response, MENU_COPY, NULL},
124 {N_("Move"), NULL, menuitem_response, MENU_MOVE, NULL},
125 {N_("Link"), NULL, menuitem_response, MENU_LINK, 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 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 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 return prop_text;
205 return NULL;
208 /* Is the sender willing to supply this target type? */
209 gboolean provides(GdkDragContext *context, GdkAtom target)
211 GList *targets = context->targets;
213 while (targets && ((GdkAtom) targets->data != target))
214 targets = targets->next;
216 return targets != NULL;
219 /* DRAGGING FROM US */
221 /* The user has held the mouse button down over a group of item and moved -
222 * start a drag. 'uri_list' is copied, so you can delete it straight away.
224 void drag_selection(GtkWidget *widget, GdkEventMotion *event, guchar *uri_list)
226 GdkPixbuf *pixbuf;
227 GdkDragContext *context;
228 GdkDragAction actions;
229 GtkTargetList *target_list;
230 GtkTargetEntry target_table[] = {
231 {"text/uri-list", 0, TARGET_URI_LIST},
232 {"UTF8_STRING", 0, TARGET_UTF8},
235 if (event->state & GDK_BUTTON1_MASK)
236 actions = GDK_ACTION_COPY | GDK_ACTION_MOVE
237 | GDK_ACTION_LINK | GDK_ACTION_ASK;
238 else
240 if (o_dnd_middle_menu.int_value)
241 actions = GDK_ACTION_ASK;
242 else
243 actions = GDK_ACTION_MOVE;
246 target_list = gtk_target_list_new(target_table,
247 G_N_ELEMENTS(target_table));
249 context = gtk_drag_begin(widget,
250 target_list,
251 actions,
252 (event->state & GDK_BUTTON1_MASK) ? 1 :
253 (event->state & GDK_BUTTON2_MASK) ? 2 : 3,
254 (GdkEvent *) event);
256 g_dataset_set_data_full(context, "uri_list",
257 g_strdup(uri_list), g_free);
259 pixbuf = gtk_widget_render_icon(widget, GTK_STOCK_DND_MULTIPLE,
260 GTK_ICON_SIZE_DIALOG, NULL);
261 gtk_drag_set_icon_pixbuf(context, pixbuf, 0, 0);
262 g_object_unref(pixbuf);
265 /* Copy/Load this item into another directory/application */
266 void drag_one_item(GtkWidget *widget,
267 GdkEventMotion *event,
268 const guchar *full_path,
269 DirItem *item,
270 MaskedPixmap *image)
272 guchar *uri, *tmp;
273 GdkDragContext *context;
274 GdkDragAction actions;
275 GtkTargetList *target_list;
276 GtkTargetEntry target_table[] = {
277 {"text/uri-list", 0, TARGET_URI_LIST},
278 {"UTF8_STRING", 0, TARGET_UTF8},
279 {"application/octet-stream", 0, TARGET_RAW},
280 {"", 0, TARGET_RAW},
283 g_return_if_fail(full_path != NULL);
284 g_return_if_fail(item != NULL);
286 if (!image)
287 image = item->image;
289 if (item->base_type == TYPE_FILE)
291 MIME_type *t = item->mime_type;
293 target_table[3].target = g_strconcat(t->media_type, "/",
294 t->subtype, NULL);
295 target_list = gtk_target_list_new(target_table,
296 G_N_ELEMENTS(target_table));
297 g_free(target_table[3].target);
299 else
300 target_list = gtk_target_list_new(target_table, 2);
302 if (event->state & GDK_BUTTON1_MASK)
303 actions = GDK_ACTION_COPY | GDK_ACTION_ASK
304 | GDK_ACTION_MOVE | GDK_ACTION_LINK;
305 else
307 if (o_dnd_middle_menu.int_value)
308 actions = GDK_ACTION_ASK;
309 else
310 actions = GDK_ACTION_MOVE;
313 context = gtk_drag_begin(widget,
314 target_list,
315 actions,
316 (event->state & GDK_BUTTON1_MASK) ? 1 :
317 (event->state & GDK_BUTTON2_MASK) ? 2 : 3,
318 (GdkEvent *) event);
320 g_dataset_set_data_full(context, "full_path",
321 g_strdup(full_path), g_free);
322 tmp = encode_path_as_uri(full_path);
323 uri = g_strconcat(tmp, "\r\n", NULL);
324 /*printf("%s\n", tmp);*/
325 g_free(tmp);
326 g_dataset_set_data_full(context, "uri_list", uri, g_free);
328 g_return_if_fail(image != NULL);
330 gtk_drag_set_icon_pixbuf(context, image->pixbuf, 0, 0);
333 /* Convert text/uri-list data to UTF8_STRING.
334 * g_free() the result.
336 static gchar *uri_list_to_utf8(const char *uri_list)
338 GString *new;
339 GList *uris, *next_uri;
340 char *string;
342 new = g_string_new(NULL);
344 uris = uri_list_to_glist(uri_list);
346 for (next_uri = uris; next_uri; next_uri = next_uri->next)
348 char *uri = (char *) next_uri->data;
349 char *local;
351 local = get_local_path(uri);
353 if (new->len)
354 g_string_append_c(new, ' ');
356 if (local)
358 g_string_append(new, local);
359 g_free(local);
361 else
362 g_warning("Not local!\n");
364 g_free(uri);
367 if (uris)
368 g_list_free(uris);
370 string = new->str;
371 g_string_free(new, FALSE);
373 return string;
376 /* Called when a remote app wants us to send it some data.
377 * TODO: Maybe we should handle errors better (ie, let the remote app know
378 * the drag has failed)?
380 void drag_data_get(GtkWidget *widget,
381 GdkDragContext *context,
382 GtkSelectionData *selection_data,
383 guint info,
384 guint32 time,
385 gpointer data)
387 char *to_send = "E"; /* Default to sending an error */
388 long to_send_length = 1;
389 gboolean delete_once_sent = FALSE;
390 GdkAtom type;
391 guchar *path;
393 type = selection_data->target;
395 switch (info)
397 case TARGET_RAW:
398 path = g_dataset_get_data(context, "full_path");
399 if (path && load_file(path, &to_send, &to_send_length))
401 delete_once_sent = TRUE;
402 break;
404 g_warning("drag_data_get: Can't find path!\n");
405 return;
406 case TARGET_UTF8:
408 char *uri_list;
409 uri_list = g_dataset_get_data(context, "uri_list");
410 to_send = uri_list_to_utf8(uri_list);
411 to_send_length = strlen(to_send);
412 delete_once_sent = TRUE;
413 break;
415 case TARGET_URI_LIST:
416 to_send = g_dataset_get_data(context, "uri_list");
417 to_send_length = strlen(to_send);
418 type = text_uri_list; /* (needed for xine) */
419 delete_once_sent = FALSE;
420 break;
421 default:
422 delayed_error("drag_data_get: %s",
423 _("Internal error - bad info type"));
424 break;
427 gtk_selection_data_set(selection_data,
428 type,
430 to_send,
431 to_send_length);
433 if (delete_once_sent)
434 g_free(to_send);
437 /* DRAGGING TO US */
439 /* Set up this widget as a drop-target.
440 * Does not attach any motion handlers.
442 void make_drop_target(GtkWidget *widget, GtkDestDefaults defaults)
444 GtkTargetEntry target_table[] =
446 {"text/uri-list", 0, TARGET_URI_LIST},
447 {"text/x-moz-url", 0, TARGET_MOZ_URL},
448 {"XdndDirectSave0", 0, TARGET_XDS},
449 {"application/octet-stream", 0, TARGET_RAW},
452 gtk_drag_dest_set(widget,
453 defaults,
454 target_table,
455 sizeof(target_table) / sizeof(*target_table),
456 GDK_ACTION_COPY | GDK_ACTION_ASK | GDK_ACTION_MOVE
457 | GDK_ACTION_LINK | GDK_ACTION_PRIVATE);
459 g_signal_connect(widget, "drag_drop", G_CALLBACK(drag_drop), NULL);
460 g_signal_connect(widget, "drag_data_received",
461 G_CALLBACK(drag_data_received), NULL);
464 /* Like drag_set_dest, but for a pinboard-type widget */
465 void drag_set_pinboard_dest(GtkWidget *widget)
467 GtkTargetEntry target_table[] = {
468 {"text/uri-list", 0, TARGET_URI_LIST},
471 gtk_drag_dest_set(widget,
472 GTK_DEST_DEFAULT_DROP,
473 target_table,
474 sizeof(target_table) / sizeof(*target_table),
475 GDK_ACTION_LINK);
476 g_signal_connect(widget, "drag_data_received",
477 G_CALLBACK(desktop_drag_data_received), NULL);
480 /* item is the item the file is held over, NULL for directory background.
481 * 'item' may be NULL on exit if the drop should be treated as onto the
482 * background. Disallow drags to a selected icon before calling this.
484 * Returns NULL to reject the drop, or drop_dest_prog/drop_dest_dir to
485 * accept. Build the path based on item.
487 const guchar *dnd_motion_item(GdkDragContext *context, DirItem **item_p)
489 DirItem *item = *item_p;
491 if (item)
493 /* If we didn't drop onto a directory, application or
494 * executable file then act as though the drop is to the
495 * window background.
497 if (item->base_type != TYPE_DIRECTORY
498 && !(item->mime_type == application_executable))
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, 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 gchar *uri;
586 uri = encode_path_as_uri(make_path(dest_path,
587 leafname));
588 set_xds_prop(context, uri);
589 g_free(uri);
591 target = XdndDirectSave0;
592 g_dataset_set_data_full(context, "leafname",
593 leafname, g_free);
596 else
597 error = _(
598 "XdndDirectSave0 target provided, but the atom "
599 "XdndDirectSave0 (type text/plain) did not "
600 "contain a leafname\n");
602 else if (provides(context, text_uri_list))
603 target = text_uri_list;
604 else if (provides(context, text_x_moz_url))
605 target = text_x_moz_url;
606 else if (provides(context, application_octet_stream))
607 target = application_octet_stream;
608 else
610 if (dest_type == drop_dest_dir)
611 error = _("Sorry - I require a target type of "
612 "text/uri-list or XdndDirectSave0.");
613 else
614 error = _("Sorry - I require a target type of "
615 "text/uri-list or application/octet-stream.");
618 if (error)
620 gtk_drag_finish(context, FALSE, FALSE, time); /* Failure */
622 delayed_error("%s", error);
624 else
625 gtk_drag_get_data(widget, context, target, time);
627 return TRUE;
630 /* Called when a text/uri-list arrives */
631 static void desktop_drag_data_received(GtkWidget *widget,
632 GdkDragContext *context,
633 gint x,
634 gint y,
635 GtkSelectionData *selection_data,
636 guint info,
637 guint32 time,
638 FilerWindow *filer_window)
640 GList *uris, *next;
641 gint dx, dy;
643 if (!selection_data->data)
645 /* Timeout? */
646 return;
649 if (pinboard_drag_in_progress)
651 pinboard_move_icons();
652 return;
655 gdk_window_get_position(widget->window, &dx, &dy);
656 x += dx;
657 y += dy;
659 uris = uri_list_to_glist(selection_data->data);
661 for (next = uris; next; next = next->next)
663 guchar *path;
665 path = get_local_path((gchar *) next->data);
666 /*printf("%s -> %s\n", (char *) next->data,
667 path? path: "NULL");*/
668 if (path)
670 pinboard_pin(path, NULL, x, y, NULL);
671 x += 64;
672 g_free(path);
675 g_free(next->data);
678 if (uris)
679 g_list_free(uris);
682 /* Convert Mozilla's text/x-moz-uri into a text/uri-list */
683 static void got_moz_uri(GtkWidget *widget,
684 GdkDragContext *context,
685 GtkSelectionData *selection_data,
686 guint32 time)
688 gchar *utf8, *uri_list, *eol;
690 utf8 = g_utf16_to_utf8((gunichar2 *) selection_data->data,
691 (glong) selection_data->length,
692 NULL, NULL, NULL);
694 eol = utf8 ? strchr(utf8, '\n') : NULL;
695 if (!eol)
697 delayed_error("Invalid UTF16 from text/x-moz-url target");
698 g_free(utf8);
699 gtk_drag_finish(context, FALSE, FALSE, time);
700 return;
703 *eol = '\0';
704 uri_list = g_strconcat(utf8, "\r\n", NULL);
705 g_free(utf8);
707 got_uri_list(widget, context, uri_list, time);
709 g_free(uri_list);
712 /* Called when some data arrives from the remote app (which we asked for
713 * in drag_drop).
715 static void drag_data_received(GtkWidget *widget,
716 GdkDragContext *context,
717 gint x,
718 gint y,
719 GtkSelectionData *selection_data,
720 guint info,
721 guint32 time,
722 gpointer user_data)
724 if (!selection_data->data)
726 /* Timeout? */
727 gtk_drag_finish(context, FALSE, FALSE, time); /* Failure */
728 return;
731 switch (info)
733 case TARGET_XDS:
734 got_data_xds_reply(widget, context,
735 selection_data, time);
736 break;
737 case TARGET_RAW:
738 got_data_raw(widget, context, selection_data, time);
739 break;
740 case TARGET_URI_LIST:
741 got_uri_list(widget, context, selection_data->data,
742 time);
743 break;
744 case TARGET_MOZ_URL:
745 got_moz_uri(widget, context, selection_data, time);
746 break;
747 default:
748 gtk_drag_finish(context, FALSE, FALSE, time);
749 delayed_error("drag_data_received: %s",
750 _("Unknown target"));
751 break;
755 static void got_data_xds_reply(GtkWidget *widget,
756 GdkDragContext *context,
757 GtkSelectionData *selection_data,
758 guint32 time)
760 gboolean mark_unsafe = TRUE;
761 char response = *selection_data->data;
762 const char *error = NULL;
763 char *dest_path;
765 dest_path = g_dataset_get_data(context, "drop_dest_path");
767 if (selection_data->length != 1)
768 response = '?';
770 if (response == 'F')
772 /* Sender couldn't save there - ask for another
773 * type if possible.
775 if (provides(context, application_octet_stream))
777 mark_unsafe = FALSE; /* Wait and see */
779 gtk_drag_get_data(widget, context,
780 application_octet_stream, time);
782 else
783 error = _("Remote app can't or won't send me "
784 "the data - sorry");
786 else if (response == 'S')
788 /* Success - data is saved */
789 mark_unsafe = FALSE; /* It really is safe */
790 gtk_drag_finish(context, TRUE, FALSE, time);
792 refresh_dirs(dest_path);
794 else if (response != 'E')
796 error = _("XDS protocol error: "
797 "return code should be 'S', 'F' or 'E'\n");
799 /* else: error has been reported by the sender */
801 if (mark_unsafe)
803 set_xds_prop(context, "");
804 /* Unsave also implies that the drag failed */
805 gtk_drag_finish(context, FALSE, FALSE, time);
808 if (error)
809 delayed_error("%s", error);
812 static void got_data_raw(GtkWidget *widget,
813 GdkDragContext *context,
814 GtkSelectionData *selection_data,
815 guint32 time)
817 const char *leafname;
818 int fd;
819 const char *error = NULL;
820 const char *dest_path;
822 g_return_if_fail(selection_data->data != NULL);
824 dest_path = g_dataset_get_data(context, "drop_dest_path");
826 if (context->action == GDK_ACTION_ASK)
828 gtk_drag_finish(context, FALSE, FALSE, time); /* Failure */
829 delayed_error(_("Sorry, can't display a menu of actions "
830 "for a remote file / raw data."));
831 return;
834 if (g_dataset_get_data(context, "drop_dest_type") == drop_dest_prog)
836 /* The data needs to be sent to an application */
837 run_with_data(dest_path,
838 selection_data->data, selection_data->length);
839 gtk_drag_finish(context, TRUE, FALSE, time); /* Success! */
840 return;
843 leafname = g_dataset_get_data(context, "leafname");
844 if (!leafname)
845 leafname = _("UntitledData");
847 fd = open(make_path(dest_path, leafname),
848 O_WRONLY | O_CREAT | O_EXCL | O_NOCTTY,
849 S_IRUSR | S_IRGRP | S_IROTH |
850 S_IWUSR | S_IWGRP | S_IWOTH);
852 if (fd == -1)
853 error = g_strerror(errno);
854 else
856 if (write(fd,
857 selection_data->data,
858 selection_data->length) == -1)
859 error = g_strerror(errno);
861 if (close(fd) == -1 && !error)
862 error = g_strerror(errno);
864 refresh_dirs(dest_path);
867 if (error)
869 if (provides(context, XdndDirectSave0))
870 set_xds_prop(context, "");
871 gtk_drag_finish(context, FALSE, FALSE, time); /* Failure */
872 delayed_error(_("Error saving file: %s"), error);
874 else
875 gtk_drag_finish(context, TRUE, FALSE, time); /* Success! */
878 static gboolean uri_is_local(const char *uri)
880 char *path;
881 path = get_local_path(uri);
882 if (!path)
883 return FALSE;
884 g_free(path);
885 return TRUE;
888 /* Run the shell command 'command', replacing $1 with 'arg' */
889 static void run_with_argument(const char *dir,
890 const char *command,
891 const char *arg)
893 GPtrArray *argv;
895 argv = g_ptr_array_new();
897 g_ptr_array_add(argv, "sh");
898 g_ptr_array_add(argv, "-c");
899 g_ptr_array_add(argv, (char *) command);
900 g_ptr_array_add(argv, "sh");
901 g_ptr_array_add(argv, (char *) arg);
902 g_ptr_array_add(argv, NULL);
904 rox_spawn(dir, (const gchar **) argv->pdata);
906 g_ptr_array_free(argv, TRUE);
909 /* We've got a list of URIs from somewhere (probably another filer window).
910 * If the files are on the local machine then try to copy them ourselves,
911 * otherwise, if there was only one file and application/octet-stream was
912 * provided, get the data via the X server.
913 * For http:, https: or ftp: schemes, use the download handler.
915 static void got_uri_list(GtkWidget *widget,
916 GdkDragContext *context,
917 const char *selection_data,
918 guint32 time)
920 GList *uri_list;
921 const char *error = NULL;
922 GList *next_uri;
923 gboolean send_reply = TRUE;
924 char *dest_path;
925 char *type;
927 dest_path = g_dataset_get_data(context, "drop_dest_path");
928 type = g_dataset_get_data(context, "drop_dest_type");
930 uri_list = uri_list_to_glist(selection_data);
932 if (type == drop_dest_bookmark)
934 GList *next;
935 for (next = uri_list; next; next = next->next)
936 bookmarks_add_uri((guchar *) next->data);
937 destroy_glist(&uri_list);
938 gtk_drag_finish(context, TRUE, FALSE, time); /* Success! */
939 return;
942 g_return_if_fail(dest_path != NULL);
944 if (!uri_list)
945 error = _("No URIs in the text/uri-list (nothing to do!)");
946 else if (context->action != GDK_ACTION_ASK && type == drop_dest_prog)
947 run_with_files(dest_path, uri_list);
948 else if ((!uri_list->next) && !uri_is_local(uri_list->data))
950 /* There is one URI in the list, and it's not on the local
951 * machine. Get it via the X server if possible.
954 if (provides(context, application_octet_stream))
956 char *leaf;
957 leaf = strrchr(uri_list->data, '/');
958 if (leaf)
959 leaf++;
960 else
961 leaf = uri_list->data;
962 g_dataset_set_data_full(context, "leafname",
963 unescape_uri(leaf), g_free);
964 gtk_drag_get_data(widget, context,
965 application_octet_stream, time);
966 send_reply = FALSE;
968 else if ((strncasecmp(uri_list->data, "http:", 5) == 0) ||
969 (strncasecmp(uri_list->data, "https:", 6) == 0) ||
970 (strncasecmp(uri_list->data, "ftp:", 4) == 0))
972 run_with_argument(dest_path,
973 o_dnd_uri_handler.value,
974 uri_list->data);
976 else
977 error = _("Can't get data from remote machine "
978 "(application/octet-stream not provided)");
980 else
982 GList *local_paths = NULL;
984 /* Either one local URI, or a list. If everything in the list
985 * isn't local then we are stuck.
988 for (next_uri = uri_list; next_uri; next_uri = next_uri->next)
990 char *path;
992 path = get_local_path((char *) next_uri->data);
993 /*printf("%s -> %s\n", (char *) next_uri->data,
994 path? path: "NULL");*/
996 if (path)
997 local_paths = g_list_append(local_paths,
998 path);
999 else
1000 error = _("Some of these files are on a "
1001 "different machine - they will be "
1002 "ignored - sorry");
1005 if (!local_paths)
1007 error = _("None of these files are on the local "
1008 "machine - I can't operate on multiple "
1009 "remote files - sorry.");
1011 else if (context->action == GDK_ACTION_ASK)
1012 prompt_action(local_paths, dest_path);
1013 else if (context->action == GDK_ACTION_MOVE)
1014 action_move(local_paths, dest_path, NULL, -1);
1015 else if (context->action == GDK_ACTION_COPY)
1016 action_copy(local_paths, dest_path, NULL, -1);
1017 else if (context->action == GDK_ACTION_LINK)
1018 action_link(local_paths, dest_path, NULL);
1019 else
1020 error = _("Unknown action requested");
1022 destroy_glist(&local_paths);
1025 if (error)
1027 gtk_drag_finish(context, FALSE, FALSE, time); /* Failure */
1028 delayed_error(_("Error getting file list: %s"), error);
1030 else if (send_reply)
1031 gtk_drag_finish(context, TRUE, FALSE, time); /* Success! */
1033 destroy_glist(&uri_list);
1036 /* Called when an item from the ACTION_ASK menu is chosen */
1037 static void menuitem_response(gpointer data, guint action, GtkWidget *widget)
1039 if (action == MENU_MOVE)
1040 action_move(prompt_local_paths, prompt_dest_path, NULL, -1);
1041 else if (action == MENU_COPY)
1042 action_copy(prompt_local_paths, prompt_dest_path, NULL, -1);
1043 else if (action == MENU_LINK)
1044 action_link(prompt_local_paths, prompt_dest_path, NULL);
1047 /* When some local files are dropped somewhere with ACTION_ASK, this
1048 * function is called to display the menu.
1050 static void prompt_action(GList *paths, gchar *dest)
1052 GList *next;
1054 if (prompt_local_paths)
1056 destroy_glist(&prompt_local_paths);
1057 null_g_free(&prompt_dest_path);
1060 /* Make a copy of the arguments */
1061 for (next = paths; next; next = next->next)
1062 prompt_local_paths = g_list_append(prompt_local_paths,
1063 g_strdup((gchar *) next->data));
1064 prompt_dest_path = g_strdup(dest);
1066 if (!dnd_menu)
1068 GtkItemFactory *item_factory;
1070 item_factory = menu_create(menu_def,
1071 sizeof(menu_def) / sizeof(*menu_def),
1072 "<dnd>", NULL);
1073 dnd_menu = gtk_item_factory_get_widget(item_factory, "<dnd>");
1076 /* Shade 'Set Icon' if there are multiple files */
1077 menu_set_items_shaded(dnd_menu, g_list_length(paths) != 1, 4, 1);
1079 show_popup_menu(dnd_menu, gtk_get_current_event(), 1);
1083 /* SPRING-LOADING */
1085 /* This is the code that makes directories pop open if you hold a
1086 * file over them...
1088 * First, call dnd_spring_load(context) to arm the system.
1089 * After a timeout (1/2 a second) the dest_path directory will be
1090 * opened in a new window, unless dnd_spring_abort is called first.
1093 static gint spring_timeout = -1;
1094 static GdkDragContext *spring_context = NULL;
1095 static FilerWindow *spring_window = NULL;
1096 static FilerWindow *spring_src_window = NULL;
1098 void dnd_spring_load(GdkDragContext *context, FilerWindow *src_win)
1100 g_return_if_fail(context != NULL);
1102 if (!o_dnd_spring_open.int_value)
1103 return;
1105 if (spring_context)
1106 dnd_spring_abort();
1108 spring_context = context;
1109 g_object_ref(spring_context);
1110 spring_src_window = src_win;
1111 spring_timeout = gtk_timeout_add(
1112 o_dnd_spring_delay.int_value, spring_now, NULL);
1115 void dnd_spring_abort(void)
1117 if (!spring_context)
1118 return;
1120 g_object_unref(spring_context);
1121 spring_context = NULL;
1122 gtk_timeout_remove(spring_timeout);
1125 /* If all mod keys are released, no buttons are pressed, and the
1126 * mouse is outside the spring window, then close it.
1128 static gboolean spring_check_idle(gpointer data)
1130 int p_x, p_y;
1132 if (!spring_window)
1133 return FALSE;
1135 if (!get_pointer_xy(&p_x, &p_y))
1138 GdkWindow *win = spring_window->window->window;
1139 int x, y;
1140 int w, h;
1142 gdk_window_get_position(win, &x, &y);
1143 gdk_window_get_size(win, &w, &h);
1145 if (p_x < x || p_x > x + w || p_y < y || p_y > y + h)
1149 gtk_widget_destroy(spring_window->window);
1150 return FALSE; /* Got it! */
1153 return TRUE; /* Try again later */
1156 static gboolean spring_now(gpointer data)
1158 const char *type;
1159 const guchar *dest_path;
1160 gint x, y;
1162 g_return_val_if_fail(spring_context != NULL, FALSE);
1163 g_return_val_if_fail(!spring_in_progress, FALSE);
1165 type = g_dataset_get_data(spring_context, "drop_dest_type");
1166 if (type == drop_dest_bookmark)
1168 bookmarks_edit();
1169 goto out;
1172 dest_path = g_dataset_get_data(spring_context, "drop_dest_path");
1173 g_return_val_if_fail(dest_path != NULL, FALSE);
1176 * Note: Due to a bug in gtk, if a window disappears during
1177 * a drag and the pointer moves over where the window was,
1178 * the sender crashes! Therefore, do not close any windows
1179 * while dragging! (fixed in later versions)
1182 if (spring_window)
1183 gtk_widget_destroy(spring_window->window);
1186 get_pointer_xy(&x, &y);
1188 spring_in_progress++;
1189 if (spring_window)
1191 view_cursor_to_iter(spring_window->view, NULL);
1192 filer_change_to(spring_window, dest_path, NULL);
1193 /* DON'T move the window. Gtk+ sometimes doesn't
1194 * notice :-(
1197 else
1199 spring_window = filer_opendir(dest_path,
1200 spring_src_window, NULL);
1201 if (spring_window)
1203 gtk_timeout_add(500, spring_check_idle, NULL);
1204 g_signal_connect(spring_window->window, "destroy",
1205 G_CALLBACK(spring_win_destroyed), NULL);
1206 centre_window(spring_window->window->window, x, y);
1209 spring_in_progress--;
1211 out:
1212 dnd_spring_abort();
1214 return FALSE;
1217 static void spring_win_destroyed(GtkWidget *widget, gpointer data)
1219 spring_window = NULL;
1222 /* HANDLING MOTION EVENTS */
1224 /* If not-NULL, then this widget has a grab */
1225 static GtkWidget *motion_widget = NULL;
1227 /* If TRUE, we must gdk_pointer_ungrab() too when finishing */
1228 static gboolean motion_pointer_grab = FALSE;
1230 /* Call this on a button press event. It stores the mouse position
1231 * as the start of the new drag and returns TRUE if all is well.
1232 * Further motions events are disabled at this point - you must
1233 * then call dnd_motion_start() to set the type of motion expected.
1234 * Grabs the widget on the first press.
1236 * If the system is not ready to handle a motion event (because a
1237 * button is already held down?) it does nothing and returns FALSE.
1239 * If the event is not a single click then it simply returns TRUE.
1241 gboolean dnd_motion_press(GtkWidget *widget, GdkEventButton *event)
1243 if (event->type != GDK_BUTTON_PRESS)
1244 return TRUE; /* Not a click event! */
1246 motion_buttons_pressed++;
1247 if (motion_buttons_pressed == 1)
1249 /* g_print("[ grab! ]\n"); */
1250 gtk_grab_add(widget);
1251 motion_widget = widget;
1254 if (motion_state != MOTION_NONE)
1255 return FALSE; /* Ignore clicks - we're busy! */
1257 motion_state = MOTION_DISABLED;
1258 drag_start_x = event->x_root;
1259 drag_start_y = event->y_root;
1261 return TRUE;
1264 /* After the button press event, decide what kind of motion is expected.
1265 * If you don't call this then the motion system is disabled - call
1266 * dnd_motion_release() to reset it.
1268 * Note: If you open a popup menu or start DND call dnd_motion_ungrab()
1269 * instead.
1271 void dnd_motion_start(MotionType motion)
1273 g_return_if_fail(motion_state == MOTION_DISABLED);
1275 motion_state = motion;
1278 /* Call this on a button release event. If some buttons are still pressed,
1279 * returns TRUE and does nothing.
1281 * Otherwise, it resets the motion system to be ready again and returns TRUE.
1283 * If the motion system wasn't being used (MOTION_NONE) then it does nothing
1284 * and returns FALSE - process the release event yourself as it isn't part
1285 * of a motion. This also happens if a motion was primed but never happened.
1287 gboolean dnd_motion_release(GdkEventButton *event)
1289 MotionType motion = motion_state;
1290 gint drag_threshold;
1291 int dx, dy;
1293 if (motion_buttons_pressed == 0)
1294 return TRUE; /* We were disabled */
1296 if (motion_buttons_pressed == 1)
1297 dnd_motion_ungrab();
1298 else
1300 motion_buttons_pressed--;
1301 return TRUE;
1304 if (motion == MOTION_REPOSITION || motion == MOTION_DISABLED)
1305 return TRUE; /* Already done something - eat the event */
1307 /* Eat release events that happen too far from the click
1308 * source. Otherwise, allow the caller to treat this as a click
1309 * that never became a motion.
1311 dx = event->x_root - drag_start_x;
1312 dy = event->y_root - drag_start_y;
1314 g_object_get(gtk_settings_get_default(),
1315 "gtk-dnd-drag-threshold", &drag_threshold,
1316 NULL);
1318 return ABS(dx) > drag_threshold || ABS(dy) > drag_threshold;
1321 /* Use this to disable the motion system. The system will be reset once
1322 * all mouse buttons are released.
1324 void dnd_motion_disable(void)
1326 g_return_if_fail(motion_state != MOTION_NONE &&
1327 motion_state != MOTION_DISABLED);
1329 motion_state = MOTION_DISABLED;
1332 /* Use this if something else is going to grab the pointer so that
1333 * we won't get any more motion or release events.
1335 void dnd_motion_ungrab(void)
1337 if (motion_buttons_pressed > 0)
1339 if (motion_pointer_grab)
1341 gdk_pointer_ungrab(GDK_CURRENT_TIME);
1342 motion_pointer_grab = FALSE;
1343 /* g_print("[ ungrab_pointer ]\n"); */
1345 gtk_grab_remove(motion_widget);
1346 motion_widget = NULL;
1347 motion_buttons_pressed = 0;
1348 /* g_print("[ ungrab ]\n"); */
1351 motion_state = MOTION_NONE;
1354 /* Call this on motion events. If the mouse position is far enough
1355 * from the click position, returns TRUE and does dnd_motion_ungrab().
1356 * You should then start regular drag-and-drop.
1358 * Otherwise, returns FALSE.
1360 gboolean dnd_motion_moved(GdkEventMotion *event)
1362 gint drag_threshold;
1363 int dx, dy;
1365 g_object_get(gtk_settings_get_default(),
1366 "gtk-dnd-drag-threshold", &drag_threshold,
1367 NULL);
1369 dx = event->x_root - drag_start_x;
1370 dy = event->y_root - drag_start_y;
1372 if (ABS(dx) <= drag_threshold && ABS(dy) <= drag_threshold)
1373 return FALSE; /* Not far enough */
1375 dnd_motion_ungrab();
1377 return TRUE;
1380 /* Normally, the X server will automatically grab the pointer on a
1381 * button press and ungrab on release. However, if the grab widget
1382 * is reparented then call this to re-aquire the grab.
1384 void dnd_motion_grab_pointer(void)
1386 g_return_if_fail(motion_widget != NULL);
1388 gdk_pointer_grab(motion_widget->window, FALSE,
1389 GDK_POINTER_MOTION_MASK |
1390 GDK_BUTTON_RELEASE_MASK,
1391 FALSE, NULL, GDK_CURRENT_TIME);
1393 motion_pointer_grab = TRUE;