r1996: Cope slightly better with invalid filenames in various places (reported by
[rox-filer.git] / ROX-Filer / src / dnd.c
blob0bab131eb8f87ccb7612600726b499e6ed78f010
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 "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, const char *text);
74 static void desktop_drag_data_received(GtkWidget *widget,
75 GdkDragContext *context,
76 gint x,
77 gint y,
78 GtkSelectionData *selection_data,
79 guint info,
80 guint32 time,
81 FilerWindow *filer_window);
82 static void got_data_xds_reply(GtkWidget *widget,
83 GdkDragContext *context,
84 GtkSelectionData *selection_data,
85 guint32 time);
86 static void got_data_raw(GtkWidget *widget,
87 GdkDragContext *context,
88 GtkSelectionData *selection_data,
89 guint32 time);
90 static void got_uri_list(GtkWidget *widget,
91 GdkDragContext *context,
92 GtkSelectionData *selection_data,
93 guint32 time);
94 static gboolean drag_drop(GtkWidget *widget,
95 GdkDragContext *context,
96 gint x,
97 gint y,
98 guint time,
99 gpointer data);
100 static void drag_data_received(GtkWidget *widget,
101 GdkDragContext *context,
102 gint x,
103 gint y,
104 GtkSelectionData *selection_data,
105 guint info,
106 guint32 time,
107 gpointer user_data);
108 static gboolean spring_now(gpointer data);
109 static void spring_win_destroyed(GtkWidget *widget, gpointer data);
110 static void menuitem_response(gpointer data, guint action, GtkWidget *widget);
111 static void prompt_action(GList *paths, gchar *dest);
113 typedef enum {
114 MENU_COPY,
115 MENU_MOVE,
116 MENU_LINK,
117 MENU_SET_ICON,
118 } MenuActionType;
120 #undef N_
121 #define N_(x) x
122 static GtkItemFactoryEntry menu_def[] = {
123 {N_("Copy"), NULL, menuitem_response, MENU_COPY, NULL},
124 {N_("Move"), NULL, menuitem_response, MENU_MOVE, NULL},
125 {N_("Link"), NULL, menuitem_response, MENU_LINK, NULL},
126 {"", NULL, NULL, 0, "<Separator>"},
127 {N_("Set Icon"), NULL, menuitem_response, MENU_SET_ICON, NULL},
129 static GtkWidget *dnd_menu = NULL;
131 /* Possible values for drop_dest_type (can also be NULL).
132 * In either case, drop_dest_path is the app/file/dir to use.
134 const char *drop_dest_prog = "drop_dest_prog"; /* Run a program */
135 const char *drop_dest_dir = "drop_dest_dir"; /* Save to path */
137 GdkAtom XdndDirectSave0;
138 GdkAtom xa_text_plain;
139 GdkAtom text_uri_list;
140 GdkAtom application_octet_stream;
141 GdkAtom xa_string; /* Not actually used for DnD, but the others are here! */
143 Option o_dnd_drag_to_icons;
144 Option o_dnd_spring_open;
145 static Option o_dnd_spring_delay;
146 static Option o_dnd_middle_menu;
148 void dnd_init(void)
150 XdndDirectSave0 = gdk_atom_intern("XdndDirectSave0", FALSE);
151 xa_text_plain = gdk_atom_intern("text/plain", FALSE);
152 text_uri_list = gdk_atom_intern("text/uri-list", FALSE);
153 application_octet_stream = gdk_atom_intern("application/octet-stream",
154 FALSE);
155 xa_string = gdk_atom_intern("STRING", FALSE);
157 option_add_int(&o_dnd_drag_to_icons, "dnd_drag_to_icons", 1);
158 option_add_int(&o_dnd_spring_open, "dnd_spring_open", 0);
159 option_add_int(&o_dnd_spring_delay, "dnd_spring_delay", 400);
160 option_add_int(&o_dnd_middle_menu, "dnd_middle_menu", TRUE);
163 /* SUPPORT FUNCTIONS */
165 /* Set the XdndDirectSave0 property on the source window for this context */
166 static void set_xds_prop(GdkDragContext *context, const char *text)
168 gdk_property_change(context->source_window,
169 XdndDirectSave0,
170 xa_text_plain, 8,
171 GDK_PROP_MODE_REPLACE,
172 text,
173 strlen(text));
176 static char *get_xds_prop(GdkDragContext *context)
178 guchar *prop_text;
179 gint length;
181 if (gdk_property_get(context->source_window,
182 XdndDirectSave0,
183 xa_text_plain,
184 0, MAXURILEN,
185 FALSE,
186 NULL, NULL,
187 &length, &prop_text) && prop_text)
189 /* Terminate the string */
190 prop_text = g_realloc(prop_text, length + 1);
191 prop_text[length] = '\0';
192 return prop_text;
195 return NULL;
198 /* Is the sender willing to supply this target type? */
199 gboolean provides(GdkDragContext *context, GdkAtom target)
201 GList *targets = context->targets;
203 while (targets && ((GdkAtom) targets->data != target))
204 targets = targets->next;
206 return targets != NULL;
209 /* Convert a list of URIs into a list of strings.
210 * Lines beginning with # are skipped.
211 * The text block passed in is zero terminated (after the final CRLF)
213 GList *uri_list_to_glist(const char *uri_list)
215 GList *list = NULL;
217 while (*uri_list)
219 char *linebreak;
220 char *uri;
221 int length;
223 linebreak = strchr(uri_list, 13);
225 if (!linebreak || linebreak[1] != 10)
227 delayed_error("uri_list_to_glist: %s",
228 _("Incorrect or missing line "
229 "break in text/uri-list data"));
230 return list;
233 length = linebreak - uri_list;
235 if (length && uri_list[0] != '#')
237 uri = g_strndup(uri_list, length);
238 list = g_list_append(list, uri);
241 uri_list = linebreak + 2;
244 return list;
247 /* DRAGGING FROM US */
249 /* The user has held the mouse button down over a group of item and moved -
250 * start a drag. 'uri_list' is copied, so you can delete it straight away.
252 void drag_selection(GtkWidget *widget, GdkEventMotion *event, guchar *uri_list)
254 GdkPixbuf *pixbuf;
255 GdkDragContext *context;
256 GdkDragAction actions;
257 GtkTargetList *target_list;
258 GtkTargetEntry target_table[] = {
259 {"text/uri-list", 0, TARGET_URI_LIST},
262 if (event->state & GDK_BUTTON1_MASK)
263 actions = GDK_ACTION_COPY | GDK_ACTION_MOVE
264 | GDK_ACTION_LINK | GDK_ACTION_ASK;
265 else
267 if (o_dnd_middle_menu.int_value)
268 actions = GDK_ACTION_ASK;
269 else
270 actions = GDK_ACTION_MOVE;
273 target_list = gtk_target_list_new(target_table, 1);
275 context = gtk_drag_begin(widget,
276 target_list,
277 actions,
278 (event->state & GDK_BUTTON1_MASK) ? 1 :
279 (event->state & GDK_BUTTON2_MASK) ? 2 : 3,
280 (GdkEvent *) event);
282 g_dataset_set_data_full(context, "uri_list",
283 g_strdup(uri_list), g_free);
285 pixbuf = gtk_widget_render_icon(widget, GTK_STOCK_DND_MULTIPLE,
286 GTK_ICON_SIZE_DIALOG, NULL);
287 gtk_drag_set_icon_pixbuf(context, pixbuf, 0, 0);
288 g_object_unref(pixbuf);
291 /* Copy/Load this item into another directory/application */
292 void drag_one_item(GtkWidget *widget,
293 GdkEventMotion *event,
294 const guchar *full_path,
295 DirItem *item,
296 MaskedPixmap *image)
298 guchar *uri;
299 GdkDragContext *context;
300 GdkDragAction actions;
301 GtkTargetList *target_list;
302 GtkTargetEntry target_table[] = {
303 {"text/uri-list", 0, TARGET_URI_LIST},
304 {"application/octet-stream", 0, TARGET_RAW},
305 {"", 0, TARGET_RAW},
308 g_return_if_fail(full_path != NULL);
309 g_return_if_fail(item != NULL);
311 if (!image)
312 image = item->image;
314 if (item->base_type == TYPE_FILE)
316 MIME_type *t = item->mime_type;
318 target_table[2].target = g_strconcat(t->media_type, "/",
319 t->subtype, NULL);
320 target_list = gtk_target_list_new(target_table, 3);
321 g_free(target_table[2].target);
323 else
324 target_list = gtk_target_list_new(target_table, 1);
326 if (event->state & GDK_BUTTON1_MASK)
327 actions = GDK_ACTION_COPY | GDK_ACTION_ASK
328 | GDK_ACTION_MOVE | GDK_ACTION_LINK;
329 else
331 if (o_dnd_middle_menu.int_value)
332 actions = GDK_ACTION_ASK;
333 else
334 actions = GDK_ACTION_MOVE;
337 context = gtk_drag_begin(widget,
338 target_list,
339 actions,
340 (event->state & GDK_BUTTON1_MASK) ? 1 :
341 (event->state & GDK_BUTTON2_MASK) ? 2 : 3,
342 (GdkEvent *) event);
344 g_dataset_set_data_full(context, "full_path",
345 g_strdup(full_path), g_free);
346 uri = g_strconcat("file://", our_host_name_for_dnd(),
347 full_path, "\r\n", NULL);
348 g_dataset_set_data_full(context, "uri_list", uri, g_free);
350 g_return_if_fail(image != NULL);
352 gtk_drag_set_icon_pixbuf(context, image->pixbuf, 0, 0);
355 /* Called when a remote app wants us to send it some data.
356 * TODO: Maybe we should handle errors better (ie, let the remote app know
357 * the drag has failed)?
359 void drag_data_get(GtkWidget *widget,
360 GdkDragContext *context,
361 GtkSelectionData *selection_data,
362 guint info,
363 guint32 time,
364 gpointer data)
366 char *to_send = "E"; /* Default to sending an error */
367 long to_send_length = 1;
368 gboolean delete_once_sent = FALSE;
369 GdkAtom type;
370 guchar *path;
372 type = gdk_x11_xatom_to_atom(XA_STRING);
374 switch (info)
376 case TARGET_RAW:
377 path = g_dataset_get_data(context, "full_path");
378 if (path && load_file(path, &to_send, &to_send_length))
380 delete_once_sent = TRUE;
381 type = selection_data->target;
382 break;
384 g_warning("drag_data_get: Can't find path!\n");
385 return;
386 case TARGET_URI_LIST:
387 to_send = g_dataset_get_data(context, "uri_list");
388 to_send_length = strlen(to_send);
389 type = text_uri_list; /* (needed for xine) */
390 delete_once_sent = FALSE;
391 break;
392 default:
393 delayed_error("drag_data_get: %s",
394 _("Internal error - bad info type"));
395 break;
398 gtk_selection_data_set(selection_data,
399 type,
401 to_send,
402 to_send_length);
404 if (delete_once_sent)
405 g_free(to_send);
408 /* DRAGGING TO US */
410 /* Set up this widget as a drop-target.
411 * Does not attach any motion handlers.
413 void make_drop_target(GtkWidget *widget, GtkDestDefaults defaults)
415 GtkTargetEntry target_table[] =
417 {"text/uri-list", 0, TARGET_URI_LIST},
418 {"XdndDirectSave0", 0, TARGET_XDS},
419 {"application/octet-stream", 0, TARGET_RAW},
422 gtk_drag_dest_set(widget,
423 defaults,
424 target_table,
425 sizeof(target_table) / sizeof(*target_table),
426 GDK_ACTION_COPY | GDK_ACTION_ASK | GDK_ACTION_MOVE
427 | GDK_ACTION_LINK | GDK_ACTION_PRIVATE);
429 g_signal_connect(widget, "drag_drop", G_CALLBACK(drag_drop), NULL);
430 g_signal_connect(widget, "drag_data_received",
431 G_CALLBACK(drag_data_received), NULL);
434 /* Like drag_set_dest, but for a pinboard-type widget */
435 void drag_set_pinboard_dest(GtkWidget *widget)
437 GtkTargetEntry target_table[] = {
438 {"text/uri-list", 0, TARGET_URI_LIST},
441 gtk_drag_dest_set(widget,
442 GTK_DEST_DEFAULT_DROP,
443 target_table,
444 sizeof(target_table) / sizeof(*target_table),
445 GDK_ACTION_LINK);
446 g_signal_connect(widget, "drag_data_received",
447 G_CALLBACK(desktop_drag_data_received), NULL);
450 /* item is the item the file is held over, NULL for directory background.
451 * 'item' may be NULL on exit if the drop should be treated as onto the
452 * background. Disallow drags to a selected icon before calling this.
454 * Returns NULL to reject the drop, or drop_dest_prog/drop_dest_dir to
455 * accept. Build the path based on item.
457 const guchar *dnd_motion_item(GdkDragContext *context, DirItem **item_p)
459 DirItem *item = *item_p;
461 if (item)
463 /* If we didn't drop onto a directory, application or
464 * executable file then act as though the drop is to the
465 * window background.
467 if (item->base_type != TYPE_DIRECTORY
468 && !(item->mime_type == application_executable))
470 item = NULL;
471 *item_p = NULL;
475 if (!item)
477 /* Drop onto the window background */
479 return drop_dest_dir;
482 /* Drop onto a program/directory of some sort */
484 if (item->base_type == TYPE_DIRECTORY &&
485 !(item->flags & ITEM_FLAG_APPDIR))
487 /* A normal directory */
488 if (provides(context, text_uri_list) ||
489 provides(context, XdndDirectSave0))
490 return drop_dest_dir;
492 else
494 if (provides(context, text_uri_list) ||
495 provides(context, application_octet_stream))
496 return drop_dest_prog;
499 return NULL;
502 /* User has tried to drop some data on us. Decide what format we would
503 * like the data in.
505 static gboolean drag_drop(GtkWidget *widget,
506 GdkDragContext *context,
507 gint x,
508 gint y,
509 guint time,
510 gpointer data)
512 const char *error = NULL;
513 char *leafname = NULL;
514 GdkAtom target = GDK_NONE;
515 char *dest_path;
516 char *dest_type = NULL;
518 dest_path = g_dataset_get_data(context, "drop_dest_path");
519 dest_type = g_dataset_get_data(context, "drop_dest_type");
521 g_return_val_if_fail(dest_path != NULL, TRUE);
523 if (dest_type == drop_dest_dir && provides(context, XdndDirectSave0))
525 leafname = get_xds_prop(context);
526 if (leafname)
528 if (strchr(leafname, '/'))
530 error = _("XDS protocol error: "
531 "leafname may not contain '/'\n");
532 null_g_free(&leafname);
534 else
536 GString *uri;
538 uri = g_string_new(NULL);
539 g_string_sprintf(uri, "file://%s%s",
540 our_host_name_for_dnd(),
541 make_path(dest_path,
542 leafname)->str);
543 set_xds_prop(context, uri->str);
544 g_string_free(uri, TRUE);
546 target = XdndDirectSave0;
547 g_dataset_set_data_full(context, "leafname",
548 leafname, g_free);
551 else
552 error = _(
553 "XdndDirectSave0 target provided, but the atom "
554 "XdndDirectSave0 (type text/plain) did not "
555 "contain a leafname\n");
557 else if (provides(context, text_uri_list))
558 target = text_uri_list;
559 else if (provides(context, application_octet_stream))
560 target = application_octet_stream;
561 else
563 if (dest_type == drop_dest_dir)
564 error = _("Sorry - I require a target type of "
565 "text/uri-list or XdndDirectSave0.");
566 else
567 error = _("Sorry - I require a target type of "
568 "text/uri-list or application/octet-stream.");
571 if (error)
573 gtk_drag_finish(context, FALSE, FALSE, time); /* Failure */
575 delayed_error("%s", error);
577 else
578 gtk_drag_get_data(widget, context, target, time);
580 return TRUE;
583 /* Called when a text/uri-list arrives */
584 static void desktop_drag_data_received(GtkWidget *widget,
585 GdkDragContext *context,
586 gint x,
587 gint y,
588 GtkSelectionData *selection_data,
589 guint info,
590 guint32 time,
591 FilerWindow *filer_window)
593 GList *uris, *next;
594 gint dx, dy;
596 if (!selection_data->data)
598 /* Timeout? */
599 return;
602 if (pinboard_drag_in_progress)
604 pinboard_move_icons();
605 return;
608 gdk_window_get_position(widget->window, &dx, &dy);
609 x += dx;
610 y += dy;
612 uris = uri_list_to_glist(selection_data->data);
614 for (next = uris; next; next = next->next)
616 const guchar *path;
618 path = get_local_path((gchar *) next->data);
619 if (path)
621 pinboard_pin(path, NULL, x, y, NULL);
622 x += 64;
625 g_free(next->data);
628 if (uris)
629 g_list_free(uris);
632 /* Called when some data arrives from the remote app (which we asked for
633 * in drag_drop).
635 static void drag_data_received(GtkWidget *widget,
636 GdkDragContext *context,
637 gint x,
638 gint y,
639 GtkSelectionData *selection_data,
640 guint info,
641 guint32 time,
642 gpointer user_data)
644 if (!selection_data->data)
646 /* Timeout? */
647 gtk_drag_finish(context, FALSE, FALSE, time); /* Failure */
648 return;
651 switch (info)
653 case TARGET_XDS:
654 got_data_xds_reply(widget, context,
655 selection_data, time);
656 break;
657 case TARGET_RAW:
658 got_data_raw(widget, context, selection_data, time);
659 break;
660 case TARGET_URI_LIST:
661 got_uri_list(widget, context, selection_data, time);
662 break;
663 default:
664 gtk_drag_finish(context, FALSE, FALSE, time);
665 delayed_error("drag_data_received: %s",
666 _("Unknown target"));
667 break;
671 static void got_data_xds_reply(GtkWidget *widget,
672 GdkDragContext *context,
673 GtkSelectionData *selection_data,
674 guint32 time)
676 gboolean mark_unsafe = TRUE;
677 char response = *selection_data->data;
678 const char *error = NULL;
679 char *dest_path;
681 dest_path = g_dataset_get_data(context, "drop_dest_path");
683 if (selection_data->length != 1)
684 response = '?';
686 if (response == 'F')
688 /* Sender couldn't save there - ask for another
689 * type if possible.
691 if (provides(context, application_octet_stream))
693 mark_unsafe = FALSE; /* Wait and see */
695 gtk_drag_get_data(widget, context,
696 application_octet_stream, time);
698 else
699 error = _("Remote app can't or won't send me "
700 "the data - sorry");
702 else if (response == 'S')
704 /* Success - data is saved */
705 mark_unsafe = FALSE; /* It really is safe */
706 gtk_drag_finish(context, TRUE, FALSE, time);
708 refresh_dirs(dest_path);
710 else if (response != 'E')
712 error = _("XDS protocol error: "
713 "return code should be 'S', 'F' or 'E'\n");
715 /* else: error has been reported by the sender */
717 if (mark_unsafe)
719 set_xds_prop(context, "");
720 /* Unsave also implies that the drag failed */
721 gtk_drag_finish(context, FALSE, FALSE, time);
724 if (error)
725 delayed_error("%s", error);
728 static void got_data_raw(GtkWidget *widget,
729 GdkDragContext *context,
730 GtkSelectionData *selection_data,
731 guint32 time)
733 const char *leafname;
734 int fd;
735 const char *error = NULL;
736 const char *dest_path;
738 g_return_if_fail(selection_data->data != NULL);
740 dest_path = g_dataset_get_data(context, "drop_dest_path");
742 if (context->action == GDK_ACTION_ASK)
744 gtk_drag_finish(context, FALSE, FALSE, time); /* Failure */
745 delayed_error(_("Sorry, can't display a menu of actions "
746 "for a remote file / raw data."));
747 return;
750 if (g_dataset_get_data(context, "drop_dest_type") == drop_dest_prog)
752 /* The data needs to be sent to an application */
753 run_with_data(dest_path,
754 selection_data->data, selection_data->length);
755 gtk_drag_finish(context, TRUE, FALSE, time); /* Success! */
756 return;
759 leafname = g_dataset_get_data(context, "leafname");
760 if (!leafname)
761 leafname = _("UntitledData");
763 fd = open(make_path(dest_path, leafname)->str,
764 O_WRONLY | O_CREAT | O_EXCL | O_NOCTTY,
765 S_IRUSR | S_IRGRP | S_IROTH |
766 S_IWUSR | S_IWGRP | S_IWOTH);
768 if (fd == -1)
769 error = g_strerror(errno);
770 else
772 if (write(fd,
773 selection_data->data,
774 selection_data->length) == -1)
775 error = g_strerror(errno);
777 if (close(fd) == -1 && !error)
778 error = g_strerror(errno);
780 refresh_dirs(dest_path);
783 if (error)
785 if (provides(context, XdndDirectSave0))
786 set_xds_prop(context, "");
787 gtk_drag_finish(context, FALSE, FALSE, time); /* Failure */
788 delayed_error(_("Error saving file: %s"), error);
790 else
791 gtk_drag_finish(context, TRUE, FALSE, time); /* Success! */
794 /* We've got a list of URIs from somewhere (probably another filer window).
795 * If the files are on the local machine then try to copy them ourselves,
796 * otherwise, if there was only one file and application/octet-stream was
797 * provided, get the data via the X server.
799 static void got_uri_list(GtkWidget *widget,
800 GdkDragContext *context,
801 GtkSelectionData *selection_data,
802 guint32 time)
804 GList *uri_list;
805 const char *error = NULL;
806 GList *next_uri;
807 gboolean send_reply = TRUE;
808 char *dest_path;
809 char *type;
811 dest_path = g_dataset_get_data(context, "drop_dest_path");
812 type = g_dataset_get_data(context, "drop_dest_type");
814 g_return_if_fail(dest_path != NULL);
816 uri_list = uri_list_to_glist(selection_data->data);
818 if (!uri_list)
819 error = _("No URIs in the text/uri-list (nothing to do!)");
820 else if (context->action != GDK_ACTION_ASK && type == drop_dest_prog)
821 run_with_files(dest_path, uri_list);
822 else if ((!uri_list->next) && (!get_local_path(uri_list->data)))
824 /* There is one URI in the list, and it's not on the local
825 * machine. Get it via the X server if possible.
828 if (provides(context, application_octet_stream))
830 char *leaf;
831 leaf = strrchr(uri_list->data, '/');
832 if (leaf)
833 leaf++;
834 else
835 leaf = uri_list->data;
836 g_dataset_set_data_full(context, "leafname",
837 g_strdup(leaf), g_free);
838 gtk_drag_get_data(widget, context,
839 application_octet_stream, time);
840 send_reply = FALSE;
842 else
843 error = _("Can't get data from remote machine "
844 "(application/octet-stream not provided)");
846 else
848 GList *local_paths = NULL;
850 /* Either one local URI, or a list. If everything in the list
851 * isn't local then we are stuck.
854 for (next_uri = uri_list; next_uri; next_uri = next_uri->next)
856 const char *path;
858 path = get_local_path((char *) next_uri->data);
860 if (path)
861 local_paths = g_list_append(local_paths,
862 g_strdup(path));
863 else
864 error = _("Some of these files are on a "
865 "different machine - they will be "
866 "ignored - sorry");
869 if (!local_paths)
871 error = _("None of these files are on the local "
872 "machine - I can't operate on multiple "
873 "remote files - sorry.");
875 else if (context->action == GDK_ACTION_ASK)
876 prompt_action(local_paths, dest_path);
877 else if (context->action == GDK_ACTION_MOVE)
878 action_move(local_paths, dest_path, NULL, -1);
879 else if (context->action == GDK_ACTION_COPY)
880 action_copy(local_paths, dest_path, NULL, -1);
881 else if (context->action == GDK_ACTION_LINK)
882 action_link(local_paths, dest_path, NULL);
883 else
884 error = _("Unknown action requested");
886 destroy_glist(&local_paths);
889 if (error)
891 gtk_drag_finish(context, FALSE, FALSE, time); /* Failure */
892 delayed_error(_("Error getting file list: %s"), error);
894 else if (send_reply)
895 gtk_drag_finish(context, TRUE, FALSE, time); /* Success! */
897 destroy_glist(&uri_list);
900 /* Called when an item from the ACTION_ASK menu is chosen */
901 static void menuitem_response(gpointer data, guint action, GtkWidget *widget)
903 if (action == MENU_MOVE)
904 action_move(prompt_local_paths, prompt_dest_path, NULL, -1);
905 else if (action == MENU_COPY)
906 action_copy(prompt_local_paths, prompt_dest_path, NULL, -1);
907 else if (action == MENU_LINK)
908 action_link(prompt_local_paths, prompt_dest_path, NULL);
909 else if (action == MENU_SET_ICON)
911 if (g_list_length(prompt_local_paths) == 1)
912 set_icon_path(prompt_dest_path,
913 (char*) prompt_local_paths->data);
914 else
915 delayed_error(
916 _("You can't use multiple files with Set Icon!"));
920 /* When some local files are dropped somewhere with ACTION_ASK, this
921 * function is called to display the menu.
923 static void prompt_action(GList *paths, gchar *dest)
925 GList *next;
927 if (prompt_local_paths)
929 destroy_glist(&prompt_local_paths);
930 null_g_free(&prompt_dest_path);
933 /* Make a copy of the arguments */
934 for (next = paths; next; next = next->next)
935 prompt_local_paths = g_list_append(prompt_local_paths,
936 g_strdup((gchar *) next->data));
937 prompt_dest_path = g_strdup(dest);
939 if (!dnd_menu)
941 GtkItemFactory *item_factory;
943 item_factory = menu_create(menu_def,
944 sizeof(menu_def) / sizeof(*menu_def),
945 "<dnd>", NULL);
946 dnd_menu = gtk_item_factory_get_widget(item_factory, "<dnd>");
949 /* Shade 'Set Icon' if there are multiple files */
950 menu_set_items_shaded(dnd_menu, g_list_length(paths) != 1, 4, 1);
952 show_popup_menu(dnd_menu, gtk_get_current_event(), 1);
956 /* SPRING-LOADING */
958 /* This is the code that makes directories pop open if you hold a
959 * file over them...
961 * First, call dnd_spring_load(context) to arm the system.
962 * After a timeout (1/2 a second) the dest_path directory will be
963 * opened in a new window, unless dnd_spring_abort is called first.
966 static gint spring_timeout = -1;
967 static GdkDragContext *spring_context = NULL;
968 static FilerWindow *spring_window = NULL;
969 static FilerWindow *spring_src_window = NULL;
971 void dnd_spring_load(GdkDragContext *context, FilerWindow *src_win)
973 g_return_if_fail(context != NULL);
975 if (!o_dnd_spring_open.int_value)
976 return;
978 if (spring_context)
979 dnd_spring_abort();
981 spring_context = context;
982 g_object_ref(spring_context);
983 spring_src_window = src_win;
984 spring_timeout = gtk_timeout_add(
985 o_dnd_spring_delay.int_value, spring_now, NULL);
988 void dnd_spring_abort(void)
990 if (!spring_context)
991 return;
993 g_object_unref(spring_context);
994 spring_context = NULL;
995 gtk_timeout_remove(spring_timeout);
998 /* If all mod keys are released, no buttons are pressed, and the
999 * mouse is outside the spring window, then close it.
1001 static gboolean spring_check_idle(gpointer data)
1003 int p_x, p_y;
1005 if (!spring_window)
1006 return FALSE;
1008 if (!get_pointer_xy(&p_x, &p_y))
1011 GdkWindow *win = spring_window->window->window;
1012 int x, y;
1013 int w, h;
1015 gdk_window_get_position(win, &x, &y);
1016 gdk_window_get_size(win, &w, &h);
1018 if (p_x < x || p_x > x + w || p_y < y || p_y > y + h)
1022 gtk_widget_destroy(spring_window->window);
1023 return FALSE; /* Got it! */
1026 return TRUE; /* Try again later */
1029 static gboolean spring_now(gpointer data)
1031 gboolean old_unique = o_unique_filer_windows.int_value;
1032 guchar *dest_path;
1033 gint x, y;
1035 g_return_val_if_fail(spring_context != NULL, FALSE);
1037 dest_path = g_dataset_get_data(spring_context, "drop_dest_path");
1038 g_return_val_if_fail(dest_path != NULL, FALSE);
1041 * Note: Due to a bug in gtk, if a window disappears during
1042 * a drag and the pointer moves over where the window was,
1043 * the sender crashes! Therefore, do not close any windows
1044 * while dragging! (fixed in later versions)
1047 if (spring_window)
1048 gtk_widget_destroy(spring_window->window);
1051 get_pointer_xy(&x, &y);
1053 o_unique_filer_windows.int_value = FALSE; /* XXX: yuck! */
1054 if (spring_window)
1056 view_cursor_to_iter(spring_window->view, NULL);
1057 filer_change_to(spring_window, dest_path, NULL);
1058 /* DON'T move the window. Gtk+ sometimes doesn't
1059 * notice :-(
1062 else
1064 spring_window = filer_opendir(dest_path, spring_src_window, NULL);
1065 if (spring_window)
1067 gtk_timeout_add(500, spring_check_idle, NULL);
1068 g_signal_connect(spring_window->window, "destroy",
1069 G_CALLBACK(spring_win_destroyed), NULL);
1070 centre_window(spring_window->window->window, x, y);
1073 o_unique_filer_windows.int_value = old_unique;
1075 dnd_spring_abort();
1077 return FALSE;
1080 static void spring_win_destroyed(GtkWidget *widget, gpointer data)
1082 spring_window = NULL;
1085 /* HANDLING MOTION EVENTS */
1087 /* If not-NULL, then this widget has a grab */
1088 static GtkWidget *motion_widget = NULL;
1090 /* If TRUE, we must gdk_pointer_ungrab() too when finishing */
1091 static gboolean motion_pointer_grab = FALSE;
1093 /* Call this on a button press event. It stores the mouse position
1094 * as the start of the new drag and returns TRUE if all is well.
1095 * Further motions events are disabled at this point - you must
1096 * then call dnd_motion_start() to set the type of motion expected.
1097 * Grabs the widget on the first press.
1099 * If the system is not ready to handle a motion event (because a
1100 * button is already held down?) it does nothing and returns FALSE.
1102 * If the event is not a single click then it simply returns TRUE.
1104 gboolean dnd_motion_press(GtkWidget *widget, GdkEventButton *event)
1106 if (event->type != GDK_BUTTON_PRESS)
1107 return TRUE; /* Not a click event! */
1109 motion_buttons_pressed++;
1110 if (motion_buttons_pressed == 1)
1112 /* g_print("[ grab! ]\n"); */
1113 gtk_grab_add(widget);
1114 motion_widget = widget;
1117 if (motion_state != MOTION_NONE)
1118 return FALSE; /* Ignore clicks - we're busy! */
1120 motion_state = MOTION_DISABLED;
1121 drag_start_x = event->x_root;
1122 drag_start_y = event->y_root;
1124 return TRUE;
1127 /* After the button press event, decide what kind of motion is expected.
1128 * If you don't call this then the motion system is disabled - call
1129 * dnd_motion_release() to reset it.
1131 * Note: If you open a popup menu or start DND call dnd_motion_ungrab()
1132 * instead.
1134 void dnd_motion_start(MotionType motion)
1136 g_return_if_fail(motion_state == MOTION_DISABLED);
1138 motion_state = motion;
1141 /* Call this on a button release event. If some buttons are still pressed,
1142 * returns TRUE and does nothing.
1144 * Otherwise, it resets the motion system to be ready again and returns TRUE.
1146 * If the motion system wasn't being used (MOTION_NONE) then it does nothing
1147 * and returns FALSE - process the release event yourself as it isn't part
1148 * of a motion. This also happens if a motion was primed but never happened.
1150 gboolean dnd_motion_release(GdkEventButton *event)
1152 MotionType motion = motion_state;
1153 int dx, dy;
1155 if (motion_buttons_pressed == 0)
1156 return TRUE; /* We were disabled */
1158 if (motion_buttons_pressed == 1)
1159 dnd_motion_ungrab();
1160 else
1162 motion_buttons_pressed--;
1163 return TRUE;
1166 if (motion == MOTION_REPOSITION || motion == MOTION_DISABLED)
1167 return TRUE; /* Already done something - eat the event */
1169 /* Eat release events that happen too far from the click
1170 * source. Otherwise, allow the caller to treat this as a click
1171 * that never became a motion.
1173 dx = event->x_root - drag_start_x;
1174 dy = event->y_root - drag_start_y;
1176 return ABS(dx) > 5 || ABS(dy) > 5;
1179 /* Use this to disable the motion system. The system will be reset once
1180 * all mouse buttons are released.
1182 void dnd_motion_disable(void)
1184 g_return_if_fail(motion_state != MOTION_NONE &&
1185 motion_state != MOTION_DISABLED);
1187 motion_state = MOTION_DISABLED;
1190 /* Use this if something else is going to grab the pointer so that
1191 * we won't get any more motion or release events.
1193 void dnd_motion_ungrab(void)
1195 if (motion_buttons_pressed > 0)
1197 if (motion_pointer_grab)
1199 gdk_pointer_ungrab(GDK_CURRENT_TIME);
1200 motion_pointer_grab = FALSE;
1201 /* g_print("[ ungrab_pointer ]\n"); */
1203 gtk_grab_remove(motion_widget);
1204 motion_widget = NULL;
1205 motion_buttons_pressed = 0;
1206 /* g_print("[ ungrab ]\n"); */
1209 motion_state = MOTION_NONE;
1212 /* Call this on motion events. If the mouse position is far enough
1213 * from the click position, returns TRUE and does dnd_motion_ungrab().
1214 * You should then start regular drag-and-drop.
1216 * Otherwise, returns FALSE.
1218 gboolean dnd_motion_moved(GdkEventMotion *event)
1220 int dx, dy;
1222 dx = event->x_root - drag_start_x;
1223 dy = event->y_root - drag_start_y;
1225 if (ABS(dx) <= 5 && ABS(dy) <= 5)
1226 return FALSE; /* Not far enough */
1228 dnd_motion_ungrab();
1230 return TRUE;
1233 /* Normally, the X server will automatically grab the pointer on a
1234 * button press and ungrab on release. However, if the grab widget
1235 * is reparented then call this to re-aquire the grab.
1237 void dnd_motion_grab_pointer(void)
1239 g_return_if_fail(motion_widget != NULL);
1241 gdk_pointer_grab(motion_widget->window, FALSE,
1242 GDK_POINTER_MOTION_MASK |
1243 GDK_BUTTON_RELEASE_MASK,
1244 FALSE, NULL, GDK_CURRENT_TIME);
1246 motion_pointer_grab = TRUE;