r1602: Moved a wrongly placed decl.
[rox-filer.git] / ROX-Filer / src / dnd.c
blob3369039208a58665ecf767bc72cbb6a22eb9f737
1 /*
2 * $Id$
4 * ROX-Filer, filer for the ROX desktop project
5 * Copyright (C) 2002, 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 "collection.h"
42 #include "dnd.h"
43 #include "type.h"
44 #include "filer.h"
45 #include "action.h"
46 #include "pixmaps.h"
47 #include "gui_support.h"
48 #include "support.h"
49 #include "options.h"
50 #include "run.h"
51 #include "pinboard.h"
52 #include "dir.h"
53 #include "diritem.h"
54 #include "usericons.h"
55 #include "menu.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 gboolean drag_motion(GtkWidget *widget,
76 GdkDragContext *context,
77 gint x,
78 gint y,
79 guint time,
80 FilerWindow *filer_window);
81 static void drag_leave(GtkWidget *widget,
82 GdkDragContext *context,
83 guint32 time,
84 FilerWindow *filer_window);
85 static void desktop_drag_data_received(GtkWidget *widget,
86 GdkDragContext *context,
87 gint x,
88 gint y,
89 GtkSelectionData *selection_data,
90 guint info,
91 guint32 time,
92 FilerWindow *filer_window);
93 static void got_data_xds_reply(GtkWidget *widget,
94 GdkDragContext *context,
95 GtkSelectionData *selection_data,
96 guint32 time);
97 static void got_data_raw(GtkWidget *widget,
98 GdkDragContext *context,
99 GtkSelectionData *selection_data,
100 guint32 time);
101 static void got_uri_list(GtkWidget *widget,
102 GdkDragContext *context,
103 GtkSelectionData *selection_data,
104 guint32 time);
105 static void drag_end(GtkWidget *widget,
106 GdkDragContext *context,
107 FilerWindow *filer_window);
108 static gboolean drag_drop(GtkWidget *widget,
109 GdkDragContext *context,
110 gint x,
111 gint y,
112 guint time,
113 gpointer data);
114 static void drag_data_received(GtkWidget *widget,
115 GdkDragContext *context,
116 gint x,
117 gint y,
118 GtkSelectionData *selection_data,
119 guint info,
120 guint32 time,
121 gpointer user_data);
122 static gboolean spring_now(gpointer data);
123 static void spring_win_destroyed(GtkWidget *widget, gpointer data);
124 static void menuitem_response(gpointer data, guint action, GtkWidget *widget);
125 static void prompt_action(GList *paths, gchar *dest);
127 typedef enum {
128 MENU_COPY,
129 MENU_MOVE,
130 MENU_LINK,
131 MENU_SET_ICON,
132 } MenuActionType;
134 #undef N_
135 #define N_(x) x
136 static GtkItemFactoryEntry menu_def[] = {
137 {N_("Copy"), NULL, menuitem_response, MENU_COPY, NULL},
138 {N_("Move"), NULL, menuitem_response, MENU_MOVE, NULL},
139 {N_("Link"), NULL, menuitem_response, MENU_LINK, NULL},
140 {"", NULL, NULL, 0, "<Separator>"},
141 {N_("Set Icon"), NULL, menuitem_response, MENU_SET_ICON, NULL},
143 static GtkWidget *dnd_menu = NULL;
145 /* The handler of the signal handler for scroll events.
146 * This is used to cancel spring loading when autoscrolling is used.
148 static gulong scrolled_signal = -1;
149 static GtkObject *scrolled_adj = NULL; /* The object watched */
151 /* Possible values for drop_dest_type (can also be NULL).
152 * In either case, drop_dest_path is the app/file/dir to use.
154 const char *drop_dest_prog = "drop_dest_prog"; /* Run a program */
155 const char *drop_dest_dir = "drop_dest_dir"; /* Save to path */
157 GdkAtom XdndDirectSave0;
158 GdkAtom xa_text_plain;
159 GdkAtom text_uri_list;
160 GdkAtom application_octet_stream;
161 GdkAtom xa_string; /* Not actually used for DnD, but the others are here! */
163 static Option o_dnd_drag_to_icons;
164 Option o_dnd_spring_open;
165 static Option o_dnd_spring_delay;
166 static Option o_dnd_middle_menu;
168 void dnd_init(void)
170 XdndDirectSave0 = gdk_atom_intern("XdndDirectSave0", FALSE);
171 xa_text_plain = gdk_atom_intern("text/plain", FALSE);
172 text_uri_list = gdk_atom_intern("text/uri-list", FALSE);
173 application_octet_stream = gdk_atom_intern("application/octet-stream",
174 FALSE);
175 xa_string = gdk_atom_intern("STRING", FALSE);
177 option_add_int(&o_dnd_drag_to_icons, "dnd_drag_to_icons", 1);
178 option_add_int(&o_dnd_spring_open, "dnd_spring_open", 0);
179 option_add_int(&o_dnd_spring_delay, "dnd_spring_delay", 400);
180 option_add_int(&o_dnd_middle_menu, "dnd_middle_menu", TRUE);
183 /* SUPPORT FUNCTIONS */
185 /* Set the XdndDirectSave0 property on the source window for this context */
186 static void set_xds_prop(GdkDragContext *context, const char *text)
188 gdk_property_change(context->source_window,
189 XdndDirectSave0,
190 xa_text_plain, 8,
191 GDK_PROP_MODE_REPLACE,
192 text,
193 strlen(text));
196 static char *get_xds_prop(GdkDragContext *context)
198 guchar *prop_text;
199 gint length;
201 if (gdk_property_get(context->source_window,
202 XdndDirectSave0,
203 xa_text_plain,
204 0, MAXURILEN,
205 FALSE,
206 NULL, NULL,
207 &length, &prop_text) && prop_text)
209 /* Terminate the string */
210 prop_text = g_realloc(prop_text, length + 1);
211 prop_text[length] = '\0';
212 return prop_text;
215 return NULL;
218 /* Is the sender willing to supply this target type? */
219 gboolean provides(GdkDragContext *context, GdkAtom target)
221 GList *targets = context->targets;
223 while (targets && ((GdkAtom) targets->data != target))
224 targets = targets->next;
226 return targets != NULL;
229 /* Convert a list of URIs into a list of strings.
230 * Lines beginning with # are skipped.
231 * The text block passed in is zero terminated (after the final CRLF)
233 GList *uri_list_to_glist(const char *uri_list)
235 GList *list = NULL;
237 while (*uri_list)
239 char *linebreak;
240 char *uri;
241 int length;
243 linebreak = strchr(uri_list, 13);
245 if (!linebreak || linebreak[1] != 10)
247 delayed_error("uri_list_to_glist: %s",
248 _("Incorrect or missing line "
249 "break in text/uri-list data"));
250 return list;
253 length = linebreak - uri_list;
255 if (length && uri_list[0] != '#')
257 uri = g_strndup(uri_list, length);
258 list = g_list_append(list, uri);
261 uri_list = linebreak + 2;
264 return list;
267 /* DRAGGING FROM US */
269 /* The user has held the mouse button down over a group of item and moved -
270 * start a drag. 'uri_list' is copied, so you can delete it straight away.
272 void drag_selection(GtkWidget *widget, GdkEventMotion *event, guchar *uri_list)
274 GdkDragContext *context;
275 GdkDragAction actions;
276 GtkTargetList *target_list;
277 GtkTargetEntry target_table[] = {
278 {"text/uri-list", 0, TARGET_URI_LIST},
281 if (event->state & GDK_BUTTON1_MASK)
282 actions = GDK_ACTION_COPY | GDK_ACTION_MOVE
283 | GDK_ACTION_LINK | GDK_ACTION_ASK;
284 else
286 if (o_dnd_middle_menu.int_value)
287 actions = GDK_ACTION_ASK;
288 else
289 actions = GDK_ACTION_MOVE;
292 target_list = gtk_target_list_new(target_table, 1);
294 context = gtk_drag_begin(widget,
295 target_list,
296 actions,
297 (event->state & GDK_BUTTON1_MASK) ? 1 :
298 (event->state & GDK_BUTTON2_MASK) ? 2 : 3,
299 (GdkEvent *) event);
301 g_dataset_set_data_full(context, "uri_list",
302 g_strdup(uri_list), g_free);
304 gtk_drag_set_icon_pixbuf(context, im_multiple->pixbuf, 0, 0);
307 /* Copy/Load this item into another directory/application */
308 void drag_one_item(GtkWidget *widget,
309 GdkEventMotion *event,
310 const guchar *full_path,
311 DirItem *item,
312 MaskedPixmap *image)
314 guchar *uri;
315 GdkDragContext *context;
316 GdkDragAction actions;
317 GtkTargetList *target_list;
318 GtkTargetEntry target_table[] = {
319 {"text/uri-list", 0, TARGET_URI_LIST},
320 {"application/octet-stream", 0, TARGET_RAW},
321 {"", 0, TARGET_RAW},
324 g_return_if_fail(full_path != NULL);
325 g_return_if_fail(item != NULL);
327 if (!image)
328 image = item->image;
330 if (item->base_type == TYPE_FILE)
332 MIME_type *t = item->mime_type;
334 target_table[2].target = g_strconcat(t->media_type, "/",
335 t->subtype, NULL);
336 target_list = gtk_target_list_new(target_table, 3);
337 g_free(target_table[2].target);
339 else
340 target_list = gtk_target_list_new(target_table, 1);
342 if (event->state & GDK_BUTTON1_MASK)
343 actions = GDK_ACTION_COPY | GDK_ACTION_ASK
344 | GDK_ACTION_MOVE | GDK_ACTION_LINK;
345 else
347 if (o_dnd_middle_menu.int_value)
348 actions = GDK_ACTION_ASK;
349 else
350 actions = GDK_ACTION_MOVE;
353 context = gtk_drag_begin(widget,
354 target_list,
355 actions,
356 (event->state & GDK_BUTTON1_MASK) ? 1 :
357 (event->state & GDK_BUTTON2_MASK) ? 2 : 3,
358 (GdkEvent *) event);
360 g_dataset_set_data_full(context, "full_path",
361 g_strdup(full_path), g_free);
362 uri = g_strconcat("file://", our_host_name_for_dnd(),
363 full_path, "\r\n", NULL);
364 g_dataset_set_data_full(context, "uri_list", uri, g_free);
366 g_return_if_fail(image != NULL);
368 gtk_drag_set_icon_pixbuf(context, image->pixbuf, 0, 0);
371 static void drag_end(GtkWidget *widget,
372 GdkDragContext *context,
373 FilerWindow *filer_window)
375 if (filer_window->temp_item_selected)
377 view_clear_selection(filer_window->view);
378 filer_window->temp_item_selected = FALSE;
382 /* Called when a remote app wants us to send it some data.
383 * TODO: Maybe we should handle errors better (ie, let the remote app know
384 * the drag has failed)?
386 void drag_data_get(GtkWidget *widget,
387 GdkDragContext *context,
388 GtkSelectionData *selection_data,
389 guint info,
390 guint32 time,
391 gpointer data)
393 char *to_send = "E"; /* Default to sending an error */
394 long to_send_length = 1;
395 gboolean delete_once_sent = FALSE;
396 GdkAtom type;
397 guchar *path;
399 type = gdk_x11_xatom_to_atom(XA_STRING);
401 switch (info)
403 case TARGET_RAW:
404 path = g_dataset_get_data(context, "full_path");
405 if (path && load_file(path, &to_send, &to_send_length))
407 delete_once_sent = TRUE;
408 type = selection_data->target;
409 break;
411 g_warning("drag_data_get: Can't find path!\n");
412 return;
413 case TARGET_URI_LIST:
414 to_send = g_dataset_get_data(context, "uri_list");
415 to_send_length = strlen(to_send);
416 type = text_uri_list; /* (needed for xine) */
417 delete_once_sent = FALSE;
418 break;
419 default:
420 delayed_error("drag_data_get: %s",
421 _("Internal error - bad info type"));
422 break;
425 gtk_selection_data_set(selection_data,
426 type,
428 to_send,
429 to_send_length);
431 if (delete_once_sent)
432 g_free(to_send);
435 /* DRAGGING TO US */
437 /* Set up this widget as a drop-target.
438 * Does not attach any motion handlers.
440 void make_drop_target(GtkWidget *widget, GtkDestDefaults defaults)
442 GtkTargetEntry target_table[] =
444 {"text/uri-list", 0, TARGET_URI_LIST},
445 {"XdndDirectSave0", 0, TARGET_XDS},
446 {"application/octet-stream", 0, TARGET_RAW},
449 gtk_drag_dest_set(widget,
450 defaults,
451 target_table,
452 sizeof(target_table) / sizeof(*target_table),
453 GDK_ACTION_COPY | GDK_ACTION_ASK | GDK_ACTION_MOVE
454 | GDK_ACTION_LINK | GDK_ACTION_PRIVATE);
456 g_signal_connect(widget, "drag_drop", G_CALLBACK(drag_drop), NULL);
457 g_signal_connect(widget, "drag_data_received",
458 G_CALLBACK(drag_data_received), NULL);
461 /* Set up this filer window as a drop target. Called once, when the
462 * filer window is first created.
464 void drag_set_dest(FilerWindow *filer_window)
466 GtkWidget *widget = GTK_WIDGET(filer_window->collection);
468 make_drop_target(widget, 0);
470 g_signal_connect(widget, "drag_motion",
471 G_CALLBACK(drag_motion), filer_window);
472 g_signal_connect(widget, "drag_leave",
473 G_CALLBACK(drag_leave), filer_window);
474 g_signal_connect(widget, "drag_end",
475 G_CALLBACK(drag_end), filer_window);
478 /* Like drag_set_dest, but for a pinboard-type widget */
479 void drag_set_pinboard_dest(GtkWidget *widget)
481 GtkTargetEntry target_table[] = {
482 {"text/uri-list", 0, TARGET_URI_LIST},
485 gtk_drag_dest_set(widget,
486 GTK_DEST_DEFAULT_DROP,
487 target_table,
488 sizeof(target_table) / sizeof(*target_table),
489 GDK_ACTION_LINK);
490 g_signal_connect(widget, "drag_data_received",
491 G_CALLBACK(desktop_drag_data_received), NULL);
494 /* Called during the drag when the mouse is in a widget registered
495 * as a drop target. Returns TRUE if we can accept the drop.
497 static gboolean drag_motion(GtkWidget *widget,
498 GdkDragContext *context,
499 gint x,
500 gint y,
501 guint time,
502 FilerWindow *filer_window)
504 DirItem *item;
505 int item_number;
506 GdkDragAction action = context->suggested_action;
507 char *new_path = NULL;
508 const char *type = NULL;
509 gboolean retval = FALSE;
511 if (o_dnd_drag_to_icons.int_value)
512 item_number = collection_get_item(filer_window->collection,
513 x, y);
514 else
515 item_number = -1;
517 item = item_number >= 0
518 ? (DirItem *) filer_window->collection->items[item_number].data
519 : NULL;
521 if (item && filer_window->collection->items[item_number].selected)
522 type = NULL;
523 else
524 type = dnd_motion_item(context, &item);
526 if (!type)
527 item = NULL;
529 /* Don't allow drops to non-writeable directories. BUT, still
530 * allow drops on non-writeable SUBdirectories so that we can
531 * do the spring-open thing.
533 if (item && type == drop_dest_dir &&
534 !(item->flags & ITEM_FLAG_APPDIR))
536 #if 0
537 /* XXX: This is needed so that directories don't
538 * spring open while we scroll. Should go in
539 * view_collection.c, I think.
541 GtkObject *vadj = GTK_OBJECT(filer_window->collection->vadj);
543 /* Subdir: prepare for spring-open */
544 if (scrolled_adj != vadj)
546 if (scrolled_adj)
547 gtk_signal_disconnect(scrolled_adj,
548 scrolled_signal);
549 scrolled_adj = vadj;
550 scrolled_signal = gtk_signal_connect(
551 scrolled_adj,
552 "value_changed",
553 GTK_SIGNAL_FUNC(scrolled),
554 filer_window->collection);
556 #endif
557 dnd_spring_load(context, filer_window);
559 else
560 dnd_spring_abort();
562 if (item)
564 collection_set_cursor_item(filer_window->collection,
565 item_number);
567 else
569 collection_set_cursor_item(filer_window->collection, -1);
571 /* Disallow background drops within a single window */
572 if (type && gtk_drag_get_source_widget(context) == widget)
573 type = NULL;
576 if (type)
578 if (item)
579 new_path = make_path(filer_window->sym_path,
580 item->leafname)->str;
581 else
582 new_path = filer_window->sym_path;
585 g_dataset_set_data(context, "drop_dest_type", (gpointer) type);
586 if (type)
588 gdk_drag_status(context, action, time);
589 g_dataset_set_data_full(context, "drop_dest_path",
590 g_strdup(new_path), g_free);
591 retval = TRUE;
594 return retval;
597 /* item is the item the file is held over, NULL for directory background.
598 * 'item' may be NULL on exit if the drop should be treated as onto the
599 * background. Disallow drags to a selected icon before calling this.
601 * Returns NULL to reject the drop, or drop_dest_prog/drop_dest_dir to
602 * accept. Build the path based on item.
604 const guchar *dnd_motion_item(GdkDragContext *context, DirItem **item_p)
606 DirItem *item = *item_p;
608 if (item)
610 /* If we didn't drop onto a directory, application or
611 * executable file then act as though the drop is to the
612 * window background.
614 if (item->base_type != TYPE_DIRECTORY
615 && !(item->mime_type == application_executable))
617 item = NULL;
618 *item_p = NULL;
622 if (!item)
624 /* Drop onto the window background */
626 return drop_dest_dir;
629 /* Drop onto a program/directory of some sort */
631 if (item->base_type == TYPE_DIRECTORY &&
632 !(item->flags & ITEM_FLAG_APPDIR))
634 /* A normal directory */
635 if (provides(context, text_uri_list) ||
636 provides(context, XdndDirectSave0))
637 return drop_dest_dir;
639 else
641 if (provides(context, text_uri_list) ||
642 provides(context, application_octet_stream))
643 return drop_dest_prog;
646 return NULL;
649 /* Remove highlights */
650 static void drag_leave(GtkWidget *widget,
651 GdkDragContext *context,
652 guint32 time,
653 FilerWindow *filer_window)
655 dnd_spring_abort();
656 if (scrolled_adj)
658 g_signal_handler_disconnect(scrolled_adj, scrolled_signal);
659 scrolled_adj = NULL;
663 /* User has tried to drop some data on us. Decide what format we would
664 * like the data in.
666 static gboolean drag_drop(GtkWidget *widget,
667 GdkDragContext *context,
668 gint x,
669 gint y,
670 guint time,
671 gpointer data)
673 const char *error = NULL;
674 char *leafname = NULL;
675 GdkAtom target = GDK_NONE;
676 char *dest_path;
677 char *dest_type = NULL;
679 dest_path = g_dataset_get_data(context, "drop_dest_path");
680 dest_type = g_dataset_get_data(context, "drop_dest_type");
682 g_return_val_if_fail(dest_path != NULL, TRUE);
684 if (dest_type == drop_dest_dir && provides(context, XdndDirectSave0))
686 leafname = get_xds_prop(context);
687 if (leafname)
689 if (strchr(leafname, '/'))
691 error = _("XDS protocol error: "
692 "leafname may not contain '/'\n");
693 g_free(leafname);
695 leafname = NULL;
697 else
699 GString *uri;
701 uri = g_string_new(NULL);
702 g_string_sprintf(uri, "file://%s%s",
703 our_host_name_for_dnd(),
704 make_path(dest_path,
705 leafname)->str);
706 set_xds_prop(context, uri->str);
707 g_string_free(uri, TRUE);
709 target = XdndDirectSave0;
710 g_dataset_set_data_full(context, "leafname",
711 leafname, g_free);
714 else
715 error = _(
716 "XdndDirectSave0 target provided, but the atom "
717 "XdndDirectSave0 (type text/plain) did not "
718 "contain a leafname\n");
720 else if (provides(context, text_uri_list))
721 target = text_uri_list;
722 else if (provides(context, application_octet_stream))
723 target = application_octet_stream;
724 else
726 if (dest_type == drop_dest_dir)
727 error = _("Sorry - I require a target type of "
728 "text/uri-list or XdndDirectSave0.");
729 else
730 error = _("Sorry - I require a target type of "
731 "text/uri-list or application/octet-stream.");
734 if (error)
736 gtk_drag_finish(context, FALSE, FALSE, time); /* Failure */
738 delayed_error("%s", error);
740 else
741 gtk_drag_get_data(widget, context, target, time);
743 return TRUE;
746 /* Called when a text/uri-list arrives */
747 static void desktop_drag_data_received(GtkWidget *widget,
748 GdkDragContext *context,
749 gint x,
750 gint y,
751 GtkSelectionData *selection_data,
752 guint info,
753 guint32 time,
754 FilerWindow *filer_window)
756 GList *uris, *next;
757 gint dx, dy;
759 if (!selection_data->data)
761 /* Timeout? */
762 return;
765 if (pinboard_drag_in_progress)
767 pinboard_move_icons();
768 return;
771 gdk_window_get_position(widget->window, &dx, &dy);
772 x += dx;
773 y += dy;
775 uris = uri_list_to_glist(selection_data->data);
777 for (next = uris; next; next = next->next)
779 const guchar *path;
781 path = get_local_path((gchar *) next->data);
782 if (path)
784 pinboard_pin(path, NULL, x, y);
785 x += 64;
788 g_free(next->data);
791 if (uris)
792 g_list_free(uris);
795 /* Called when some data arrives from the remote app (which we asked for
796 * in drag_drop).
798 static void drag_data_received(GtkWidget *widget,
799 GdkDragContext *context,
800 gint x,
801 gint y,
802 GtkSelectionData *selection_data,
803 guint info,
804 guint32 time,
805 gpointer user_data)
807 if (!selection_data->data)
809 /* Timeout? */
810 gtk_drag_finish(context, FALSE, FALSE, time); /* Failure */
811 return;
814 switch (info)
816 case TARGET_XDS:
817 got_data_xds_reply(widget, context,
818 selection_data, time);
819 break;
820 case TARGET_RAW:
821 got_data_raw(widget, context, selection_data, time);
822 break;
823 case TARGET_URI_LIST:
824 got_uri_list(widget, context, selection_data, time);
825 break;
826 default:
827 gtk_drag_finish(context, FALSE, FALSE, time);
828 delayed_error("drag_data_received: %s",
829 _("Unknown target"));
830 break;
834 static void got_data_xds_reply(GtkWidget *widget,
835 GdkDragContext *context,
836 GtkSelectionData *selection_data,
837 guint32 time)
839 gboolean mark_unsafe = TRUE;
840 char response = *selection_data->data;
841 const char *error = NULL;
842 char *dest_path;
844 dest_path = g_dataset_get_data(context, "drop_dest_path");
846 if (selection_data->length != 1)
847 response = '?';
849 if (response == 'F')
851 /* Sender couldn't save there - ask for another
852 * type if possible.
854 if (provides(context, application_octet_stream))
856 mark_unsafe = FALSE; /* Wait and see */
858 gtk_drag_get_data(widget, context,
859 application_octet_stream, time);
861 else
862 error = _("Remote app can't or won't send me "
863 "the data - sorry");
865 else if (response == 'S')
867 /* Success - data is saved */
868 mark_unsafe = FALSE; /* It really is safe */
869 gtk_drag_finish(context, TRUE, FALSE, time);
871 refresh_dirs(dest_path);
873 else if (response != 'E')
875 error = _("XDS protocol error: "
876 "return code should be 'S', 'F' or 'E'\n");
878 /* else: error has been reported by the sender */
880 if (mark_unsafe)
882 set_xds_prop(context, "");
883 /* Unsave also implies that the drag failed */
884 gtk_drag_finish(context, FALSE, FALSE, time);
887 if (error)
888 delayed_error("%s", error);
891 static void got_data_raw(GtkWidget *widget,
892 GdkDragContext *context,
893 GtkSelectionData *selection_data,
894 guint32 time)
896 const char *leafname;
897 int fd;
898 const char *error = NULL;
899 const char *dest_path;
901 g_return_if_fail(selection_data->data != NULL);
903 dest_path = g_dataset_get_data(context, "drop_dest_path");
905 if (context->action == GDK_ACTION_ASK)
907 gtk_drag_finish(context, FALSE, FALSE, time); /* Failure */
908 delayed_error(_("Sorry, can't display a menu of actions "
909 "for a remote file / raw data."));
910 return;
913 if (g_dataset_get_data(context, "drop_dest_type") == drop_dest_prog)
915 /* The data needs to be sent to an application */
916 run_with_data(dest_path,
917 selection_data->data, selection_data->length);
918 gtk_drag_finish(context, TRUE, FALSE, time); /* Success! */
919 return;
922 leafname = g_dataset_get_data(context, "leafname");
923 if (!leafname)
924 leafname = _("UntitledData");
926 fd = open(make_path(dest_path, leafname)->str,
927 O_WRONLY | O_CREAT | O_EXCL | O_NOCTTY,
928 S_IRUSR | S_IRGRP | S_IROTH |
929 S_IWUSR | S_IWGRP | S_IWOTH);
931 if (fd == -1)
932 error = g_strerror(errno);
933 else
935 if (write(fd,
936 selection_data->data,
937 selection_data->length) == -1)
938 error = g_strerror(errno);
940 if (close(fd) == -1 && !error)
941 error = g_strerror(errno);
943 refresh_dirs(dest_path);
946 if (error)
948 if (provides(context, XdndDirectSave0))
949 set_xds_prop(context, "");
950 gtk_drag_finish(context, FALSE, FALSE, time); /* Failure */
951 delayed_error(_("Error saving file: %s"), error);
953 else
954 gtk_drag_finish(context, TRUE, FALSE, time); /* Success! */
957 /* We've got a list of URIs from somewhere (probably another filer window).
958 * If the files are on the local machine then try to copy them ourselves,
959 * otherwise, if there was only one file and application/octet-stream was
960 * provided, get the data via the X server.
962 static void got_uri_list(GtkWidget *widget,
963 GdkDragContext *context,
964 GtkSelectionData *selection_data,
965 guint32 time)
967 GList *uri_list;
968 const char *error = NULL;
969 GList *next_uri;
970 gboolean send_reply = TRUE;
971 char *dest_path;
972 char *type;
974 dest_path = g_dataset_get_data(context, "drop_dest_path");
975 type = g_dataset_get_data(context, "drop_dest_type");
977 g_return_if_fail(dest_path != NULL);
979 uri_list = uri_list_to_glist(selection_data->data);
981 if (!uri_list)
982 error = _("No URIs in the text/uri-list (nothing to do!)");
983 else if (context->action != GDK_ACTION_ASK && type == drop_dest_prog)
984 run_with_files(dest_path, uri_list);
985 else if ((!uri_list->next) && (!get_local_path(uri_list->data)))
987 /* There is one URI in the list, and it's not on the local
988 * machine. Get it via the X server if possible.
991 if (provides(context, application_octet_stream))
993 char *leaf;
994 leaf = strrchr(uri_list->data, '/');
995 if (leaf)
996 leaf++;
997 else
998 leaf = uri_list->data;
999 g_dataset_set_data_full(context, "leafname",
1000 g_strdup(leaf), g_free);
1001 gtk_drag_get_data(widget, context,
1002 application_octet_stream, time);
1003 send_reply = FALSE;
1005 else
1006 error = _("Can't get data from remote machine "
1007 "(application/octet-stream not provided)");
1009 else
1011 GList *local_paths = NULL;
1012 GList *next;
1014 /* Either one local URI, or a list. If everything in the list
1015 * isn't local then we are stuck.
1018 for (next_uri = uri_list; next_uri; next_uri = next_uri->next)
1020 const char *path;
1022 path = get_local_path((char *) next_uri->data);
1024 if (path)
1025 local_paths = g_list_append(local_paths,
1026 g_strdup(path));
1027 else
1028 error = _("Some of these files are on a "
1029 "different machine - they will be "
1030 "ignored - sorry");
1033 if (!local_paths)
1035 error = _("None of these files are on the local "
1036 "machine - I can't operate on multiple "
1037 "remote files - sorry.");
1039 else if (context->action == GDK_ACTION_ASK)
1040 prompt_action(local_paths, dest_path);
1041 else if (context->action == GDK_ACTION_MOVE)
1042 action_move(local_paths, dest_path, NULL, -1);
1043 else if (context->action == GDK_ACTION_COPY)
1044 action_copy(local_paths, dest_path, NULL, -1);
1045 else if (context->action == GDK_ACTION_LINK)
1046 action_link(local_paths, dest_path, NULL);
1047 else
1048 error = _("Unknown action requested");
1050 for (next = local_paths; next; next = next->next)
1051 g_free(next->data);
1052 g_list_free(local_paths);
1055 if (error)
1057 gtk_drag_finish(context, FALSE, FALSE, time); /* Failure */
1058 delayed_error(_("Error getting file list: %s"), error);
1060 else if (send_reply)
1061 gtk_drag_finish(context, TRUE, FALSE, time); /* Success! */
1063 next_uri = uri_list;
1064 while (next_uri)
1066 g_free(next_uri->data);
1067 next_uri = next_uri->next;
1069 g_list_free(uri_list);
1072 /* Called when an item from the ACTION_ASK menu is chosen */
1073 static void menuitem_response(gpointer data, guint action, GtkWidget *widget)
1075 if (action == MENU_MOVE)
1076 action_move(prompt_local_paths, prompt_dest_path, NULL, -1);
1077 else if (action == MENU_COPY)
1078 action_copy(prompt_local_paths, prompt_dest_path, NULL, -1);
1079 else if (action == MENU_LINK)
1080 action_link(prompt_local_paths, prompt_dest_path, NULL);
1081 else if (action == MENU_SET_ICON)
1083 if (g_list_length(prompt_local_paths) == 1)
1084 set_icon_path(prompt_dest_path,
1085 (char*) prompt_local_paths->data);
1086 else
1087 delayed_error(
1088 _("You can't use multiple files with Set Icon!"));
1092 /* When some local files are dropped somewhere with ACTION_ASK, this
1093 * function is called to display the menu.
1095 static void prompt_action(GList *paths, gchar *dest)
1097 GList *next;
1099 if (prompt_local_paths)
1101 g_list_foreach(prompt_local_paths, (GFunc) g_free, NULL);
1102 g_list_free(prompt_local_paths);
1103 g_free(prompt_dest_path);
1105 prompt_dest_path = NULL;
1106 prompt_local_paths = NULL;
1109 /* Make a copy of the arguments */
1110 for (next = paths; next; next = next->next)
1111 prompt_local_paths = g_list_append(prompt_local_paths,
1112 g_strdup((gchar *) next->data));
1113 prompt_dest_path = g_strdup(dest);
1115 if (!dnd_menu)
1117 GtkItemFactory *item_factory;
1119 item_factory = menu_create(menu_def,
1120 sizeof(menu_def) / sizeof(*menu_def),
1121 "<dnd>", NULL);
1122 dnd_menu = gtk_item_factory_get_widget(item_factory, "<dnd>");
1125 /* Shade 'Set Icon' if there are multiple files */
1126 menu_set_items_shaded(dnd_menu, g_list_length(paths) != 1, 4, 1);
1128 show_popup_menu(dnd_menu, gtk_get_current_event(), 1);
1132 /* SPRING-LOADING */
1134 /* This is the code that makes directories pop open if you hold a
1135 * file over them...
1137 * First, call dnd_spring_load(context) to arm the system.
1138 * After a timeout (1/2 a second) the dest_path directory will be
1139 * opened in a new window, unless dnd_spring_abort is called first.
1142 static gint spring_timeout = -1;
1143 static GdkDragContext *spring_context = NULL;
1144 static FilerWindow *spring_window = NULL;
1145 static FilerWindow *spring_src_window = NULL;
1147 void dnd_spring_load(GdkDragContext *context, FilerWindow *src_win)
1149 g_return_if_fail(context != NULL);
1151 if (!o_dnd_spring_open.int_value)
1152 return;
1154 if (spring_context)
1155 dnd_spring_abort();
1157 spring_context = context;
1158 g_object_ref(spring_context);
1159 spring_src_window = src_win;
1160 spring_timeout = gtk_timeout_add(
1161 o_dnd_spring_delay.int_value, spring_now, NULL);
1164 void dnd_spring_abort(void)
1166 if (!spring_context)
1167 return;
1169 g_object_unref(spring_context);
1170 spring_context = NULL;
1171 gtk_timeout_remove(spring_timeout);
1174 /* If all mod keys are released, no buttons are pressed, and the
1175 * mouse is outside the spring window, then close it.
1177 static gboolean spring_check_idle(gpointer data)
1179 int p_x, p_y;
1181 if (!spring_window)
1182 return FALSE;
1184 if (!get_pointer_xy(&p_x, &p_y))
1187 GdkWindow *win = spring_window->window->window;
1188 int x, y;
1189 int w, h;
1191 gdk_window_get_position(win, &x, &y);
1192 gdk_window_get_size(win, &w, &h);
1194 if (p_x < x || p_x > x + w || p_y < y || p_y > y + h)
1198 gtk_widget_destroy(spring_window->window);
1199 return FALSE; /* Got it! */
1202 return TRUE; /* Try again later */
1205 static gboolean spring_now(gpointer data)
1207 gboolean old_unique = o_unique_filer_windows.int_value;
1208 guchar *dest_path;
1209 gint x, y;
1211 g_return_val_if_fail(spring_context != NULL, FALSE);
1213 dest_path = g_dataset_get_data(spring_context, "drop_dest_path");
1214 g_return_val_if_fail(dest_path != NULL, FALSE);
1217 * Note: Due to a bug in gtk, if a window disappears during
1218 * a drag and the pointer moves over where the window was,
1219 * the sender crashes! Therefore, do not close any windows
1220 * while dragging! (fixed in later versions)
1223 if (spring_window)
1224 gtk_widget_destroy(spring_window->window);
1227 get_pointer_xy(&x, &y);
1229 o_unique_filer_windows.int_value = FALSE; /* XXX: yuck! */
1230 if (spring_window)
1232 collection_set_cursor_item(spring_window->collection, -1);
1233 filer_change_to(spring_window, dest_path, NULL);
1234 /* DON'T move the window. Gtk+ sometimes doesn't
1235 * notice :-(
1238 else
1240 spring_window = filer_opendir(dest_path, spring_src_window);
1241 if (spring_window)
1243 gtk_timeout_add(500, spring_check_idle, NULL);
1244 g_signal_connect(spring_window->window, "destroy",
1245 G_CALLBACK(spring_win_destroyed), NULL);
1246 centre_window(spring_window->window->window, x, y);
1249 o_unique_filer_windows.int_value = old_unique;
1251 dnd_spring_abort();
1253 return FALSE;
1256 static void spring_win_destroyed(GtkWidget *widget, gpointer data)
1258 spring_window = NULL;
1261 /* HANDLING MOTION EVENTS */
1263 /* If not-NULL, then this widget has a grab */
1264 static GtkWidget *motion_widget = NULL;
1266 /* If TRUE, we must gdk_pointer_ungrab() too when finishing */
1267 static gboolean motion_pointer_grab = FALSE;
1269 /* Call this on a button press event. It stores the mouse position
1270 * as the start of the new drag and returns TRUE if all is well.
1271 * Further motions events are disabled at this point - you must
1272 * then call dnd_motion_start() to set the type of motion expected.
1273 * Grabs the widget on the first press.
1275 * If the system is not ready to handle a motion event (because a
1276 * button is already held down?) it does nothing and returns FALSE.
1278 * If the event is not a single click then it simply returns TRUE.
1280 gboolean dnd_motion_press(GtkWidget *widget, GdkEventButton *event)
1282 if (event->type != GDK_BUTTON_PRESS)
1283 return TRUE; /* Not a click event! */
1285 motion_buttons_pressed++;
1286 if (motion_buttons_pressed == 1)
1288 /* g_print("[ grab! ]\n"); */
1289 gtk_grab_add(widget);
1290 motion_widget = widget;
1293 if (motion_state != MOTION_NONE)
1294 return FALSE; /* Ignore clicks - we're busy! */
1296 motion_state = MOTION_DISABLED;
1297 drag_start_x = event->x_root;
1298 drag_start_y = event->y_root;
1300 return TRUE;
1303 /* After the button press event, decide what kind of motion is expected.
1304 * If you don't call this then the motion system is disabled - call
1305 * dnd_motion_release() to reset it.
1307 * Note: If you open a popup menu or start DND call dnd_motion_ungrab()
1308 * instead.
1310 void dnd_motion_start(MotionType motion)
1312 g_return_if_fail(motion_state == MOTION_DISABLED);
1314 motion_state = motion;
1317 /* Call this on a button release event. If some buttons are still pressed,
1318 * returns TRUE and does nothing.
1320 * Otherwise, it resets the motion system to be ready again and returns TRUE.
1322 * If the motion system wasn't being used (MOTION_NONE) then it does nothing
1323 * and returns FALSE - process the release event yourself as it isn't part
1324 * of a motion. This also happens if a motion was primed but never happened.
1326 gboolean dnd_motion_release(GdkEventButton *event)
1328 MotionType motion = motion_state;
1329 int dx, dy;
1331 if (motion_buttons_pressed == 0)
1332 return TRUE; /* We were disabled */
1334 if (motion_buttons_pressed == 1)
1335 dnd_motion_ungrab();
1336 else
1338 motion_buttons_pressed--;
1339 return TRUE;
1342 if (motion == MOTION_REPOSITION || motion == MOTION_DISABLED)
1343 return TRUE; /* Already done something - eat the event */
1345 /* Eat release events that happen too far from the click
1346 * source. Otherwise, allow the caller to treat this as a click
1347 * that never became a motion.
1349 dx = event->x_root - drag_start_x;
1350 dy = event->y_root - drag_start_y;
1352 return ABS(dx) > 5 || ABS(dy) > 5;
1355 /* Use this to disable the motion system. The system will be reset once
1356 * all mouse buttons are released.
1358 void dnd_motion_disable(void)
1360 g_return_if_fail(motion_state != MOTION_NONE &&
1361 motion_state != MOTION_DISABLED);
1363 motion_state = MOTION_DISABLED;
1366 /* Use this if something else is going to grab the pointer so that
1367 * we won't get any more motion or release events.
1369 void dnd_motion_ungrab(void)
1371 if (motion_buttons_pressed > 0)
1373 if (motion_pointer_grab)
1375 gdk_pointer_ungrab(GDK_CURRENT_TIME);
1376 motion_pointer_grab = FALSE;
1377 /* g_print("[ ungrab_pointer ]\n"); */
1379 gtk_grab_remove(motion_widget);
1380 motion_widget = NULL;
1381 motion_buttons_pressed = 0;
1382 /* g_print("[ ungrab ]\n"); */
1385 motion_state = MOTION_NONE;
1388 /* Call this on motion events. If the mouse position is far enough
1389 * from the click position, returns TRUE and does dnd_motion_ungrab().
1390 * You should then start regular drag-and-drop.
1392 * Otherwise, returns FALSE.
1394 gboolean dnd_motion_moved(GdkEventMotion *event)
1396 int dx, dy;
1398 dx = event->x_root - drag_start_x;
1399 dy = event->y_root - drag_start_y;
1401 if (ABS(dx) <= 5 && ABS(dy) <= 5)
1402 return FALSE; /* Not far enough */
1404 dnd_motion_ungrab();
1406 return TRUE;
1409 /* Normally, the X server will automatically grab the pointer on a
1410 * button press and ungrab on release. However, if the grab widget
1411 * is reparented then call this to re-aquire the grab.
1413 void dnd_motion_grab_pointer(void)
1415 g_return_if_fail(motion_widget != NULL);
1417 gdk_pointer_grab(motion_widget->window, FALSE,
1418 GDK_POINTER_MOTION_MASK |
1419 GDK_BUTTON_RELEASE_MASK,
1420 FALSE, NULL, GDK_CURRENT_TIME);
1422 motion_pointer_grab = TRUE;