r1190: Moved CPPFLAGS to pixmaps rule to avoid getting it twice.
[rox-filer.git] / ROX-Filer / src / dnd.c
blob5005db35701a7ea34bf2d4259d721c201d0e7ad8
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 "collection.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"
56 #define MAXURILEN 4096 /* Longest URI to allow */
58 gint drag_start_x, drag_start_y;
59 MotionType motion_state = MOTION_NONE;
61 static GList *prompt_local_paths = NULL;
62 static gchar *prompt_dest_path = NULL;
64 /* This keeps track of how many mouse buttons are currently down.
65 * We add a grab when it does 0->1 and release it on 1<-0.
67 * It may also be set to zero to disable the motion system (eg,
68 * when popping up a menu).
70 gint motion_buttons_pressed = 0;
72 /* Static prototypes */
73 static void set_xds_prop(GdkDragContext *context, char *text);
74 static gboolean drag_motion(GtkWidget *widget,
75 GdkDragContext *context,
76 gint x,
77 gint y,
78 guint time,
79 FilerWindow *filer_window);
80 static void drag_leave(GtkWidget *widget,
81 GdkDragContext *context,
82 guint32 time,
83 FilerWindow *filer_window);
84 static void desktop_drag_data_received(GtkWidget *widget,
85 GdkDragContext *context,
86 gint x,
87 gint y,
88 GtkSelectionData *selection_data,
89 guint info,
90 guint32 time,
91 FilerWindow *filer_window);
92 static void got_data_xds_reply(GtkWidget *widget,
93 GdkDragContext *context,
94 GtkSelectionData *selection_data,
95 guint32 time);
96 static void got_data_raw(GtkWidget *widget,
97 GdkDragContext *context,
98 GtkSelectionData *selection_data,
99 guint32 time);
100 static void got_uri_list(GtkWidget *widget,
101 GdkDragContext *context,
102 GtkSelectionData *selection_data,
103 guint32 time);
104 static void drag_end(GtkWidget *widget,
105 GdkDragContext *context,
106 FilerWindow *filer_window);
107 static gboolean drag_drop(GtkWidget *widget,
108 GdkDragContext *context,
109 gint x,
110 gint y,
111 guint time,
112 gpointer data);
113 static void drag_data_received(GtkWidget *widget,
114 GdkDragContext *context,
115 gint x,
116 gint y,
117 GtkSelectionData *selection_data,
118 guint info,
119 guint32 time,
120 gpointer user_data);
121 static gboolean spring_now(gpointer data);
122 static void spring_win_destroyed(GtkWidget *widget, gpointer data);
123 static void menuitem_response(gpointer data, guint action, GtkWidget *widget);
124 static void prompt_action(GList *paths, gchar *dest);
126 typedef enum {
127 MENU_COPY,
128 MENU_MOVE,
129 MENU_LINK,
130 MENU_SET_ICON,
131 } MenuActionType;
132 #undef N_
133 #define N_(x) x
134 static GtkItemFactoryEntry menu_def[] = {
135 {N_("Copy"), NULL, menuitem_response, MENU_COPY, NULL},
136 {N_("Move"), NULL, menuitem_response, MENU_MOVE, NULL},
137 {N_("Link"), NULL, menuitem_response, MENU_LINK, NULL},
138 {"", NULL, NULL, 0, "<Separator>"},
139 {N_("Set Icon"), NULL, menuitem_response, MENU_SET_ICON, NULL},
141 static GtkWidget *dnd_menu = NULL;
143 /* The handler of the signal handler for scroll events.
144 * This is used to cancel spring loading when autoscrolling is used.
146 static gint scrolled_signal = -1;
147 static GtkObject *scrolled_adj = NULL; /* The object watched */
149 /* Possible values for drop_dest_type (can also be NULL).
150 * In either case, drop_dest_path is the app/file/dir to use.
152 char *drop_dest_prog = "drop_dest_prog"; /* Run a program */
153 char *drop_dest_dir = "drop_dest_dir"; /* Save to path */
155 GdkAtom XdndDirectSave0;
156 GdkAtom xa_text_plain;
157 GdkAtom text_uri_list;
158 GdkAtom application_octet_stream;
159 GdkAtom xa_string; /* Not actually used for DnD, but the others are here! */
161 void dnd_init()
163 GtkItemFactory *item_factory;
165 XdndDirectSave0 = gdk_atom_intern("XdndDirectSave0", FALSE);
166 xa_text_plain = gdk_atom_intern("text/plain", FALSE);
167 text_uri_list = gdk_atom_intern("text/uri-list", FALSE);
168 application_octet_stream = gdk_atom_intern("application/octet-stream",
169 FALSE);
170 xa_string = gdk_atom_intern("STRING", FALSE);
172 option_add_int("dnd_drag_to_icons", 1, NULL);
173 option_add_int("dnd_spring_open", 0, NULL);
174 option_add_int("dnd_spring_delay", 400, NULL);
175 option_add_int("dnd_middle_menu", TRUE, NULL);
177 item_factory = menu_create(menu_def,
178 sizeof(menu_def) / sizeof(*menu_def),
179 "<dnd>", NULL);
180 dnd_menu = gtk_item_factory_get_widget(item_factory, "<dnd>");
183 /* SUPPORT FUNCTIONS */
185 /* Set the XdndDirectSave0 property on the source window for this context */
186 static void set_xds_prop(GdkDragContext *context, 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(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_malloc(sizeof(char) * (length + 1));
258 strncpy(uri, uri_list, length);
259 uri[length] = 0;
260 list = g_list_append(list, uri);
263 uri_list = linebreak + 2;
266 return list;
269 /* DRAGGING FROM US */
271 /* The user has held the mouse button down over a group of item and moved -
272 * start a drag. 'uri_list' is copied, so you can delete it straight away.
274 void drag_selection(GtkWidget *widget, GdkEventMotion *event, guchar *uri_list)
276 GdkDragContext *context;
277 GdkDragAction actions;
278 GtkTargetList *target_list;
279 GtkTargetEntry target_table[] = {
280 {"text/uri-list", 0, TARGET_URI_LIST},
283 if (event->state & GDK_BUTTON1_MASK)
284 actions = GDK_ACTION_COPY | GDK_ACTION_MOVE
285 | GDK_ACTION_LINK | GDK_ACTION_ASK;
286 else
288 if (option_get_int("dnd_middle_menu"))
289 actions = GDK_ACTION_ASK;
290 else
291 actions = GDK_ACTION_MOVE;
294 target_list = gtk_target_list_new(target_table, 1);
296 context = gtk_drag_begin(widget,
297 target_list,
298 actions,
299 (event->state & GDK_BUTTON1_MASK) ? 1 :
300 (event->state & GDK_BUTTON2_MASK) ? 2 : 3,
301 (GdkEvent *) event);
303 g_dataset_set_data_full(context, "uri_list",
304 g_strdup(uri_list), g_free);
306 gtk_drag_set_icon_pixmap(context,
307 gtk_widget_get_colormap(widget),
308 im_multiple->pixmap,
309 im_multiple->mask,
310 0, 0);
313 /* Copy/Load this item into another directory/application */
314 void drag_one_item(GtkWidget *widget,
315 GdkEventMotion *event,
316 guchar *full_path,
317 DirItem *item,
318 MaskedPixmap *image)
320 guchar *uri;
321 GdkDragContext *context;
322 GdkDragAction actions;
323 GtkTargetList *target_list;
324 GtkTargetEntry target_table[] = {
325 {"text/uri-list", 0, TARGET_URI_LIST},
326 {"application/octet-stream", 0, TARGET_RAW},
327 {"", 0, TARGET_RAW},
330 g_return_if_fail(full_path != NULL);
331 g_return_if_fail(item != NULL);
333 if (!image)
334 image = item->image;
336 if (item->base_type == TYPE_FILE)
338 MIME_type *t = item->mime_type;
340 target_table[2].target = g_strconcat(t->media_type, "/",
341 t->subtype, NULL);
342 target_list = gtk_target_list_new(target_table, 3);
343 g_free(target_table[2].target);
345 else
346 target_list = gtk_target_list_new(target_table, 1);
348 if (event->state & GDK_BUTTON1_MASK)
349 actions = GDK_ACTION_COPY | GDK_ACTION_ASK
350 | GDK_ACTION_MOVE | GDK_ACTION_LINK;
351 else
353 if (option_get_int("dnd_middle_menu"))
354 actions = GDK_ACTION_ASK;
355 else
356 actions = GDK_ACTION_MOVE;
359 context = gtk_drag_begin(widget,
360 target_list,
361 actions,
362 (event->state & GDK_BUTTON1_MASK) ? 1 :
363 (event->state & GDK_BUTTON2_MASK) ? 2 : 3,
364 (GdkEvent *) event);
366 g_dataset_set_data_full(context, "full_path",
367 g_strdup(full_path), g_free);
368 uri = g_strconcat("file://", our_host_name_for_dnd(),
369 full_path, "\r\n", NULL);
370 g_dataset_set_data_full(context, "uri_list", uri, g_free);
372 g_return_if_fail(image != NULL);
374 gtk_drag_set_icon_pixmap(context,
375 gtk_widget_get_colormap(widget),
376 image->pixmap, image->mask, 0, 0);
379 static void drag_end(GtkWidget *widget,
380 GdkDragContext *context,
381 FilerWindow *filer_window)
383 collection_set_autoscroll(filer_window->collection, FALSE);
384 if (filer_window->temp_item_selected)
386 collection_clear_selection(filer_window->collection);
387 filer_window->temp_item_selected = FALSE;
391 /* Called when a remote app wants us to send it some data.
392 * TODO: Maybe we should handle errors better (ie, let the remote app know
393 * the drag has failed)?
395 void drag_data_get(GtkWidget *widget,
396 GdkDragContext *context,
397 GtkSelectionData *selection_data,
398 guint info,
399 guint32 time,
400 gpointer data)
402 char *to_send = "E"; /* Default to sending an error */
403 long to_send_length = 1;
404 gboolean delete_once_sent = FALSE;
405 GdkAtom type;
406 guchar *path;
408 type = gdk_x11_xatom_to_atom(XA_STRING);
410 switch (info)
412 case TARGET_RAW:
413 path = g_dataset_get_data(context, "full_path");
414 if (path && load_file(path, &to_send, &to_send_length))
416 delete_once_sent = TRUE;
417 type = selection_data->target;
418 break;
420 g_warning("drag_data_get: Can't find path!\n");
421 return;
422 case TARGET_URI_LIST:
423 to_send = g_dataset_get_data(context, "uri_list");
424 to_send_length = strlen(to_send);
425 type = text_uri_list; /* (needed for xine) */
426 delete_once_sent = FALSE;
427 break;
428 default:
429 delayed_error("drag_data_get: %s",
430 _("Internal error - bad info type"));
431 break;
434 gtk_selection_data_set(selection_data,
435 type,
437 to_send,
438 to_send_length);
440 if (delete_once_sent)
441 g_free(to_send);
444 /* DRAGGING TO US */
446 /* Set up this widget as a drop-target.
447 * Does not attach any motion handlers.
449 void make_drop_target(GtkWidget *widget, GtkDestDefaults defaults)
451 GtkTargetEntry target_table[] =
453 {"text/uri-list", 0, TARGET_URI_LIST},
454 {"XdndDirectSave0", 0, TARGET_XDS},
455 {"application/octet-stream", 0, TARGET_RAW},
458 gtk_drag_dest_set(widget,
459 defaults,
460 target_table,
461 sizeof(target_table) / sizeof(*target_table),
462 GDK_ACTION_COPY | GDK_ACTION_ASK | GDK_ACTION_MOVE
463 | GDK_ACTION_LINK | GDK_ACTION_PRIVATE);
465 gtk_signal_connect(GTK_OBJECT(widget), "drag_drop",
466 GTK_SIGNAL_FUNC(drag_drop), NULL);
467 gtk_signal_connect(GTK_OBJECT(widget), "drag_data_received",
468 GTK_SIGNAL_FUNC(drag_data_received), NULL);
471 /* Set up this filer window as a drop target. Called once, when the
472 * filer window is first created.
474 void drag_set_dest(FilerWindow *filer_window)
476 GtkWidget *widget = GTK_WIDGET(filer_window->collection);
478 make_drop_target(widget, 0);
480 gtk_signal_connect(GTK_OBJECT(widget), "drag_motion",
481 GTK_SIGNAL_FUNC(drag_motion), filer_window);
482 gtk_signal_connect(GTK_OBJECT(widget), "drag_leave",
483 GTK_SIGNAL_FUNC(drag_leave), filer_window);
484 gtk_signal_connect(GTK_OBJECT(widget), "drag_end",
485 GTK_SIGNAL_FUNC(drag_end), filer_window);
488 /* Like drag_set_dest, but for a pinboard-type widget.
489 * You must ensure that dnd events reach this widget (eg with
490 * setup_xdnd_proxy() for the root window).
492 void drag_set_pinboard_dest(GtkWidget *widget)
494 GtkTargetEntry target_table[] = {
495 {"text/uri-list", 0, TARGET_URI_LIST},
498 gtk_drag_dest_set(widget,
499 GTK_DEST_DEFAULT_DROP,
500 target_table,
501 sizeof(target_table) / sizeof(*target_table),
502 GDK_ACTION_LINK);
503 gtk_signal_connect(GTK_OBJECT(widget), "drag_data_received",
504 (GtkSignalFunc) desktop_drag_data_received,
505 NULL);
508 #ifndef GTK2
509 static void scrolled(GtkAdjustment *adj, Collection *collection)
511 collection_set_cursor_item(collection, -1);
512 dnd_spring_abort();
514 #endif
516 /* Called during the drag when the mouse is in a widget registered
517 * as a drop target. Returns TRUE if we can accept the drop.
519 static gboolean drag_motion(GtkWidget *widget,
520 GdkDragContext *context,
521 gint x,
522 gint y,
523 guint time,
524 FilerWindow *filer_window)
526 DirItem *item;
527 int item_number;
528 GdkDragAction action = context->suggested_action;
529 char *new_path = NULL;
530 char *type = NULL;
531 gboolean retval = FALSE;
533 if (filer_window->collection->auto_scroll == -1)
534 collection_set_autoscroll(filer_window->collection, TRUE);
536 if (option_get_int("dnd_drag_to_icons"))
537 item_number = collection_get_item(filer_window->collection,
538 x, y);
539 else
540 item_number = -1;
542 item = item_number >= 0
543 ? (DirItem *) filer_window->collection->items[item_number].data
544 : NULL;
546 if (item && filer_window->collection->items[item_number].selected)
547 type = NULL;
548 else
549 type = dnd_motion_item(context, &item);
551 if (!type)
552 item = NULL;
554 /* Don't allow drops to non-writeable directories. BUT, still
555 * allow drops on non-writeable SUBdirectories so that we can
556 * do the spring-open thing.
558 if (item && type == drop_dest_dir &&
559 !(item->flags & ITEM_FLAG_APPDIR))
561 #ifndef GTK2
562 /* XXX: Do we still need this under 2.0? */
563 GtkObject *vadj = GTK_OBJECT(filer_window->collection->vadj);
565 /* Subdir: prepare for spring-open */
566 if (scrolled_adj != vadj)
568 if (scrolled_adj)
569 gtk_signal_disconnect(scrolled_adj,
570 scrolled_signal);
571 scrolled_adj = vadj;
572 scrolled_signal = gtk_signal_connect(
573 scrolled_adj,
574 "value_changed",
575 GTK_SIGNAL_FUNC(scrolled),
576 filer_window->collection);
578 #endif
579 dnd_spring_load(context, filer_window);
581 else
582 dnd_spring_abort();
584 if (item)
586 collection_set_cursor_item(filer_window->collection,
587 item_number);
589 else
591 collection_set_cursor_item(filer_window->collection, -1);
593 /* Disallow background drops within a single window */
594 if (type && gtk_drag_get_source_widget(context) == widget)
595 type = NULL;
598 if (type)
600 if (item)
601 new_path = make_path(filer_window->path,
602 item->leafname)->str;
603 else
604 new_path = filer_window->path;
607 g_dataset_set_data(context, "drop_dest_type", type);
608 if (type)
610 gdk_drag_status(context, action, time);
611 g_dataset_set_data_full(context, "drop_dest_path",
612 g_strdup(new_path), g_free);
613 retval = TRUE;
616 return retval;
619 /* item is the item the file is held over, NULL for directory background.
620 * 'item' may be NULL on exit if the drop should be treated as onto the
621 * background. Disallow drags to a selected icon before calling this.
623 * Returns NULL to reject the drop, or drop_dest_prog/drop_dest_dir to
624 * accept. Build the path based on item.
626 guchar *dnd_motion_item(GdkDragContext *context, DirItem **item_p)
628 DirItem *item = *item_p;
630 if (item)
632 /* If we didn't drop onto a directory, application or
633 * executable file then act as though the drop is to the
634 * window background.
636 if (item->base_type != TYPE_DIRECTORY
637 && !(item->mime_type == special_exec))
639 item = NULL;
640 *item_p = NULL;
644 if (!item)
646 /* Drop onto the window background */
648 return drop_dest_dir;
651 /* Drop onto a program/directory of some sort */
653 if (item->base_type == TYPE_DIRECTORY &&
654 !(item->flags & ITEM_FLAG_APPDIR))
656 /* A normal directory */
657 if (provides(context, text_uri_list) ||
658 provides(context, XdndDirectSave0))
659 return drop_dest_dir;
661 else
663 if (provides(context, text_uri_list) ||
664 provides(context, application_octet_stream))
665 return drop_dest_prog;
668 return NULL;
671 /* Remove highlights */
672 static void drag_leave(GtkWidget *widget,
673 GdkDragContext *context,
674 guint32 time,
675 FilerWindow *filer_window)
677 collection_set_autoscroll(filer_window->collection, FALSE);
678 collection_set_cursor_item(filer_window->collection, -1);
679 dnd_spring_abort();
680 if (scrolled_adj)
682 gtk_signal_disconnect(scrolled_adj,
683 scrolled_signal);
684 scrolled_adj = NULL;
688 /* User has tried to drop some data on us. Decide what format we would
689 * like the data in.
691 static gboolean drag_drop(GtkWidget *widget,
692 GdkDragContext *context,
693 gint x,
694 gint y,
695 guint time,
696 gpointer data)
698 char *error = NULL;
699 char *leafname = NULL;
700 GdkAtom target = GDK_NONE;
701 char *dest_path;
702 char *dest_type = NULL;
704 dest_path = g_dataset_get_data(context, "drop_dest_path");
705 dest_type = g_dataset_get_data(context, "drop_dest_type");
707 g_return_val_if_fail(dest_path != NULL, TRUE);
709 if (dest_type == drop_dest_dir && provides(context, XdndDirectSave0))
711 leafname = get_xds_prop(context);
712 if (leafname)
714 if (strchr(leafname, '/'))
716 error = _("XDS protocol error: "
717 "leafname may not contain '/'\n");
718 g_free(leafname);
720 leafname = NULL;
722 else
724 GString *uri;
726 uri = g_string_new(NULL);
727 g_string_sprintf(uri, "file://%s%s",
728 our_host_name_for_dnd(),
729 make_path(dest_path,
730 leafname)->str);
731 set_xds_prop(context, uri->str);
732 g_string_free(uri, TRUE);
734 target = XdndDirectSave0;
735 g_dataset_set_data_full(context, "leafname",
736 leafname, g_free);
739 else
740 error = _(
741 "XdndDirectSave0 target provided, but the atom "
742 "XdndDirectSave0 (type text/plain) did not "
743 "contain a leafname\n");
745 else if (provides(context, text_uri_list))
746 target = text_uri_list;
747 else if (provides(context, application_octet_stream))
748 target = application_octet_stream;
749 else
751 if (dest_type == drop_dest_dir)
752 error = _("Sorry - I require a target type of "
753 "text/uri-list or XdndDirectSave0.");
754 else
755 error = _("Sorry - I require a target type of "
756 "text/uri-list or application/octet-stream.");
759 if (error)
761 gtk_drag_finish(context, FALSE, FALSE, time); /* Failure */
763 delayed_error("%s", error);
765 else
766 gtk_drag_get_data(widget, context, target, time);
768 return TRUE;
771 /* Called when a text/uri-list arrives */
772 static void desktop_drag_data_received(GtkWidget *widget,
773 GdkDragContext *context,
774 gint x,
775 gint y,
776 GtkSelectionData *selection_data,
777 guint info,
778 guint32 time,
779 FilerWindow *filer_window)
781 GList *uris, *next;
782 gint dx, dy;
784 if (!selection_data->data)
786 /* Timeout? */
787 return;
790 if (pinboard_drag_in_progress)
792 pinboard_move_icons();
793 return;
796 gdk_window_get_position(widget->window, &dx, &dy);
797 x += dx;
798 y += dy;
800 uris = uri_list_to_glist(selection_data->data);
802 for (next = uris; next; next = next->next)
804 guchar *path;
806 path = get_local_path((gchar *) next->data);
807 if (path)
809 pinboard_pin(path, NULL, x, y);
810 x += 64;
813 g_free(next->data);
816 if (uris)
817 g_list_free(uris);
820 /* Called when some data arrives from the remote app (which we asked for
821 * in drag_drop).
823 static void drag_data_received(GtkWidget *widget,
824 GdkDragContext *context,
825 gint x,
826 gint y,
827 GtkSelectionData *selection_data,
828 guint info,
829 guint32 time,
830 gpointer user_data)
832 if (!selection_data->data)
834 /* Timeout? */
835 gtk_drag_finish(context, FALSE, FALSE, time); /* Failure */
836 return;
839 switch (info)
841 case TARGET_XDS:
842 got_data_xds_reply(widget, context,
843 selection_data, time);
844 break;
845 case TARGET_RAW:
846 got_data_raw(widget, context, selection_data, time);
847 break;
848 case TARGET_URI_LIST:
849 got_uri_list(widget, context, selection_data, time);
850 break;
851 default:
852 gtk_drag_finish(context, FALSE, FALSE, time);
853 delayed_error("drag_data_received: %s",
854 _("Unknown target"));
855 break;
859 static void got_data_xds_reply(GtkWidget *widget,
860 GdkDragContext *context,
861 GtkSelectionData *selection_data,
862 guint32 time)
864 gboolean mark_unsafe = TRUE;
865 char response = *selection_data->data;
866 char *error = NULL;
867 char *dest_path;
869 dest_path = g_dataset_get_data(context, "drop_dest_path");
871 if (selection_data->length != 1)
872 response = '?';
874 if (response == 'F')
876 /* Sender couldn't save there - ask for another
877 * type if possible.
879 if (provides(context, application_octet_stream))
881 mark_unsafe = FALSE; /* Wait and see */
883 gtk_drag_get_data(widget, context,
884 application_octet_stream, time);
886 else
887 error = _("Remote app can't or won't send me "
888 "the data - sorry");
890 else if (response == 'S')
892 /* Success - data is saved */
893 mark_unsafe = FALSE; /* It really is safe */
894 gtk_drag_finish(context, TRUE, FALSE, time);
896 refresh_dirs(dest_path);
898 else if (response != 'E')
900 error = _("XDS protocol error: "
901 "return code should be 'S', 'F' or 'E'\n");
903 /* else: error has been reported by the sender */
905 if (mark_unsafe)
907 set_xds_prop(context, "");
908 /* Unsave also implies that the drag failed */
909 gtk_drag_finish(context, FALSE, FALSE, time);
912 if (error)
913 delayed_error("%s", error);
916 static void got_data_raw(GtkWidget *widget,
917 GdkDragContext *context,
918 GtkSelectionData *selection_data,
919 guint32 time)
921 char *leafname;
922 int fd;
923 char *error = NULL;
924 char *dest_path;
926 g_return_if_fail(selection_data->data != NULL);
928 dest_path = g_dataset_get_data(context, "drop_dest_path");
930 if (context->action == GDK_ACTION_ASK)
932 gtk_drag_finish(context, FALSE, FALSE, time); /* Failure */
933 delayed_error(_("Sorry, can't display a menu of actions "
934 "for a remote file / raw data."));
935 return;
938 if (g_dataset_get_data(context, "drop_dest_type") == drop_dest_prog)
940 /* The data needs to be sent to an application */
941 run_with_data(dest_path,
942 selection_data->data, selection_data->length);
943 gtk_drag_finish(context, TRUE, FALSE, time); /* Success! */
944 return;
947 leafname = g_dataset_get_data(context, "leafname");
948 if (!leafname)
949 leafname = _("UntitledData");
951 fd = open(make_path(dest_path, leafname)->str,
952 O_WRONLY | O_CREAT | O_EXCL | O_NOCTTY,
953 S_IRUSR | S_IRGRP | S_IROTH |
954 S_IWUSR | S_IWGRP | S_IWOTH);
956 if (fd == -1)
957 error = g_strerror(errno);
958 else
960 if (write(fd,
961 selection_data->data,
962 selection_data->length) == -1)
963 error = g_strerror(errno);
965 if (close(fd) == -1 && !error)
966 error = g_strerror(errno);
968 refresh_dirs(dest_path);
971 if (error)
973 if (provides(context, XdndDirectSave0))
974 set_xds_prop(context, "");
975 gtk_drag_finish(context, FALSE, FALSE, time); /* Failure */
976 delayed_error(_("Error saving file: %s"), error);
978 else
979 gtk_drag_finish(context, TRUE, FALSE, time); /* Success! */
982 /* We've got a list of URIs from somewhere (probably another filer window).
983 * If the files are on the local machine then try to copy them ourselves,
984 * otherwise, if there was only one file and application/octet-stream was
985 * provided, get the data via the X server.
987 static void got_uri_list(GtkWidget *widget,
988 GdkDragContext *context,
989 GtkSelectionData *selection_data,
990 guint32 time)
992 GList *uri_list;
993 char *error = NULL;
994 GList *next_uri;
995 gboolean send_reply = TRUE;
996 char *dest_path;
997 char *type;
999 dest_path = g_dataset_get_data(context, "drop_dest_path");
1000 type = g_dataset_get_data(context, "drop_dest_type");
1002 g_return_if_fail(dest_path != NULL);
1004 uri_list = uri_list_to_glist(selection_data->data);
1006 if (!uri_list)
1007 error = _("No URIs in the text/uri-list (nothing to do!)");
1008 else if (context->action != GDK_ACTION_ASK && type == drop_dest_prog)
1009 run_with_files(dest_path, uri_list);
1010 else if ((!uri_list->next) && (!get_local_path(uri_list->data)))
1012 /* There is one URI in the list, and it's not on the local
1013 * machine. Get it via the X server if possible.
1016 if (provides(context, application_octet_stream))
1018 char *leaf;
1019 leaf = strrchr(uri_list->data, '/');
1020 if (leaf)
1021 leaf++;
1022 else
1023 leaf = uri_list->data;
1024 g_dataset_set_data_full(context, "leafname",
1025 g_strdup(leaf), g_free);
1026 gtk_drag_get_data(widget, context,
1027 application_octet_stream, time);
1028 send_reply = FALSE;
1030 else
1031 error = _("Can't get data from remote machine "
1032 "(application/octet-stream not provided)");
1034 else
1036 GList *local_paths = NULL;
1037 GList *next;
1039 /* Either one local URI, or a list. If everything in the list
1040 * isn't local then we are stuck.
1043 for (next_uri = uri_list; next_uri; next_uri = next_uri->next)
1045 char *path;
1047 path = get_local_path((char *) next_uri->data);
1049 if (path)
1050 local_paths = g_list_append(local_paths,
1051 g_strdup(path));
1052 else
1053 error = _("Some of these files are on a "
1054 "different machine - they will be "
1055 "ignored - sorry");
1058 if (!local_paths)
1060 error = _("None of these files are on the local "
1061 "machine - I can't operate on multiple "
1062 "remote files - sorry.");
1064 else if (context->action == GDK_ACTION_ASK)
1065 prompt_action(local_paths, dest_path);
1066 else if (context->action == GDK_ACTION_MOVE)
1067 action_move(local_paths, dest_path, NULL, -1);
1068 else if (context->action == GDK_ACTION_COPY)
1069 action_copy(local_paths, dest_path, NULL, -1);
1070 else if (context->action == GDK_ACTION_LINK)
1071 action_link(local_paths, dest_path, NULL);
1072 else
1073 error = _("Unknown action requested");
1075 for (next = local_paths; next; next = next->next)
1076 g_free(next->data);
1077 g_list_free(local_paths);
1080 if (error)
1082 gtk_drag_finish(context, FALSE, FALSE, time); /* Failure */
1083 delayed_error(_("Error getting file list: %s"), error);
1085 else if (send_reply)
1086 gtk_drag_finish(context, TRUE, FALSE, time); /* Success! */
1088 next_uri = uri_list;
1089 while (next_uri)
1091 g_free(next_uri->data);
1092 next_uri = next_uri->next;
1094 g_list_free(uri_list);
1097 /* Called when an item from the ACTION_ASK menu is chosen */
1098 static void menuitem_response(gpointer data, guint action, GtkWidget *widget)
1100 if (action == MENU_MOVE)
1101 action_move(prompt_local_paths, prompt_dest_path, NULL, -1);
1102 else if (action == MENU_COPY)
1103 action_copy(prompt_local_paths, prompt_dest_path, NULL, -1);
1104 else if (action == MENU_LINK)
1105 action_link(prompt_local_paths, prompt_dest_path, NULL);
1106 else if (action == MENU_SET_ICON)
1108 if (g_list_length(prompt_local_paths) == 1)
1109 set_icon_path(prompt_dest_path,
1110 (char*) prompt_local_paths->data);
1111 else
1112 delayed_error(
1113 _("You can't use multiple files with Set Icon!"));
1117 /* When some local files are dropped somewhere with ACTION_ASK, this
1118 * function is called to display the menu.
1120 static void prompt_action(GList *paths, gchar *dest)
1122 GList *next;
1124 if (prompt_local_paths)
1126 g_list_foreach(prompt_local_paths, (GFunc) g_free, NULL);
1127 g_list_free(prompt_local_paths);
1128 g_free(prompt_dest_path);
1130 prompt_dest_path = NULL;
1131 prompt_local_paths = NULL;
1134 /* Make a copy of the arguments */
1135 for (next = paths; next; next = next->next)
1136 prompt_local_paths = g_list_append(prompt_local_paths,
1137 g_strdup((gchar *) next->data));
1138 prompt_dest_path = g_strdup(dest);
1140 /* Shade 'Set Icon' if there are multiple files */
1141 menu_set_items_shaded(dnd_menu, g_list_length(paths) != 1, 4, 1);
1143 show_popup_menu(dnd_menu, gtk_get_current_event(), 1);
1147 /* SPRING-LOADING */
1149 /* This is the code that makes directories pop open if you hold a
1150 * file over them...
1152 * First, call dnd_spring_load(context) to arm the system.
1153 * After a timeout (1/2 a second) the dest_path directory will be
1154 * opened in a new window, unless dnd_spring_abort is called first.
1157 static gint spring_timeout = -1;
1158 static GdkDragContext *spring_context = NULL;
1159 static FilerWindow *spring_window = NULL;
1160 static FilerWindow *spring_src_window = NULL;
1162 void dnd_spring_load(GdkDragContext *context, FilerWindow *src_win)
1164 g_return_if_fail(context != NULL);
1166 if (!option_get_int("dnd_spring_open"))
1167 return;
1169 if (spring_context)
1170 dnd_spring_abort();
1172 spring_context = context;
1173 gdk_drag_context_ref(spring_context);
1174 spring_src_window = src_win;
1175 spring_timeout = gtk_timeout_add(
1176 option_get_int("dnd_spring_delay"), spring_now, NULL);
1179 void dnd_spring_abort(void)
1181 if (!spring_context)
1182 return;
1184 gdk_drag_context_unref(spring_context);
1185 spring_context = NULL;
1186 gtk_timeout_remove(spring_timeout);
1189 /* If all mod keys are released, no buttons are pressed, and the
1190 * mouse is outside the spring window, then close it.
1192 static gboolean spring_check_idle(gpointer data)
1194 int p_x, p_y;
1196 if (!spring_window)
1197 return FALSE;
1199 if (!get_pointer_xy(&p_x, &p_y))
1202 GdkWindow *win = spring_window->window->window;
1203 int x, y;
1204 int w, h;
1206 gdk_window_get_position(win, &x, &y);
1207 gdk_window_get_size(win, &w, &h);
1209 if (p_x < x || p_x > x + w || p_y < y || p_y > y + h)
1213 gtk_widget_destroy(spring_window->window);
1214 return FALSE; /* Got it! */
1217 return TRUE; /* Try again later */
1220 static gboolean spring_now(gpointer data)
1222 gboolean old_unique = o_unique_filer_windows;
1223 guchar *dest_path;
1224 gint x, y;
1226 g_return_val_if_fail(spring_context != NULL, FALSE);
1228 dest_path = g_dataset_get_data(spring_context, "drop_dest_path");
1229 g_return_val_if_fail(dest_path != NULL, FALSE);
1232 * Note: Due to a bug in gtk, if a window disappears during
1233 * a drag and the pointer moves over where the window was,
1234 * the sender crashes! Therefore, do not close any windows
1235 * while dragging! (fixed in later versions)
1238 if (spring_window)
1239 gtk_widget_destroy(spring_window->window);
1242 get_pointer_xy(&x, &y);
1244 o_unique_filer_windows = FALSE;
1245 if (spring_window)
1247 collection_set_cursor_item(spring_window->collection, -1);
1248 filer_change_to(spring_window, dest_path, NULL);
1249 /* DON'T move the window. Gtk+ sometimes doesn't
1250 * notice :-(
1253 else
1255 spring_window = filer_opendir(dest_path, spring_src_window);
1256 if (spring_window)
1258 gtk_timeout_add(500, spring_check_idle, NULL);
1259 gtk_signal_connect(GTK_OBJECT(spring_window->window),
1260 "destroy",
1261 GTK_SIGNAL_FUNC(spring_win_destroyed),
1262 NULL);
1263 centre_window(spring_window->window->window, x, y);
1266 o_unique_filer_windows = old_unique;
1268 dnd_spring_abort();
1270 return FALSE;
1273 static void spring_win_destroyed(GtkWidget *widget, gpointer data)
1275 spring_window = NULL;
1278 /* HANDLING MOTION EVENTS */
1280 /* If not-NULL, then this widget has a grab */
1281 static GtkWidget *motion_widget = NULL;
1283 /* If TRUE, we must gdk_pointer_ungrab() too when finishing */
1284 static gboolean motion_pointer_grab = FALSE;
1286 /* Call this on a button press event. It stores the mouse position
1287 * as the start of the new drag and returns TRUE if all is well.
1288 * Further motions events are disabled at this point - you must
1289 * then call dnd_motion_start() to set the type of motion expected.
1290 * Grabs the widget on the first press.
1292 * If the system is not ready to handle a motion event (because a
1293 * button is already held down?) it does nothing and returns FALSE.
1295 * If the event is not a single click then it simply returns TRUE.
1297 gboolean dnd_motion_press(GtkWidget *widget, GdkEventButton *event)
1299 if (event->type != GDK_BUTTON_PRESS)
1300 return TRUE; /* Not a click event! */
1302 motion_buttons_pressed++;
1303 if (motion_buttons_pressed == 1)
1305 /* g_print("[ grab! ]\n"); */
1306 gtk_grab_add(widget);
1307 motion_widget = widget;
1310 if (motion_state != MOTION_NONE)
1311 return FALSE; /* Ignore clicks - we're busy! */
1313 motion_state = MOTION_DISABLED;
1314 drag_start_x = event->x_root;
1315 drag_start_y = event->y_root;
1317 return TRUE;
1320 /* After the button press event, decide what kind of motion is expected.
1321 * If you don't call this then the motion system is disabled - call
1322 * dnd_motion_release() to reset it.
1324 * Note: If you open a popup menu or start DND call dnd_motion_ungrab()
1325 * instead.
1327 void dnd_motion_start(MotionType motion)
1329 g_return_if_fail(motion_state == MOTION_DISABLED);
1331 motion_state = motion;
1334 /* Call this on a button release event. If some buttons are still pressed,
1335 * returns TRUE and does nothing.
1337 * Otherwise, it resets the motion system to be ready again and returns TRUE.
1339 * If the motion system wasn't being used (MOTION_NONE) then it does nothing
1340 * and returns FALSE - process the release event yourself as it isn't part
1341 * of a motion. This also happens if a motion was primed but never happened.
1343 gboolean dnd_motion_release(GdkEventButton *event)
1345 MotionType motion = motion_state;
1346 int dx, dy;
1348 if (motion_buttons_pressed == 0)
1349 return TRUE; /* We were disabled */
1351 if (motion_buttons_pressed == 1)
1352 dnd_motion_ungrab();
1353 else
1355 motion_buttons_pressed--;
1356 return TRUE;
1359 if (motion == MOTION_REPOSITION || motion == MOTION_DISABLED)
1360 return TRUE; /* Already done something - eat the event */
1362 /* Eat release events that happen too far from the click
1363 * source. Otherwise, allow the caller to treat this as a click
1364 * that never became a motion.
1366 dx = event->x_root - drag_start_x;
1367 dy = event->y_root - drag_start_y;
1369 return ABS(dx) > 5 || ABS(dy) > 5;
1372 /* Use this to disable the motion system. The system will be reset once
1373 * all mouse buttons are released.
1375 void dnd_motion_disable(void)
1377 g_return_if_fail(motion_state != MOTION_NONE &&
1378 motion_state != MOTION_DISABLED);
1380 motion_state = MOTION_DISABLED;
1383 /* Use this if something else is going to grab the pointer so that
1384 * we won't get any more motion or release events.
1386 void dnd_motion_ungrab(void)
1388 if (motion_buttons_pressed > 0)
1390 if (motion_pointer_grab)
1392 gdk_pointer_ungrab(GDK_CURRENT_TIME);
1393 motion_pointer_grab = FALSE;
1394 /* g_print("[ ungrab_pointer ]\n"); */
1396 gtk_grab_remove(motion_widget);
1397 motion_widget = NULL;
1398 motion_buttons_pressed = 0;
1399 /* g_print("[ ungrab ]\n"); */
1402 motion_state = MOTION_NONE;
1405 /* Call this on motion events. If the mouse position is far enough
1406 * from the click position, returns TRUE and does dnd_motion_ungrab().
1407 * You should then start regular drag-and-drop.
1409 * Otherwise, returns FALSE.
1411 gboolean dnd_motion_moved(GdkEventMotion *event)
1413 int dx, dy;
1415 dx = event->x_root - drag_start_x;
1416 dy = event->y_root - drag_start_y;
1418 if (ABS(dx) <= 5 && ABS(dy) <= 5)
1419 return FALSE; /* Not far enough */
1421 dnd_motion_ungrab();
1423 return TRUE;
1426 /* Normally, the X server will automatically grab the pointer on a
1427 * button press and ungrab on release. However, if the grab widget
1428 * is reparented then call this to re-aquire the grab.
1430 void dnd_motion_grab_pointer(void)
1432 g_return_if_fail(motion_widget != NULL);
1434 gdk_pointer_grab(motion_widget->window, FALSE,
1435 GDK_POINTER_MOTION_MASK |
1436 GDK_BUTTON_RELEASE_MASK,
1437 FALSE, NULL, GDK_CURRENT_TIME);
1439 motion_pointer_grab = TRUE;