r824: Middle-button dragging to a program brings up the menu rather than
[rox-filer.git] / ROX-Filer / src / dnd.c
blobf6ad593e13f6f7c22cda02abae7d3cd4101700b7
1 /*
2 * $Id$
4 * ROX-Filer, filer for the ROX desktop project
5 * Copyright (C) 2001, 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 "collection.h"
38 #include "global.h"
40 #include "dnd.h"
41 #include "type.h"
42 #include "filer.h"
43 #include "action.h"
44 #include "pixmaps.h"
45 #include "gui_support.h"
46 #include "support.h"
47 #include "options.h"
48 #include "run.h"
49 #include "pinboard.h"
50 #include "dir.h"
51 #include "usericons.h"
52 #include "menu.h"
54 #define MAXURILEN 4096 /* Longest URI to allow */
56 gint drag_start_x, drag_start_y;
57 MotionType motion_state = MOTION_NONE;
59 static GList *prompt_local_paths = NULL;
60 static gchar *prompt_dest_path = NULL;
62 /* This keeps track of how many mouse buttons are currently down.
63 * We add a grab when it does 0->1 and release it on 1<-0.
65 * It may also be set to zero to disable the motion system (eg,
66 * when popping up a menu).
68 gint motion_buttons_pressed = 0;
70 /* Static prototypes */
71 static void set_xds_prop(GdkDragContext *context, char *text);
72 static gboolean drag_motion(GtkWidget *widget,
73 GdkDragContext *context,
74 gint x,
75 gint y,
76 guint time,
77 FilerWindow *filer_window);
78 static void drag_leave(GtkWidget *widget,
79 GdkDragContext *context,
80 guint32 time,
81 FilerWindow *filer_window);
82 static void desktop_drag_data_received(GtkWidget *widget,
83 GdkDragContext *context,
84 gint x,
85 gint y,
86 GtkSelectionData *selection_data,
87 guint info,
88 guint32 time,
89 FilerWindow *filer_window);
90 static void got_data_xds_reply(GtkWidget *widget,
91 GdkDragContext *context,
92 GtkSelectionData *selection_data,
93 guint32 time);
94 static void got_data_raw(GtkWidget *widget,
95 GdkDragContext *context,
96 GtkSelectionData *selection_data,
97 guint32 time);
98 static void got_uri_list(GtkWidget *widget,
99 GdkDragContext *context,
100 GtkSelectionData *selection_data,
101 guint32 time);
102 static void drag_end(GtkWidget *widget,
103 GdkDragContext *context,
104 FilerWindow *filer_window);
105 static gboolean drag_drop(GtkWidget *widget,
106 GdkDragContext *context,
107 gint x,
108 gint y,
109 guint time,
110 gpointer data);
111 static void drag_data_received(GtkWidget *widget,
112 GdkDragContext *context,
113 gint x,
114 gint y,
115 GtkSelectionData *selection_data,
116 guint info,
117 guint32 time,
118 gpointer user_data);
119 static gboolean spring_now(gpointer data);
120 static void spring_win_destroyed(GtkWidget *widget, gpointer data);
121 static void menuitem_response(gpointer data, guint action, GtkWidget *widget);
122 static void prompt_action(GList *paths, gchar *dest);
124 typedef enum {
125 MENU_COPY,
126 MENU_MOVE,
127 MENU_LINK,
128 MENU_SET_ICON,
129 } MenuActionType;
130 #undef N_
131 #define N_(x) x
132 static GtkItemFactoryEntry menu_def[] = {
133 {N_("Copy"), NULL, menuitem_response, MENU_COPY, NULL},
134 {N_("Move"), NULL, menuitem_response, MENU_MOVE, NULL},
135 {N_("Link"), NULL, menuitem_response, MENU_LINK, NULL},
136 {"", NULL, NULL, 0, "<Separator>"},
137 {N_("Set Icon"), NULL, menuitem_response, MENU_SET_ICON, NULL},
139 static GtkWidget *dnd_menu = NULL;
141 /* The handler of the signal handler for scroll events.
142 * This is used to cancel spring loading when autoscrolling is used.
144 static gint scrolled_signal = -1;
145 static GtkObject *scrolled_adj = NULL; /* The object watched */
147 /* Possible values for drop_dest_type (can also be NULL).
148 * In either case, drop_dest_path is the app/file/dir to use.
150 char *drop_dest_prog = "drop_dest_prog"; /* Run a program */
151 char *drop_dest_dir = "drop_dest_dir"; /* Save to path */
153 GdkAtom XdndDirectSave0;
154 GdkAtom xa_text_plain;
155 GdkAtom text_uri_list;
156 GdkAtom application_octet_stream;
157 GdkAtom xa_string; /* Not actually used for DnD, but the others are here! */
159 void dnd_init()
161 GtkItemFactory *item_factory;
163 XdndDirectSave0 = gdk_atom_intern("XdndDirectSave0", FALSE);
164 xa_text_plain = gdk_atom_intern("text/plain", FALSE);
165 text_uri_list = gdk_atom_intern("text/uri-list", FALSE);
166 application_octet_stream = gdk_atom_intern("application/octet-stream",
167 FALSE);
168 xa_string = gdk_atom_intern("STRING", FALSE);
170 option_add_int("dnd_drag_to_icons", 1, NULL);
171 option_add_int("dnd_spring_open", 0, NULL);
172 option_add_int("dnd_spring_delay", 400, NULL);
174 item_factory = menu_create(menu_def,
175 sizeof(menu_def) / sizeof(*menu_def),
176 "<dnd>");
177 dnd_menu = gtk_item_factory_get_widget(item_factory, "<dnd>");
180 /* SUPPORT FUNCTIONS */
182 /* Set the XdndDirectSave0 property on the source window for this context */
183 static void set_xds_prop(GdkDragContext *context, char *text)
185 gdk_property_change(context->source_window,
186 XdndDirectSave0,
187 xa_text_plain, 8,
188 GDK_PROP_MODE_REPLACE,
189 text,
190 strlen(text));
193 static char *get_xds_prop(GdkDragContext *context)
195 guchar *prop_text;
196 gint length;
198 if (gdk_property_get(context->source_window,
199 XdndDirectSave0,
200 xa_text_plain,
201 0, MAXURILEN,
202 FALSE,
203 NULL, NULL,
204 &length, &prop_text) && prop_text)
206 /* Terminate the string */
207 prop_text = g_realloc(prop_text, length + 1);
208 prop_text[length] = '\0';
209 return prop_text;
212 return NULL;
215 /* Is the sender willing to supply this target type? */
216 gboolean provides(GdkDragContext *context, GdkAtom target)
218 GList *targets = context->targets;
220 while (targets && ((GdkAtom) targets->data != target))
221 targets = targets->next;
223 return targets != NULL;
226 /* Convert a list of URIs into a list of strings.
227 * Lines beginning with # are skipped.
228 * The text block passed in is zero terminated (after the final CRLF)
230 GList *uri_list_to_glist(char *uri_list)
232 GList *list = NULL;
234 while (*uri_list)
236 char *linebreak;
237 char *uri;
238 int length;
240 linebreak = strchr(uri_list, 13);
242 if (!linebreak || linebreak[1] != 10)
244 delayed_error("uri_list_to_glist",
245 _("Incorrect or missing line "
246 "break in text/uri-list data"));
247 return list;
250 length = linebreak - uri_list;
252 if (length && uri_list[0] != '#')
254 uri = g_malloc(sizeof(char) * (length + 1));
255 strncpy(uri, uri_list, length);
256 uri[length] = 0;
257 list = g_list_append(list, uri);
260 uri_list = linebreak + 2;
263 return list;
266 /* DRAGGING FROM US */
268 /* The user has held the mouse button down over a group of item and moved -
269 * start a drag. 'uri_list' is copied, so you can delete it straight away.
271 void drag_selection(GtkWidget *widget, GdkEventMotion *event, guchar *uri_list)
273 GdkDragContext *context;
274 GdkDragAction actions;
275 GtkTargetList *target_list;
276 GtkTargetEntry target_table[] = {
277 {"text/uri-list", 0, TARGET_URI_LIST},
280 if (event->state & GDK_BUTTON1_MASK)
281 actions = GDK_ACTION_COPY | GDK_ACTION_MOVE
282 | GDK_ACTION_LINK | GDK_ACTION_ASK;
283 else
284 actions = GDK_ACTION_ASK; /* TODO: Option for move */
286 target_list = gtk_target_list_new(target_table, 1);
288 context = gtk_drag_begin(widget,
289 target_list,
290 actions,
291 (event->state & GDK_BUTTON1_MASK) ? 1 :
292 (event->state & GDK_BUTTON2_MASK) ? 2 : 3,
293 (GdkEvent *) event);
295 g_dataset_set_data_full(context, "uri_list",
296 g_strdup(uri_list), g_free);
298 gtk_drag_set_icon_pixmap(context,
299 gtk_widget_get_colormap(widget),
300 im_multiple->pixmap,
301 im_multiple->mask,
302 0, 0);
305 /* Copy/Load this item into another directory/application */
306 void drag_one_item(GtkWidget *widget,
307 GdkEventMotion *event,
308 guchar *full_path,
309 DirItem *item)
311 guchar *uri;
312 GdkDragContext *context;
313 GdkDragAction actions;
314 GtkTargetList *target_list;
315 GtkTargetEntry target_table[] = {
316 {"text/uri-list", 0, TARGET_URI_LIST},
317 {"application/octet-stream", 0, TARGET_RAW},
318 {"", 0, TARGET_RAW},
321 g_return_if_fail(full_path != NULL);
322 g_return_if_fail(item != NULL);
324 if (item->base_type == TYPE_FILE)
326 MIME_type *t = item->mime_type;
328 target_table[2].target = g_strconcat(t->media_type, "/",
329 t->subtype, NULL);
330 target_list = gtk_target_list_new(target_table, 3);
331 g_free(target_table[2].target);
333 else
334 target_list = gtk_target_list_new(target_table, 1);
336 if (event->state & GDK_BUTTON1_MASK)
337 actions = GDK_ACTION_COPY | GDK_ACTION_ASK
338 | GDK_ACTION_MOVE | GDK_ACTION_LINK;
339 else
340 actions = GDK_ACTION_ASK; /* TODO: Option for move */
342 context = gtk_drag_begin(widget,
343 target_list,
344 actions,
345 (event->state & GDK_BUTTON1_MASK) ? 1 :
346 (event->state & GDK_BUTTON2_MASK) ? 2 : 3,
347 (GdkEvent *) event);
349 g_dataset_set_data_full(context, "full_path",
350 g_strdup(full_path), g_free);
351 uri = g_strconcat("file://", our_host_name(), full_path, "\r\n", NULL);
352 g_dataset_set_data_full(context, "uri_list",
353 uri, g_free);
355 gtk_drag_set_icon_pixmap(context,
356 gtk_widget_get_colormap(widget),
357 item->image->pixmap,
358 item->image->mask,
359 0, 0);
362 static void drag_end(GtkWidget *widget,
363 GdkDragContext *context,
364 FilerWindow *filer_window)
366 collection_set_autoscroll(filer_window->collection, FALSE);
367 if (filer_window->temp_item_selected)
369 collection_clear_selection(filer_window->collection);
370 filer_window->temp_item_selected = FALSE;
374 /* Called when a remote app wants us to send it some data.
375 * TODO: Maybe we should handle errors better (ie, let the remote app know
376 * the drag has failed)?
378 void drag_data_get(GtkWidget *widget,
379 GdkDragContext *context,
380 GtkSelectionData *selection_data,
381 guint info,
382 guint32 time,
383 gpointer data)
385 char *to_send = "E"; /* Default to sending an error */
386 long to_send_length = 1;
387 gboolean delete_once_sent = FALSE;
388 GdkAtom type = XA_STRING;
389 guchar *path;
391 switch (info)
393 case TARGET_RAW:
394 path = g_dataset_get_data(context, "full_path");
395 if (path && load_file(path, &to_send, &to_send_length))
397 delete_once_sent = TRUE;
398 type = selection_data->target;
399 break;
401 g_warning("drag_data_get: Can't find path!\n");
402 return;
403 case TARGET_URI_LIST:
404 to_send = g_dataset_get_data(context, "uri_list");
405 to_send_length = strlen(to_send);
406 type = text_uri_list; /* (needed for xine) */
407 delete_once_sent = FALSE;
408 break;
409 default:
410 delayed_error("drag_data_get",
411 _("Internal error - bad info type"));
412 break;
415 gtk_selection_data_set(selection_data,
416 type,
418 to_send,
419 to_send_length);
421 if (delete_once_sent)
422 g_free(to_send);
425 /* DRAGGING TO US */
427 /* Set up this widget as a drop-target.
428 * Does not attach any motion handlers.
430 void make_drop_target(GtkWidget *widget, GtkDestDefaults defaults)
432 GtkTargetEntry target_table[] =
434 {"text/uri-list", 0, TARGET_URI_LIST},
435 {"XdndDirectSave0", 0, TARGET_XDS},
436 {"application/octet-stream", 0, TARGET_RAW},
439 gtk_drag_dest_set(widget,
440 defaults,
441 target_table,
442 sizeof(target_table) / sizeof(*target_table),
443 GDK_ACTION_COPY | GDK_ACTION_ASK | GDK_ACTION_MOVE
444 | GDK_ACTION_LINK | GDK_ACTION_PRIVATE);
446 gtk_signal_connect(GTK_OBJECT(widget), "drag_drop",
447 GTK_SIGNAL_FUNC(drag_drop), NULL);
448 gtk_signal_connect(GTK_OBJECT(widget), "drag_data_received",
449 GTK_SIGNAL_FUNC(drag_data_received), NULL);
452 /* Set up this filer window as a drop target. Called once, when the
453 * filer window is first created.
455 void drag_set_dest(FilerWindow *filer_window)
457 GtkWidget *widget = GTK_WIDGET(filer_window->collection);
459 make_drop_target(widget, 0);
461 gtk_signal_connect(GTK_OBJECT(widget), "drag_motion",
462 GTK_SIGNAL_FUNC(drag_motion), filer_window);
463 gtk_signal_connect(GTK_OBJECT(widget), "drag_leave",
464 GTK_SIGNAL_FUNC(drag_leave), filer_window);
465 gtk_signal_connect(GTK_OBJECT(widget), "drag_end",
466 GTK_SIGNAL_FUNC(drag_end), filer_window);
469 /* Like drag_set_dest, but for a pinboard-type widget.
470 * You must ensure that dnd events reach this widget (eg with
471 * setup_xdnd_proxy() for the root window).
473 void drag_set_pinboard_dest(GtkWidget *widget)
475 GtkTargetEntry target_table[] = {
476 {"text/uri-list", 0, TARGET_URI_LIST},
479 gtk_drag_dest_set(widget,
480 GTK_DEST_DEFAULT_DROP,
481 target_table,
482 sizeof(target_table) / sizeof(*target_table),
483 GDK_ACTION_LINK);
484 gtk_signal_connect(GTK_OBJECT(widget), "drag_data_received",
485 (GtkSignalFunc) desktop_drag_data_received,
486 NULL);
489 static void scrolled(GtkAdjustment *adj, Collection *collection)
491 collection_set_cursor_item(collection, -1);
492 dnd_spring_abort();
495 /* Called during the drag when the mouse is in a widget registered
496 * as a drop target. Returns TRUE if we can accept the drop.
498 static gboolean drag_motion(GtkWidget *widget,
499 GdkDragContext *context,
500 gint x,
501 gint y,
502 guint time,
503 FilerWindow *filer_window)
505 DirItem *item;
506 int item_number;
507 GdkDragAction action = context->suggested_action;
508 char *new_path = NULL;
509 char *type = NULL;
510 gboolean retval = FALSE;
512 if (filer_window->collection->auto_scroll == -1)
513 collection_set_autoscroll(filer_window->collection, TRUE);
515 if (option_get_int("dnd_drag_to_icons"))
516 item_number = collection_get_item(filer_window->collection,
517 x, y);
518 else
519 item_number = -1;
521 item = item_number >= 0
522 ? (DirItem *) filer_window->collection->items[item_number].data
523 : NULL;
525 if (item && filer_window->collection->items[item_number].selected)
526 type = NULL;
527 else
528 type = dnd_motion_item(context, &item);
530 if (!type)
531 item = NULL;
533 /* Don't allow drops to non-writeable directories. BUT, still
534 * allow drops on non-writeable SUBdirectories so that we can
535 * do the spring-open thing.
537 if (item && type == drop_dest_dir &&
538 !(item->flags & ITEM_FLAG_APPDIR))
540 GtkObject *vadj = GTK_OBJECT(filer_window->collection->vadj);
542 /* Subdir: prepare for spring-open */
543 if (scrolled_adj != vadj)
545 if (scrolled_adj)
546 gtk_signal_disconnect(scrolled_adj,
547 scrolled_signal);
548 scrolled_adj = vadj;
549 scrolled_signal = gtk_signal_connect(
550 scrolled_adj,
551 "value_changed",
552 GTK_SIGNAL_FUNC(scrolled),
553 filer_window->collection);
555 dnd_spring_load(context);
557 else
558 dnd_spring_abort();
560 if (item)
562 collection_set_cursor_item(filer_window->collection,
563 item_number);
565 else
567 collection_set_cursor_item(filer_window->collection, -1);
569 /* Disallow background drops within a single window */
570 if (type && gtk_drag_get_source_widget(context) == widget)
571 type = NULL;
574 if (type)
576 if (item)
577 new_path = make_path(filer_window->path,
578 item->leafname)->str;
579 else
580 new_path = filer_window->path;
583 g_dataset_set_data(context, "drop_dest_type", type);
584 if (type)
586 gdk_drag_status(context, action, time);
587 g_dataset_set_data_full(context, "drop_dest_path",
588 g_strdup(new_path), g_free);
589 retval = TRUE;
592 return retval;
595 /* item is the item the file is held over, NULL for directory background.
596 * 'item' may be NULL on exit if the drop should be treated as onto the
597 * background. Disallow drags to a selected icon before calling this.
599 * Returns NULL to reject the drop, or drop_dest_prog/drop_dest_dir to
600 * accept. Build the path based on item.
602 guchar *dnd_motion_item(GdkDragContext *context, DirItem **item_p)
604 DirItem *item = *item_p;
606 if (item)
608 /* If we didn't drop onto a directory, application or
609 * executable file then act as though the drop is to the
610 * window background.
612 if (item->base_type != TYPE_DIRECTORY
613 && !(item->mime_type == &special_exec))
615 item = NULL;
616 *item_p = NULL;
620 if (!item)
622 /* Drop onto the window background */
624 return drop_dest_dir;
627 /* Drop onto a program/directory of some sort */
629 if (item->base_type == TYPE_DIRECTORY &&
630 !(item->flags & ITEM_FLAG_APPDIR))
632 /* A normal directory */
633 if (provides(context, text_uri_list) ||
634 provides(context, XdndDirectSave0))
635 return drop_dest_dir;
637 else
639 if (provides(context, text_uri_list) ||
640 provides(context, application_octet_stream))
641 return drop_dest_prog;
644 return NULL;
647 /* Remove highlights */
648 static void drag_leave(GtkWidget *widget,
649 GdkDragContext *context,
650 guint32 time,
651 FilerWindow *filer_window)
653 collection_set_autoscroll(filer_window->collection, FALSE);
654 collection_set_cursor_item(filer_window->collection, -1);
655 dnd_spring_abort();
656 if (scrolled_adj)
658 gtk_signal_disconnect(scrolled_adj,
659 scrolled_signal);
660 scrolled_adj = NULL;
664 /* User has tried to drop some data on us. Decide what format we would
665 * like the data in.
667 static gboolean drag_drop(GtkWidget *widget,
668 GdkDragContext *context,
669 gint x,
670 gint y,
671 guint time,
672 gpointer data)
674 char *error = NULL;
675 char *leafname = NULL;
676 GdkAtom target = GDK_NONE;
677 char *dest_path;
678 char *dest_type = NULL;
680 dest_path = g_dataset_get_data(context, "drop_dest_path");
681 dest_type = g_dataset_get_data(context, "drop_dest_type");
683 g_return_val_if_fail(dest_path != NULL, TRUE);
685 if (dest_type == drop_dest_dir && provides(context, XdndDirectSave0))
687 leafname = get_xds_prop(context);
688 if (leafname)
690 if (strchr(leafname, '/'))
692 error = _("XDS protocol error: "
693 "leafname may not contain '/'\n");
694 g_free(leafname);
696 leafname = NULL;
698 else
700 GString *uri;
702 uri = g_string_new(NULL);
703 g_string_sprintf(uri, "file://%s%s",
704 our_host_name(),
705 make_path(dest_path,
706 leafname)->str);
707 set_xds_prop(context, uri->str);
708 g_string_free(uri, TRUE);
710 target = XdndDirectSave0;
711 g_dataset_set_data_full(context, "leafname",
712 leafname, g_free);
715 else
716 error = _(
717 "XdndDirectSave0 target provided, but the atom "
718 "XdndDirectSave0 (type text/plain) did not "
719 "contain a leafname\n");
721 else if (provides(context, text_uri_list))
722 target = text_uri_list;
723 else if (provides(context, application_octet_stream))
724 target = application_octet_stream;
725 else
727 if (dest_type == drop_dest_dir)
728 error = _("Sorry - I require a target type of "
729 "text/uri-list or XdndDirectSave0.");
730 else
731 error = _("Sorry - I require a target type of "
732 "text/uri-list or application/octet-stream.");
735 if (error)
737 gtk_drag_finish(context, FALSE, FALSE, time); /* Failure */
739 delayed_error(PROJECT, error);
741 else
742 gtk_drag_get_data(widget, context, target, time);
744 return TRUE;
747 /* Called when a text/uri-list arrives */
748 static void desktop_drag_data_received(GtkWidget *widget,
749 GdkDragContext *context,
750 gint x,
751 gint y,
752 GtkSelectionData *selection_data,
753 guint info,
754 guint32 time,
755 FilerWindow *filer_window)
757 GList *uris, *next;
758 gint dx, dy;
760 if (!selection_data->data)
762 /* Timeout? */
763 return;
766 gdk_window_get_position(widget->window, &dx, &dy);
767 x += dx;
768 y += dy;
770 uris = uri_list_to_glist(selection_data->data);
772 for (next = uris; next; next = next->next)
774 guchar *path;
776 path = get_local_path((gchar *) next->data);
777 if (path)
779 pinboard_pin(path, NULL, x, y, TRUE);
780 x += 64;
783 g_free(next->data);
786 if (uris)
787 g_list_free(uris);
790 /* Called when some data arrives from the remote app (which we asked for
791 * in drag_drop).
793 static void drag_data_received(GtkWidget *widget,
794 GdkDragContext *context,
795 gint x,
796 gint y,
797 GtkSelectionData *selection_data,
798 guint info,
799 guint32 time,
800 gpointer user_data)
802 if (!selection_data->data)
804 /* Timeout? */
805 gtk_drag_finish(context, FALSE, FALSE, time); /* Failure */
806 return;
809 switch (info)
811 case TARGET_XDS:
812 got_data_xds_reply(widget, context,
813 selection_data, time);
814 break;
815 case TARGET_RAW:
816 got_data_raw(widget, context, selection_data, time);
817 break;
818 case TARGET_URI_LIST:
819 got_uri_list(widget, context, selection_data, time);
820 break;
821 default:
822 gtk_drag_finish(context, FALSE, FALSE, time);
823 delayed_error("drag_data_received",
824 _("Unknown target"));
825 break;
829 static void got_data_xds_reply(GtkWidget *widget,
830 GdkDragContext *context,
831 GtkSelectionData *selection_data,
832 guint32 time)
834 gboolean mark_unsafe = TRUE;
835 char response = *selection_data->data;
836 char *error = NULL;
837 char *dest_path;
839 dest_path = g_dataset_get_data(context, "drop_dest_path");
841 if (selection_data->length != 1)
842 response = '?';
844 if (response == 'F')
846 /* Sender couldn't save there - ask for another
847 * type if possible.
849 if (provides(context, application_octet_stream))
851 mark_unsafe = FALSE; /* Wait and see */
853 gtk_drag_get_data(widget, context,
854 application_octet_stream, time);
856 else
857 error = _("Remote app can't or won't send me "
858 "the data - sorry");
860 else if (response == 'S')
862 /* Success - data is saved */
863 mark_unsafe = FALSE; /* It really is safe */
864 gtk_drag_finish(context, TRUE, FALSE, time);
866 refresh_dirs(dest_path);
868 else if (response != 'E')
870 error = _("XDS protocol error: "
871 "return code should be 'S', 'F' or 'E'\n");
873 /* else: error has been reported by the sender */
875 if (mark_unsafe)
877 set_xds_prop(context, "");
878 /* Unsave also implies that the drag failed */
879 gtk_drag_finish(context, FALSE, FALSE, time);
882 if (error)
883 delayed_error(PROJECT, error);
886 static void got_data_raw(GtkWidget *widget,
887 GdkDragContext *context,
888 GtkSelectionData *selection_data,
889 guint32 time)
891 char *leafname;
892 int fd;
893 char *error = NULL;
894 char *dest_path;
896 g_return_if_fail(selection_data->data != NULL);
898 dest_path = g_dataset_get_data(context, "drop_dest_path");
900 if (g_dataset_get_data(context, "drop_dest_type") == drop_dest_prog)
902 /* The data needs to be sent to an application */
903 run_with_data(dest_path,
904 selection_data->data, selection_data->length);
905 gtk_drag_finish(context, TRUE, FALSE, time); /* Success! */
906 return;
909 leafname = g_dataset_get_data(context, "leafname");
910 if (!leafname)
911 leafname = _("UntitledData");
913 fd = open(make_path(dest_path, leafname)->str,
914 O_WRONLY | O_CREAT | O_EXCL | O_NOCTTY,
915 S_IRUSR | S_IRGRP | S_IROTH |
916 S_IWUSR | S_IWGRP | S_IWOTH);
918 if (fd == -1)
919 error = g_strerror(errno);
920 else
922 if (write(fd,
923 selection_data->data,
924 selection_data->length) == -1)
925 error = g_strerror(errno);
927 if (close(fd) == -1 && !error)
928 error = g_strerror(errno);
930 refresh_dirs(dest_path);
933 if (error)
935 if (provides(context, XdndDirectSave0))
936 set_xds_prop(context, "");
937 gtk_drag_finish(context, FALSE, FALSE, time); /* Failure */
938 delayed_error(_("Error saving file"), error);
940 else
941 gtk_drag_finish(context, TRUE, FALSE, time); /* Success! */
944 /* We've got a list of URIs from somewhere (probably another filer window).
945 * If the files are on the local machine then try to copy them ourselves,
946 * otherwise, if there was only one file and application/octet-stream was
947 * provided, get the data via the X server.
949 static void got_uri_list(GtkWidget *widget,
950 GdkDragContext *context,
951 GtkSelectionData *selection_data,
952 guint32 time)
954 GList *uri_list;
955 char *error = NULL;
956 GList *next_uri;
957 gboolean send_reply = TRUE;
958 char *dest_path;
959 char *type;
961 dest_path = g_dataset_get_data(context, "drop_dest_path");
962 type = g_dataset_get_data(context, "drop_dest_type");
964 g_return_if_fail(dest_path != NULL);
966 uri_list = uri_list_to_glist(selection_data->data);
968 if (!uri_list)
969 error = _("No URIs in the text/uri-list (nothing to do!)");
970 else if (context->action != GDK_ACTION_ASK && type == drop_dest_prog)
971 run_with_files(dest_path, uri_list);
972 else if ((!uri_list->next) && (!get_local_path(uri_list->data)))
974 /* There is one URI in the list, and it's not on the local
975 * machine. Get it via the X server if possible.
977 /* XXX: Action ask? */
979 if (provides(context, application_octet_stream))
981 char *leaf;
982 leaf = strrchr(uri_list->data, '/');
983 if (leaf)
984 leaf++;
985 else
986 leaf = uri_list->data;
987 g_dataset_set_data_full(context, "leafname",
988 g_strdup(leaf), g_free);
989 gtk_drag_get_data(widget, context,
990 application_octet_stream, time);
991 send_reply = FALSE;
993 else
994 error = _("Can't get data from remote machine "
995 "(application/octet-stream not provided)");
997 else
999 GList *local_paths = NULL;
1000 GList *next;
1002 /* Either one local URI, or a list. If everything in the list
1003 * isn't local then we are stuck.
1006 for (next_uri = uri_list; next_uri; next_uri = next_uri->next)
1008 char *path;
1010 path = get_local_path((char *) next_uri->data);
1012 if (path)
1013 local_paths = g_list_append(local_paths,
1014 g_strdup(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);
1031 else if (context->action == GDK_ACTION_COPY)
1032 action_copy(local_paths, dest_path, NULL);
1033 else if (context->action == GDK_ACTION_LINK)
1034 action_link(local_paths, dest_path);
1035 else
1036 error = _("Unknown action requested");
1038 for (next = local_paths; next; next = next->next)
1039 g_free(next->data);
1040 g_list_free(local_paths);
1043 if (error)
1045 gtk_drag_finish(context, FALSE, FALSE, time); /* Failure */
1046 delayed_error(_("Error getting file list"), error);
1048 else if (send_reply)
1049 gtk_drag_finish(context, TRUE, FALSE, time); /* Success! */
1051 next_uri = uri_list;
1052 while (next_uri)
1054 g_free(next_uri->data);
1055 next_uri = next_uri->next;
1057 g_list_free(uri_list);
1060 /* Called when an item from the ACTION_ASK menu is chosen */
1061 static void menuitem_response(gpointer data, guint action, GtkWidget *widget)
1063 if (action == MENU_MOVE)
1064 action_move(prompt_local_paths, prompt_dest_path, NULL);
1065 else if (action == MENU_COPY)
1066 action_copy(prompt_local_paths, prompt_dest_path, NULL);
1067 else if (action == MENU_LINK)
1068 action_link(prompt_local_paths, prompt_dest_path);
1069 else if (action == MENU_SET_ICON)
1071 if (g_list_length(prompt_local_paths) == 1)
1072 set_icon_path(prompt_dest_path,
1073 (char*) prompt_local_paths->data);
1074 else
1075 delayed_error(PROJECT,
1076 _("You can't use multiple files with Set Icon!"));
1080 /* When some local files are dropped somewhere with ACTION_ASK, this
1081 * function is called to display the menu.
1083 static void prompt_action(GList *paths, gchar *dest)
1085 GList *next;
1087 if (prompt_local_paths)
1089 g_list_foreach(prompt_local_paths, (GFunc) g_free, NULL);
1090 g_list_free(prompt_local_paths);
1091 g_free(prompt_dest_path);
1093 prompt_dest_path = NULL;
1094 prompt_local_paths = NULL;
1097 /* Make a copy of the arguments */
1098 for (next = paths; next; next = next->next)
1099 prompt_local_paths = g_list_append(prompt_local_paths,
1100 g_strdup((gchar *) next->data));
1101 prompt_dest_path = g_strdup(dest);
1103 /* Shade 'Set Icon' if there are multiple files */
1104 menu_set_items_shaded(dnd_menu, g_list_length(paths) != 1, 4, 1);
1106 show_popup_menu(dnd_menu, gtk_get_current_event(), 1);
1110 /* SPRING-LOADING */
1112 /* This is the code that makes directories pop open if you hold a
1113 * file over them...
1115 * First, call dnd_spring_load(context) to arm the system.
1116 * After a timeout (1/2 a second) the dest_path directory will be
1117 * opened in a new window, unless dnd_spring_abort is called first.
1120 static gint spring_timeout = -1;
1121 static GdkDragContext *spring_context = NULL;
1122 static FilerWindow *spring_window = NULL;
1124 void dnd_spring_load(GdkDragContext *context)
1126 g_return_if_fail(context != NULL);
1128 if (!option_get_int("dnd_spring_open"))
1129 return;
1131 if (spring_context)
1132 dnd_spring_abort();
1134 spring_context = context;
1135 gdk_drag_context_ref(spring_context);
1136 spring_timeout = gtk_timeout_add(
1137 option_get_int("dnd_spring_delay"), spring_now, NULL);
1140 void dnd_spring_abort(void)
1142 if (!spring_context)
1143 return;
1145 gdk_drag_context_unref(spring_context);
1146 spring_context = NULL;
1147 gtk_timeout_remove(spring_timeout);
1150 /* If all mod keys are released, no buttons are pressed, and the
1151 * mouse is outside the spring window, then close it.
1153 static gboolean spring_check_idle(gpointer data)
1155 int p_x, p_y;
1157 if (!spring_window)
1158 return FALSE;
1160 if (!get_pointer_xy(&p_x, &p_y))
1163 GdkWindow *win = spring_window->window->window;
1164 int x, y;
1165 int w, h;
1167 gdk_window_get_position(win, &x, &y);
1168 gdk_window_get_size(win, &w, &h);
1170 if (p_x < x || p_x > x + w || p_y < y || p_y > y + h)
1174 gtk_widget_destroy(spring_window->window);
1175 return FALSE; /* Got it! */
1178 return TRUE; /* Try again later */
1181 static gboolean spring_now(gpointer data)
1183 gboolean old_unique = o_unique_filer_windows;
1184 guchar *dest_path;
1185 gint x, y;
1187 g_return_val_if_fail(spring_context != NULL, FALSE);
1189 dest_path = g_dataset_get_data(spring_context, "drop_dest_path");
1190 g_return_val_if_fail(dest_path != NULL, FALSE);
1193 * Note: Due to a bug in gtk, if a window disappears during
1194 * a drag and the pointer moves over where the window was,
1195 * the sender crashes! Therefore, do not close any windows
1196 * while dragging! (fixed in later versions)
1199 if (spring_window)
1200 gtk_widget_destroy(spring_window->window);
1203 get_pointer_xy(&x, &y);
1205 o_unique_filer_windows = FALSE;
1206 if (spring_window)
1208 collection_set_cursor_item(spring_window->collection, -1);
1209 filer_change_to(spring_window, dest_path, NULL);
1210 /* DON'T move the window. Gtk+ sometimes doesn't
1211 * notice :-(
1214 else
1216 spring_window = filer_opendir(dest_path);
1217 gtk_timeout_add(500, spring_check_idle, NULL);
1218 gtk_signal_connect(GTK_OBJECT(spring_window->window), "destroy",
1219 GTK_SIGNAL_FUNC(spring_win_destroyed), NULL);
1220 if (spring_window)
1221 centre_window(spring_window->window->window, x, y);
1223 o_unique_filer_windows = old_unique;
1225 dnd_spring_abort();
1227 return FALSE;
1230 static void spring_win_destroyed(GtkWidget *widget, gpointer data)
1232 spring_window = NULL;
1235 /* HANDLING MOTION EVENTS */
1237 /* If not-NULL, then this widget has a grab */
1238 static GtkWidget *motion_widget = NULL;
1240 /* If TRUE, we must gdk_pointer_ungrab() too when finishing */
1241 static gboolean motion_pointer_grab = FALSE;
1243 /* Call this on a button press event. It stores the mouse position
1244 * as the start of the new drag and returns TRUE if all is well.
1245 * Further motions events are disabled at this point - you must
1246 * then call dnd_motion_start() to set the type of motion expected.
1247 * Grabs the widget on the first press.
1249 * If the system is not ready to handle a motion event (because a
1250 * button is already held down?) it does nothing and returns FALSE.
1252 * If the event is not a single click then it simply returns TRUE.
1254 gboolean dnd_motion_press(GtkWidget *widget, GdkEventButton *event)
1256 if (event->type != GDK_BUTTON_PRESS)
1257 return TRUE; /* Not a click event! */
1259 motion_buttons_pressed++;
1260 if (motion_buttons_pressed == 1)
1262 /* g_print("[ grab! ]\n"); */
1263 gtk_grab_add(widget);
1264 motion_widget = widget;
1267 if (motion_state != MOTION_NONE)
1268 return FALSE; /* Ignore clicks - we're busy! */
1270 motion_state = MOTION_DISABLED;
1271 drag_start_x = event->x_root;
1272 drag_start_y = event->y_root;
1274 return TRUE;
1277 /* After the button press event, decide what kind of motion is expected.
1278 * If you don't call this then the motion system is disabled - call
1279 * dnd_motion_release() to reset it.
1281 * Note: If you open a popup menu or start DND call dnd_motion_ungrab()
1282 * instead.
1284 void dnd_motion_start(MotionType motion)
1286 g_return_if_fail(motion_state == MOTION_DISABLED);
1288 motion_state = motion;
1291 /* Call this on a button release event. If some buttons are still pressed,
1292 * returns TRUE and does nothing.
1294 * Otherwise, it resets the motion system to be ready again and returns TRUE.
1296 * If the motion system wasn't being used (MOTION_NONE) then it does nothing
1297 * and returns FALSE - process the release event yourself as it isn't part
1298 * of a motion. This also happens if a motion was primed but never happened.
1300 gboolean dnd_motion_release(GdkEventButton *event)
1302 MotionType motion = motion_state;
1303 int dx, dy;
1305 if (motion_buttons_pressed == 0)
1306 return TRUE; /* We were disabled */
1308 if (motion_buttons_pressed == 1)
1309 dnd_motion_ungrab();
1310 else
1312 motion_buttons_pressed--;
1313 return TRUE;
1316 if (motion == MOTION_REPOSITION || motion == MOTION_DISABLED)
1317 return TRUE; /* Already done something - eat the event */
1319 /* Eat release events that happen too far from the click
1320 * source. Otherwise, allow the caller to treat this as a click
1321 * that never became a motion.
1323 dx = event->x_root - drag_start_x;
1324 dy = event->y_root - drag_start_y;
1326 return ABS(dx) > 5 || ABS(dy) > 5;
1329 /* Use this to disable the motion system. The system will be reset once
1330 * all mouse buttons are released.
1332 void dnd_motion_disable(void)
1334 g_return_if_fail(motion_state != MOTION_NONE &&
1335 motion_state != MOTION_DISABLED);
1337 motion_state = MOTION_DISABLED;
1340 /* Use this if something else is going to grab the pointer so that
1341 * we won't get any more motion or release events.
1343 void dnd_motion_ungrab(void)
1345 if (motion_buttons_pressed > 0)
1347 if (motion_pointer_grab)
1349 gdk_pointer_ungrab(GDK_CURRENT_TIME);
1350 motion_pointer_grab = FALSE;
1351 /* g_print("[ ungrab_pointer ]\n"); */
1353 gtk_grab_remove(motion_widget);
1354 motion_widget = NULL;
1355 motion_buttons_pressed = 0;
1356 /* g_print("[ ungrab ]\n"); */
1359 motion_state = MOTION_NONE;
1362 /* Call this on motion events. If the mouse position is far enough
1363 * from the click position, returns TRUE and does dnd_motion_ungrab().
1364 * You should then start regular drag-and-drop.
1366 * Otherwise, returns FALSE.
1368 gboolean dnd_motion_moved(GdkEventMotion *event)
1370 int dx, dy;
1372 dx = event->x_root - drag_start_x;
1373 dy = event->y_root - drag_start_y;
1375 if (ABS(dx) <= 5 && ABS(dy) <= 5)
1376 return FALSE; /* Not far enough */
1378 dnd_motion_ungrab();
1380 return TRUE;
1383 /* Normally, the X server will automatically grab the pointer on a
1384 * button press and ungrab on release. However, if the grab widget
1385 * is reparented then call this to re-aquire the grab.
1387 void dnd_motion_grab_pointer(void)
1389 g_return_if_fail(motion_widget != NULL);
1391 gdk_pointer_grab(motion_widget->window, FALSE,
1392 GDK_POINTER_MOTION_MASK |
1393 GDK_BUTTON_RELEASE_MASK,
1394 FALSE, NULL, GDK_CURRENT_TIME);
1396 motion_pointer_grab = TRUE;