r4255: Make Path optional in PanelRemove SOAP call, allowing removal of items based
[rox-filer/translations.git] / ROX-Filer / src / dnd.c
blob0fc45a82dcf71ea9ca956389b1f5667cfc1c96cb
1 /*
2 * $Id$
4 * ROX-Filer, filer for the ROX desktop project
5 * Copyright (C) 2005, 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_REL,
118 MENU_LINK_ABS,
119 } MenuActionType;
121 #undef N_
122 #define N_(x) x
123 static GtkItemFactoryEntry menu_def[] = {
124 {N_("Copy"), NULL, menuitem_response, MENU_COPY, NULL},
125 {N_("Move"), NULL, menuitem_response, MENU_MOVE, NULL},
126 {N_("Link (relative)"), NULL, menuitem_response, MENU_LINK_REL, NULL},
127 {N_("Link (absolute)"), NULL, menuitem_response, MENU_LINK_ABS, NULL},
129 static GtkWidget *dnd_menu = NULL;
131 /* Possible values for drop_dest_type (can also be NULL).
132 * In either case, drop_dest_path is the app/file/dir to use.
134 const char *drop_dest_prog = "drop_dest_prog"; /* Run a program */
135 const char *drop_dest_dir = "drop_dest_dir"; /* Save to path */
136 const char *drop_dest_pass_through = "drop_dest_pass"; /* Pass to parent */
137 const char *drop_dest_bookmark = "drop_dest_bookmark"; /* Add to bookmarks */
139 GdkAtom XdndDirectSave0;
140 GdkAtom xa_text_plain;
141 GdkAtom text_uri_list;
142 GdkAtom text_x_moz_url;
143 GdkAtom xa_application_octet_stream;
144 GdkAtom xa_string; /* Not actually used for DnD, but the others are here! */
146 int spring_in_progress = 0; /* Non-zero changes filer_opendir slightly */
148 Option o_dnd_drag_to_icons;
149 Option o_dnd_spring_open;
150 static Option o_dnd_spring_delay;
151 static Option o_dnd_middle_menu;
152 Option o_dnd_left_menu;
153 static Option o_dnd_uri_handler;
155 void dnd_init(void)
157 XdndDirectSave0 = gdk_atom_intern("XdndDirectSave0", FALSE);
158 xa_text_plain = gdk_atom_intern("text/plain", FALSE);
159 text_uri_list = gdk_atom_intern("text/uri-list", FALSE);
160 text_x_moz_url = gdk_atom_intern("text/x-moz-url", FALSE);
161 xa_application_octet_stream = gdk_atom_intern("application/octet-stream",
162 FALSE);
163 xa_string = gdk_atom_intern("STRING", FALSE);
165 option_add_int(&o_dnd_drag_to_icons, "dnd_drag_to_icons", 1);
166 option_add_int(&o_dnd_spring_open, "dnd_spring_open", 0);
167 option_add_int(&o_dnd_spring_delay, "dnd_spring_delay", 400);
168 option_add_int(&o_dnd_left_menu, "dnd_left_menu", TRUE);
169 option_add_int(&o_dnd_middle_menu, "dnd_middle_menu", TRUE);
171 option_add_string(&o_dnd_uri_handler, "dnd_uri_handler",
172 "xterm -e wget $1");
175 /* SUPPORT FUNCTIONS */
177 /* Set the XdndDirectSave0 property on the source window for this context */
178 static void set_xds_prop(GdkDragContext *context, const char *text)
180 gdk_property_change(context->source_window,
181 XdndDirectSave0,
182 xa_text_plain, 8,
183 GDK_PROP_MODE_REPLACE,
184 text,
185 strlen(text));
188 static char *get_xds_prop(GdkDragContext *context)
190 guchar *prop_text;
191 gint length;
193 if (gdk_property_get(context->source_window,
194 XdndDirectSave0,
195 xa_text_plain,
196 0, MAXURILEN,
197 FALSE,
198 NULL, NULL,
199 &length, &prop_text) && prop_text)
201 /* Terminate the string */
202 prop_text = g_realloc(prop_text, length + 1);
203 prop_text[length] = '\0';
204 /* Note: assuming UTF-8 (should convert here) */
205 return prop_text;
208 return NULL;
211 /* Is the sender willing to supply this target type? */
212 gboolean provides(GdkDragContext *context, GdkAtom target)
214 GList *targets = context->targets;
216 while (targets && ((GdkAtom) targets->data != target))
217 targets = targets->next;
219 return targets != NULL;
222 /* DRAGGING FROM US */
224 /* The user has held the mouse button down over a group of item and moved -
225 * start a drag. 'uri_list' is copied, so you can delete it straight away.
227 void drag_selection(GtkWidget *widget, GdkEventMotion *event, guchar *uri_list)
229 GdkPixbuf *pixbuf;
230 GdkDragContext *context;
231 GdkDragAction actions;
232 GtkTargetList *target_list;
233 GtkTargetEntry target_table[] = {
234 {"text/uri-list", 0, TARGET_URI_LIST},
235 {"UTF8_STRING", 0, TARGET_UTF8},
238 if (event->state & GDK_BUTTON1_MASK)
239 actions = GDK_ACTION_COPY | GDK_ACTION_MOVE
240 | GDK_ACTION_LINK | GDK_ACTION_ASK;
241 else
243 if (o_dnd_middle_menu.int_value)
244 actions = GDK_ACTION_ASK;
245 else
246 actions = GDK_ACTION_MOVE;
249 target_list = gtk_target_list_new(target_table,
250 G_N_ELEMENTS(target_table));
252 context = gtk_drag_begin(widget,
253 target_list,
254 actions,
255 (event->state & GDK_BUTTON1_MASK) ? 1 :
256 (event->state & GDK_BUTTON2_MASK) ? 2 : 3,
257 (GdkEvent *) event);
259 g_dataset_set_data_full(context, "uri_list",
260 g_strdup(uri_list), g_free);
262 pixbuf = gtk_widget_render_icon(widget, GTK_STOCK_DND_MULTIPLE,
263 GTK_ICON_SIZE_DIALOG, NULL);
264 gtk_drag_set_icon_pixbuf(context, pixbuf, 0, 0);
265 g_object_unref(pixbuf);
268 /* Copy/Load this item into another directory/application */
269 void drag_one_item(GtkWidget *widget,
270 GdkEventMotion *event,
271 const guchar *full_path,
272 DirItem *item,
273 MaskedPixmap *image)
275 guchar *uri, *tmp;
276 GdkDragContext *context;
277 GdkDragAction actions;
278 GtkTargetList *target_list;
279 GtkTargetEntry target_table[] = {
280 {"text/uri-list", 0, TARGET_URI_LIST},
281 {"UTF8_STRING", 0, TARGET_UTF8},
282 {"application/octet-stream", 0, TARGET_RAW},
283 {"", 0, TARGET_RAW},
286 g_return_if_fail(full_path != NULL);
287 g_return_if_fail(item != NULL);
289 if (!image)
290 image = di_image(item);
292 if (item->base_type == TYPE_FILE)
294 MIME_type *t = item->mime_type;
296 target_table[3].target = g_strconcat(t->media_type, "/",
297 t->subtype, NULL);
298 target_list = gtk_target_list_new(target_table,
299 G_N_ELEMENTS(target_table));
300 g_free(target_table[3].target);
302 else
303 target_list = gtk_target_list_new(target_table, 2);
305 if (event->state & GDK_BUTTON1_MASK)
306 actions = GDK_ACTION_COPY | GDK_ACTION_ASK
307 | GDK_ACTION_MOVE | GDK_ACTION_LINK;
308 else
310 if (o_dnd_middle_menu.int_value)
311 actions = GDK_ACTION_ASK;
312 else
313 actions = GDK_ACTION_MOVE;
316 context = gtk_drag_begin(widget,
317 target_list,
318 actions,
319 (event->state & GDK_BUTTON1_MASK) ? 1 :
320 (event->state & GDK_BUTTON2_MASK) ? 2 : 3,
321 (GdkEvent *) event);
323 g_dataset_set_data_full(context, "full_path",
324 g_strdup(full_path), g_free);
325 tmp = (char *) encode_path_as_uri(full_path);
326 uri = g_strconcat(tmp, "\r\n", NULL);
327 /*printf("%s\n", tmp);*/
328 g_free(tmp);
329 g_dataset_set_data_full(context, "uri_list", uri, g_free);
331 g_return_if_fail(image != NULL);
333 gtk_drag_set_icon_pixbuf(context, image->pixbuf, 0, 0);
336 /* Convert text/uri-list data to UTF8_STRING.
337 * g_free() the result.
339 static gchar *uri_list_to_utf8(const char *uri_list)
341 GString *new;
342 GList *uris, *next_uri;
343 char *string;
345 new = g_string_new(NULL);
347 uris = uri_list_to_glist(uri_list);
349 for (next_uri = uris; next_uri; next_uri = next_uri->next)
351 EscapedPath *uri = next_uri->data;
352 char *local;
354 local = get_local_path(uri);
356 if (new->len)
357 g_string_append_c(new, ' ');
359 if (local)
361 g_string_append(new, local);
362 g_free(local);
364 else
365 g_warning("Not local!\n");
367 g_free(uri);
370 if (uris)
371 g_list_free(uris);
373 string = new->str;
374 g_string_free(new, FALSE);
376 return string;
379 /* Called when a remote app wants us to send it some data.
380 * TODO: Maybe we should handle errors better (ie, let the remote app know
381 * the drag has failed)?
383 void drag_data_get(GtkWidget *widget,
384 GdkDragContext *context,
385 GtkSelectionData *selection_data,
386 guint info,
387 guint32 time,
388 gpointer data)
390 char *to_send = "E"; /* Default to sending an error */
391 long to_send_length = 1;
392 gboolean delete_once_sent = FALSE;
393 GdkAtom type;
394 guchar *path;
396 type = selection_data->target;
398 switch (info)
400 case TARGET_RAW:
401 path = g_dataset_get_data(context, "full_path");
402 if (path && load_file(path, &to_send, &to_send_length))
404 delete_once_sent = TRUE;
405 break;
407 g_warning("drag_data_get: Can't find path!\n");
408 return;
409 case TARGET_UTF8:
411 char *uri_list;
412 uri_list = g_dataset_get_data(context, "uri_list");
413 to_send = uri_list_to_utf8(uri_list);
414 to_send_length = strlen(to_send);
415 delete_once_sent = TRUE;
416 break;
418 case TARGET_URI_LIST:
419 to_send = g_dataset_get_data(context, "uri_list");
420 to_send_length = strlen(to_send);
421 type = text_uri_list; /* (needed for xine) */
422 delete_once_sent = FALSE;
423 break;
424 default:
425 delayed_error("drag_data_get: %s",
426 _("Internal error - bad info type"));
427 break;
430 gtk_selection_data_set(selection_data,
431 type,
433 to_send,
434 to_send_length);
436 if (delete_once_sent)
437 g_free(to_send);
440 /* DRAGGING TO US */
442 /* Set up this widget as a drop-target.
443 * Does not attach any motion handlers.
445 void make_drop_target(GtkWidget *widget, GtkDestDefaults defaults)
447 GtkTargetEntry target_table[] =
449 {"text/uri-list", 0, TARGET_URI_LIST},
450 {"text/x-moz-url", 0, TARGET_MOZ_URL},
451 {"XdndDirectSave0", 0, TARGET_XDS},
452 {"application/octet-stream", 0, TARGET_RAW},
455 gtk_drag_dest_set(widget,
456 defaults,
457 target_table,
458 sizeof(target_table) / sizeof(*target_table),
459 GDK_ACTION_COPY | GDK_ACTION_ASK | GDK_ACTION_MOVE
460 | GDK_ACTION_LINK | GDK_ACTION_PRIVATE);
462 g_signal_connect(widget, "drag_drop", G_CALLBACK(drag_drop), NULL);
463 g_signal_connect(widget, "drag_data_received",
464 G_CALLBACK(drag_data_received), NULL);
467 /* Like drag_set_dest, but for a pinboard-type widget */
468 void drag_set_pinboard_dest(GtkWidget *widget)
470 GtkTargetEntry target_table[] = {
471 {"text/uri-list", 0, TARGET_URI_LIST},
474 gtk_drag_dest_set(widget,
475 GTK_DEST_DEFAULT_DROP,
476 target_table,
477 sizeof(target_table) / sizeof(*target_table),
478 GDK_ACTION_LINK);
479 g_signal_connect(widget, "drag_data_received",
480 G_CALLBACK(desktop_drag_data_received), NULL);
483 /* item is the item the file is held over, NULL for directory background.
484 * 'item' may be NULL on exit if the drop should be treated as onto the
485 * background. Disallow drags to a selected icon before calling this.
487 * Returns NULL to reject the drop, or drop_dest_prog/drop_dest_dir to
488 * accept. Build the path based on item.
490 const guchar *dnd_motion_item(GdkDragContext *context, DirItem **item_p)
492 DirItem *item = *item_p;
494 if (item)
496 /* If we didn't drop onto a directory, application or
497 * executable file then act as though the drop is to the
498 * window background.
500 if (item->base_type != TYPE_DIRECTORY && !EXECUTABLE_FILE(item))
502 item = NULL;
503 *item_p = NULL;
507 if (!item)
509 /* Drop onto the window background */
511 return drop_dest_dir;
514 /* Drop onto a program/directory of some sort */
516 if (item->base_type == TYPE_DIRECTORY &&
517 !(item->flags & ITEM_FLAG_APPDIR))
519 /* A normal directory */
520 if (provides(context, text_uri_list) ||
521 provides(context, text_x_moz_url) ||
522 provides(context, XdndDirectSave0))
523 return drop_dest_dir;
525 else
527 if (provides(context, text_uri_list) ||
528 provides(context, text_x_moz_url) ||
529 provides(context, xa_application_octet_stream))
530 return drop_dest_prog;
533 return NULL;
536 /* User has tried to drop some data on us. Decide what format we would
537 * like the data in.
539 static gboolean drag_drop(GtkWidget *widget,
540 GdkDragContext *context,
541 gint x,
542 gint y,
543 guint time,
544 gpointer data)
546 const char *error = NULL;
547 char *leafname = NULL;
548 GdkAtom target = GDK_NONE;
549 char *dest_path;
550 char *dest_type = NULL;
552 dest_path = g_dataset_get_data(context, "drop_dest_path");
553 dest_type = g_dataset_get_data(context, "drop_dest_type");
555 if (dest_type == drop_dest_pass_through)
556 return FALSE; /* Let the parent widget handle it */
558 if (dest_type == drop_dest_bookmark)
560 if (provides(context, text_uri_list))
561 gtk_drag_get_data(widget, context, text_uri_list, time);
562 else
564 gtk_drag_finish(context, FALSE, FALSE, time);
565 delayed_error(_("Drag a directory here to "
566 "bookmark it."));
568 return TRUE;
571 g_return_val_if_fail(dest_path != NULL, TRUE);
573 if (dest_type == drop_dest_dir && provides(context, XdndDirectSave0))
575 leafname = get_xds_prop(context);
576 if (leafname)
578 if (strchr(leafname, '/'))
580 error = _("XDS protocol error: "
581 "leafname may not contain '/'\n");
582 null_g_free(&leafname);
584 else
586 char *dest_uri;
588 /* Not escaped. */
589 dest_uri = g_strconcat("file://",
590 our_host_name_for_dnd(),
591 dest_path, NULL);
593 set_xds_prop(context,
594 make_path(dest_uri, leafname));
596 g_free(dest_uri);
598 target = XdndDirectSave0;
599 g_dataset_set_data_full(context, "leafname",
600 leafname, g_free);
603 else
604 error = _(
605 "XdndDirectSave0 target provided, but the atom "
606 "XdndDirectSave0 (type text/plain) did not "
607 "contain a leafname\n");
609 else if (provides(context, text_uri_list))
610 target = text_uri_list;
611 else if (provides(context, text_x_moz_url))
612 target = text_x_moz_url;
613 else if (provides(context, xa_application_octet_stream))
614 target = xa_application_octet_stream;
615 else
617 if (dest_type == drop_dest_dir)
618 error = _("Sorry - I require a target type of "
619 "text/uri-list or XdndDirectSave0.");
620 else
621 error = _("Sorry - I require a target type of "
622 "text/uri-list or application/octet-stream.");
625 if (error)
627 gtk_drag_finish(context, FALSE, FALSE, time); /* Failure */
629 delayed_error("%s", error);
631 else
632 gtk_drag_get_data(widget, context, target, time);
634 return TRUE;
637 /* Called when a text/uri-list arrives */
638 static void desktop_drag_data_received(GtkWidget *widget,
639 GdkDragContext *context,
640 gint x,
641 gint y,
642 GtkSelectionData *selection_data,
643 guint info,
644 guint32 time,
645 FilerWindow *filer_window)
647 GList *uris, *next;
648 char *error_example = NULL;
649 gint dx, dy;
651 if (!selection_data->data)
653 /* Timeout? */
654 return;
657 if (pinboard_drag_in_progress)
659 pinboard_move_icons();
660 return;
663 gdk_window_get_position(widget->window, &dx, &dy);
664 x += dx;
665 y += dy;
667 uris = uri_list_to_glist(selection_data->data);
669 for (next = uris; next; next = next->next)
671 guchar *path;
673 path = get_local_path((EscapedPath *) next->data);
674 if (path)
676 pinboard_pin(path, NULL, x, y, NULL);
677 x += 64;
678 g_free(path);
680 else if (!error_example)
681 error_example = g_strdup(next->data);
683 g_free(next->data);
686 if (uris)
687 g_list_free(uris);
689 if (error_example)
691 delayed_error(_("Failed to add some items to the pinboard, "
692 "because they are on a remote machine. For example:\n"
693 "\n%s"), error_example);
694 g_free(error_example);
698 /* Convert Mozilla's text/x-moz-uri into a text/uri-list */
699 static void got_moz_uri(GtkWidget *widget,
700 GdkDragContext *context,
701 GtkSelectionData *selection_data,
702 guint32 time)
704 gchar *utf8, *uri_list, *eol;
706 utf8 = g_utf16_to_utf8((gunichar2 *) selection_data->data,
707 (glong) selection_data->length,
708 NULL, NULL, NULL);
710 eol = utf8 ? strchr(utf8, '\n') : NULL;
711 if (!eol)
713 delayed_error("Invalid UTF16 from text/x-moz-url target");
714 g_free(utf8);
715 gtk_drag_finish(context, FALSE, FALSE, time);
716 return;
719 *eol = '\0';
720 uri_list = g_strconcat(utf8, "\r\n", NULL);
721 g_free(utf8);
723 got_uri_list(widget, context, uri_list, time);
725 g_free(uri_list);
728 /* Called when some data arrives from the remote app (which we asked for
729 * in drag_drop).
731 static void drag_data_received(GtkWidget *widget,
732 GdkDragContext *context,
733 gint x,
734 gint y,
735 GtkSelectionData *selection_data,
736 guint info,
737 guint32 time,
738 gpointer user_data)
740 if (!selection_data->data)
742 /* Timeout? */
743 gtk_drag_finish(context, FALSE, FALSE, time); /* Failure */
744 return;
747 switch (info)
749 case TARGET_XDS:
750 got_data_xds_reply(widget, context,
751 selection_data, time);
752 break;
753 case TARGET_RAW:
754 got_data_raw(widget, context, selection_data, time);
755 break;
756 case TARGET_URI_LIST:
757 got_uri_list(widget, context, selection_data->data,
758 time);
759 break;
760 case TARGET_MOZ_URL:
761 got_moz_uri(widget, context, selection_data, time);
762 break;
763 default:
764 gtk_drag_finish(context, FALSE, FALSE, time);
765 delayed_error("drag_data_received: %s",
766 _("Unknown target"));
767 break;
771 static void got_data_xds_reply(GtkWidget *widget,
772 GdkDragContext *context,
773 GtkSelectionData *selection_data,
774 guint32 time)
776 gboolean mark_unsafe = TRUE;
777 char response = *selection_data->data;
778 const char *error = NULL;
779 char *dest_path;
781 dest_path = g_dataset_get_data(context, "drop_dest_path");
783 if (selection_data->length != 1)
784 response = '?';
786 if (response == 'F')
788 /* Sender couldn't save there - ask for another
789 * type if possible.
791 if (provides(context, xa_application_octet_stream))
793 mark_unsafe = FALSE; /* Wait and see */
795 gtk_drag_get_data(widget, context,
796 xa_application_octet_stream, time);
798 else
799 error = _("Remote app can't or won't send me "
800 "the data - sorry");
802 else if (response == 'S')
804 /* Success - data is saved */
805 mark_unsafe = FALSE; /* It really is safe */
806 gtk_drag_finish(context, TRUE, FALSE, time);
808 refresh_dirs(dest_path);
810 else if (response != 'E')
812 error = _("XDS protocol error: "
813 "return code should be 'S', 'F' or 'E'\n");
815 /* else: error has been reported by the sender */
817 if (mark_unsafe)
819 set_xds_prop(context, "");
820 /* Unsave also implies that the drag failed */
821 gtk_drag_finish(context, FALSE, FALSE, time);
824 if (error)
825 delayed_error("%s", error);
828 static void got_data_raw(GtkWidget *widget,
829 GdkDragContext *context,
830 GtkSelectionData *selection_data,
831 guint32 time)
833 const char *leafname;
834 int fd;
835 const char *error = NULL;
836 const char *dest_path;
838 g_return_if_fail(selection_data->data != NULL);
840 dest_path = g_dataset_get_data(context, "drop_dest_path");
842 if (context->action == GDK_ACTION_ASK)
844 gtk_drag_finish(context, FALSE, FALSE, time); /* Failure */
845 delayed_error(_("Sorry, can't display a menu of actions "
846 "for a remote file / raw data."));
847 return;
850 if (g_dataset_get_data(context, "drop_dest_type") == drop_dest_prog)
852 /* The data needs to be sent to an application */
853 run_with_data(dest_path,
854 selection_data->data, selection_data->length);
855 gtk_drag_finish(context, TRUE, FALSE, time); /* Success! */
856 return;
859 leafname = g_dataset_get_data(context, "leafname");
860 if (!leafname)
861 leafname = _("UntitledData");
863 fd = open(make_path(dest_path, leafname),
864 O_WRONLY | O_CREAT | O_EXCL | O_NOCTTY,
865 S_IRUSR | S_IRGRP | S_IROTH |
866 S_IWUSR | S_IWGRP | S_IWOTH);
868 if (fd == -1)
869 error = g_strerror(errno);
870 else
872 if (write(fd,
873 selection_data->data,
874 selection_data->length) == -1)
875 error = g_strerror(errno);
877 if (close(fd) == -1 && !error)
878 error = g_strerror(errno);
880 refresh_dirs(dest_path);
883 if (error)
885 if (provides(context, XdndDirectSave0))
886 set_xds_prop(context, "");
887 gtk_drag_finish(context, FALSE, FALSE, time); /* Failure */
888 delayed_error(_("Error saving file: %s"), error);
890 else
891 gtk_drag_finish(context, TRUE, FALSE, time); /* Success! */
894 static gboolean uri_is_local(const EscapedPath *uri)
896 char *path;
897 path = get_local_path(uri);
898 if (!path)
899 return FALSE;
900 g_free(path);
901 return TRUE;
904 /* Run the shell command 'command', replacing $1 with 'arg' */
905 static void run_with_argument(const char *dir,
906 const char *command,
907 const char *arg)
909 GPtrArray *argv;
911 argv = g_ptr_array_new();
913 g_ptr_array_add(argv, "sh");
914 g_ptr_array_add(argv, "-c");
915 g_ptr_array_add(argv, (char *) command);
916 g_ptr_array_add(argv, "sh");
917 g_ptr_array_add(argv, (char *) arg);
918 g_ptr_array_add(argv, NULL);
920 rox_spawn(dir, (const gchar **) argv->pdata);
922 g_ptr_array_free(argv, TRUE);
925 /* We've got a list of URIs from somewhere (probably another filer window).
926 * If the files are on the local machine then try to copy them ourselves,
927 * otherwise, if there was only one file and application/octet-stream was
928 * provided, get the data via the X server.
929 * For http:, https: or ftp: schemes, use the download handler.
931 static void got_uri_list(GtkWidget *widget,
932 GdkDragContext *context,
933 const char *selection_data,
934 guint32 time)
936 GList *uri_list;
937 const char *error = NULL;
938 GList *next_uri;
939 gboolean send_reply = TRUE;
940 char *dest_path;
941 char *type;
943 dest_path = g_dataset_get_data(context, "drop_dest_path");
944 type = g_dataset_get_data(context, "drop_dest_type");
946 uri_list = uri_list_to_glist(selection_data);
948 if (type == drop_dest_bookmark)
950 GList *next;
951 for (next = uri_list; next; next = next->next)
952 bookmarks_add_uri((EscapedPath *) next->data);
953 destroy_glist(&uri_list);
954 gtk_drag_finish(context, TRUE, FALSE, time); /* Success! */
955 return;
958 g_return_if_fail(dest_path != NULL);
960 if (!uri_list)
961 error = _("No URIs in the text/uri-list (nothing to do!)");
962 else if (context->action != GDK_ACTION_ASK && type == drop_dest_prog)
963 run_with_files(dest_path, uri_list);
964 else if ((!uri_list->next) && !uri_is_local(uri_list->data))
966 /* There is one URI in the list, and it's not on the local
967 * machine. Get it via the X server if possible.
970 if (provides(context, xa_application_octet_stream))
972 char *leaf;
973 leaf = strrchr(uri_list->data, '/');
974 if (leaf)
975 leaf++;
976 else
977 leaf = uri_list->data;
978 g_dataset_set_data_full(context, "leafname",
979 unescape_uri((EscapedPath *) leaf), g_free);
980 gtk_drag_get_data(widget, context,
981 xa_application_octet_stream, time);
982 send_reply = FALSE;
984 else if ((strncasecmp(uri_list->data, "http:", 5) == 0) ||
985 (strncasecmp(uri_list->data, "https:", 6) == 0) ||
986 (strncasecmp(uri_list->data, "ftp:", 4) == 0))
988 run_with_argument(dest_path,
989 o_dnd_uri_handler.value,
990 (char *) uri_list->data);
992 else
993 error = _("Can't get data from remote machine "
994 "(application/octet-stream not provided)");
996 else
998 GList *local_paths = NULL;
1000 /* Either one local URI, or a list. If everything in the list
1001 * isn't local then we are stuck.
1004 for (next_uri = uri_list; next_uri; next_uri = next_uri->next)
1006 char *path;
1008 path = get_local_path((EscapedPath *) next_uri->data);
1009 /*printf("%s -> %s\n", (char *) next_uri->data,
1010 path? path: "NULL");*/
1012 if (path)
1013 local_paths = g_list_append(local_paths,
1014 path);
1015 else
1016 error = _("Some of these files are on a "
1017 "different machine - they will be "
1018 "ignored - sorry");
1021 if (!local_paths)
1023 error = _("None of these files are on the local "
1024 "machine - I can't operate on multiple "
1025 "remote files - sorry.");
1027 else if (context->action == GDK_ACTION_ASK)
1028 prompt_action(local_paths, dest_path);
1029 else if (context->action == GDK_ACTION_MOVE)
1030 action_move(local_paths, dest_path, NULL, -1);
1031 else if (context->action == GDK_ACTION_COPY)
1032 action_copy(local_paths, dest_path, NULL, -1);
1033 else if (context->action == GDK_ACTION_LINK)
1034 action_link(local_paths, dest_path, NULL, TRUE);
1035 else
1036 error = _("Unknown action requested");
1038 destroy_glist(&local_paths);
1041 if (error)
1043 gtk_drag_finish(context, FALSE, FALSE, time); /* Failure */
1044 delayed_error(_("Error getting file list: %s"), error);
1046 else if (send_reply)
1047 gtk_drag_finish(context, TRUE, FALSE, time); /* Success! */
1049 destroy_glist(&uri_list);
1052 /* Called when an item from the ACTION_ASK menu is chosen */
1053 static void menuitem_response(gpointer data, guint action, GtkWidget *widget)
1055 if (action == MENU_MOVE)
1056 action_move(prompt_local_paths, prompt_dest_path, NULL, -1);
1057 else if (action == MENU_COPY)
1058 action_copy(prompt_local_paths, prompt_dest_path, NULL, -1);
1059 else if (action == MENU_LINK_REL)
1060 action_link(prompt_local_paths, prompt_dest_path, NULL, TRUE);
1061 else if (action == MENU_LINK_ABS)
1062 action_link(prompt_local_paths, prompt_dest_path, NULL, FALSE);
1065 /* When some local files are dropped somewhere with ACTION_ASK, this
1066 * function is called to display the menu.
1068 static void prompt_action(GList *paths, gchar *dest)
1070 GList *next;
1071 GdkEvent *event;
1073 if (prompt_local_paths)
1075 destroy_glist(&prompt_local_paths);
1076 null_g_free(&prompt_dest_path);
1079 /* Make a copy of the arguments */
1080 for (next = paths; next; next = next->next)
1081 prompt_local_paths = g_list_append(prompt_local_paths,
1082 g_strdup((gchar *) next->data));
1083 prompt_dest_path = g_strdup(dest);
1085 if (!dnd_menu)
1087 GtkItemFactory *item_factory;
1089 item_factory = menu_create(menu_def,
1090 sizeof(menu_def) / sizeof(*menu_def),
1091 "<dnd>", NULL);
1092 dnd_menu = gtk_item_factory_get_widget(item_factory, "<dnd>");
1095 /* Shade 'Set Icon' if there are multiple files */
1096 menu_set_items_shaded(dnd_menu, g_list_length(paths) != 1, 4, 1);
1098 event = gtk_get_current_event();
1099 show_popup_menu(dnd_menu, event, 1);
1100 if (event)
1101 gdk_event_free(event);
1105 /* SPRING-LOADING */
1107 /* This is the code that makes directories pop open if you hold a
1108 * file over them...
1110 * First, call dnd_spring_load(context) to arm the system.
1111 * After a timeout (1/2 a second) the dest_path directory will be
1112 * opened in a new window, unless dnd_spring_abort is called first.
1115 static gint spring_timeout = -1;
1116 static GdkDragContext *spring_context = NULL;
1117 static FilerWindow *spring_window = NULL;
1118 static FilerWindow *spring_src_window = NULL;
1120 void dnd_spring_load(GdkDragContext *context, FilerWindow *src_win)
1122 g_return_if_fail(context != NULL);
1124 if (!o_dnd_spring_open.int_value)
1125 return;
1127 if (spring_context)
1128 dnd_spring_abort();
1130 spring_context = context;
1131 g_object_ref(spring_context);
1132 spring_src_window = src_win;
1133 spring_timeout = gtk_timeout_add(
1134 o_dnd_spring_delay.int_value, spring_now, NULL);
1137 void dnd_spring_abort(void)
1139 if (!spring_context)
1140 return;
1142 g_object_unref(spring_context);
1143 spring_context = NULL;
1144 gtk_timeout_remove(spring_timeout);
1147 /* If all mod keys are released, no buttons are pressed, and the
1148 * mouse is outside the spring window, then close it.
1150 static gboolean spring_check_idle(gpointer data)
1152 int p_x, p_y;
1154 if (!spring_window)
1155 return FALSE;
1157 if (!get_pointer_xy(&p_x, &p_y))
1160 GdkWindow *win = spring_window->window->window;
1161 int x, y;
1162 int w, h;
1164 gdk_window_get_position(win, &x, &y);
1165 gdk_window_get_size(win, &w, &h);
1167 if (p_x < x || p_x > x + w || p_y < y || p_y > y + h)
1171 gtk_widget_destroy(spring_window->window);
1172 return FALSE; /* Got it! */
1175 return TRUE; /* Try again later */
1178 static gboolean spring_now(gpointer data)
1180 const char *type;
1181 const guchar *dest_path;
1182 gint x, y;
1184 g_return_val_if_fail(spring_context != NULL, FALSE);
1185 g_return_val_if_fail(!spring_in_progress, FALSE);
1187 type = g_dataset_get_data(spring_context, "drop_dest_type");
1188 if (type == drop_dest_bookmark)
1190 bookmarks_edit();
1191 goto out;
1194 dest_path = g_dataset_get_data(spring_context, "drop_dest_path");
1195 g_return_val_if_fail(dest_path != NULL, FALSE);
1198 * Note: Due to a bug in gtk, if a window disappears during
1199 * a drag and the pointer moves over where the window was,
1200 * the sender crashes! Therefore, do not close any windows
1201 * while dragging! (fixed in later versions)
1204 if (spring_window)
1205 gtk_widget_destroy(spring_window->window);
1208 get_pointer_xy(&x, &y);
1210 spring_in_progress++;
1211 if (spring_window)
1213 view_cursor_to_iter(spring_window->view, NULL);
1214 filer_change_to(spring_window, dest_path, NULL);
1215 /* DON'T move the window. Gtk+ sometimes doesn't
1216 * notice :-(
1219 else
1221 spring_window = filer_opendir(dest_path,
1222 spring_src_window, NULL);
1223 if (spring_window)
1225 gtk_timeout_add(500, spring_check_idle, NULL);
1226 g_signal_connect(spring_window->window, "destroy",
1227 G_CALLBACK(spring_win_destroyed), NULL);
1228 centre_window(spring_window->window->window, x, y);
1231 spring_in_progress--;
1233 out:
1234 dnd_spring_abort();
1236 return FALSE;
1239 static void spring_win_destroyed(GtkWidget *widget, gpointer data)
1241 spring_window = NULL;
1244 /* HANDLING MOTION EVENTS */
1246 /* If not-NULL, then this widget has a grab */
1247 static GtkWidget *motion_widget = NULL;
1249 /* If TRUE, we must gdk_pointer_ungrab() too when finishing */
1250 static gboolean motion_pointer_grab = FALSE;
1252 /* Call this on a button press event. It stores the mouse position
1253 * as the start of the new drag and returns TRUE if all is well.
1254 * Further motions events are disabled at this point - you must
1255 * then call dnd_motion_start() to set the type of motion expected.
1256 * Grabs the widget on the first press.
1258 * If the system is not ready to handle a motion event (because a
1259 * button is already held down?) it does nothing and returns FALSE.
1261 * If the event is not a single click then it simply returns TRUE.
1263 gboolean dnd_motion_press(GtkWidget *widget, GdkEventButton *event)
1265 if (event->type != GDK_BUTTON_PRESS)
1266 return TRUE; /* Not a click event! */
1268 motion_buttons_pressed++;
1269 if (motion_buttons_pressed == 1)
1271 /* g_print("[ grab! ]\n"); */
1272 gtk_grab_add(widget);
1273 motion_widget = widget;
1276 if (motion_state != MOTION_NONE)
1277 return FALSE; /* Ignore clicks - we're busy! */
1279 motion_state = MOTION_DISABLED;
1280 drag_start_x = event->x_root;
1281 drag_start_y = event->y_root;
1283 return TRUE;
1286 /* After the button press event, decide what kind of motion is expected.
1287 * If you don't call this then the motion system is disabled - call
1288 * dnd_motion_release() to reset it.
1290 * Note: If you open a popup menu or start DND call dnd_motion_ungrab()
1291 * instead.
1293 void dnd_motion_start(MotionType motion)
1295 g_return_if_fail(motion_state == MOTION_DISABLED);
1297 motion_state = motion;
1300 /* Call this on a button release event. If some buttons are still pressed,
1301 * returns TRUE and does nothing.
1303 * Otherwise, it resets the motion system to be ready again and returns TRUE.
1305 * If the motion system wasn't being used (MOTION_NONE) then it does nothing
1306 * and returns FALSE - process the release event yourself as it isn't part
1307 * of a motion. This also happens if a motion was primed but never happened.
1309 gboolean dnd_motion_release(GdkEventButton *event)
1311 MotionType motion = motion_state;
1312 gint drag_threshold;
1313 int dx, dy;
1315 if (motion_buttons_pressed == 0)
1316 return TRUE; /* We were disabled */
1318 if (motion_buttons_pressed == 1)
1319 dnd_motion_ungrab();
1320 else
1322 motion_buttons_pressed--;
1323 return TRUE;
1326 if (motion == MOTION_REPOSITION || motion == MOTION_DISABLED)
1327 return TRUE; /* Already done something - eat the event */
1329 /* Eat release events that happen too far from the click
1330 * source. Otherwise, allow the caller to treat this as a click
1331 * that never became a motion.
1333 dx = event->x_root - drag_start_x;
1334 dy = event->y_root - drag_start_y;
1336 g_object_get(gtk_settings_get_default(),
1337 "gtk-dnd-drag-threshold", &drag_threshold,
1338 NULL);
1340 return ABS(dx) > drag_threshold || ABS(dy) > drag_threshold;
1343 /* Use this to disable the motion system. The system will be reset once
1344 * all mouse buttons are released.
1346 void dnd_motion_disable(void)
1348 g_return_if_fail(motion_state != MOTION_NONE &&
1349 motion_state != MOTION_DISABLED);
1351 motion_state = MOTION_DISABLED;
1354 /* Use this if something else is going to grab the pointer so that
1355 * we won't get any more motion or release events.
1357 void dnd_motion_ungrab(void)
1359 if (motion_buttons_pressed > 0)
1361 if (motion_pointer_grab)
1363 gdk_pointer_ungrab(GDK_CURRENT_TIME);
1364 motion_pointer_grab = FALSE;
1365 /* g_print("[ ungrab_pointer ]\n"); */
1367 gtk_grab_remove(motion_widget);
1368 motion_widget = NULL;
1369 motion_buttons_pressed = 0;
1370 /* g_print("[ ungrab ]\n"); */
1373 motion_state = MOTION_NONE;
1376 /* Call this on motion events. If the mouse position is far enough
1377 * from the click position, returns TRUE and does dnd_motion_ungrab().
1378 * You should then start regular drag-and-drop.
1380 * Otherwise, returns FALSE.
1382 gboolean dnd_motion_moved(GdkEventMotion *event)
1384 gint drag_threshold;
1385 int dx, dy;
1387 g_object_get(gtk_settings_get_default(),
1388 "gtk-dnd-drag-threshold", &drag_threshold,
1389 NULL);
1391 dx = event->x_root - drag_start_x;
1392 dy = event->y_root - drag_start_y;
1394 if (ABS(dx) <= drag_threshold && ABS(dy) <= drag_threshold)
1395 return FALSE; /* Not far enough */
1397 dnd_motion_ungrab();
1399 return TRUE;
1402 /* Normally, the X server will automatically grab the pointer on a
1403 * button press and ungrab on release. However, if the grab widget
1404 * is reparented then call this to re-aquire the grab.
1406 void dnd_motion_grab_pointer(void)
1408 g_return_if_fail(motion_widget != NULL);
1410 gdk_pointer_grab(motion_widget->window, FALSE,
1411 GDK_POINTER_MOTION_MASK |
1412 GDK_BUTTON_RELEASE_MASK,
1413 FALSE, NULL, GDK_CURRENT_TIME);
1415 motion_pointer_grab = TRUE;