r1553: Created View interface and started moving collection specific stuff
[rox-filer.git] / ROX-Filer / src / dnd.c
blobad1d0d0bb96a18210bdb7324f5af36aa7c21748d
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 gulong 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 static Option o_dnd_drag_to_icons;
162 Option o_dnd_spring_open;
163 static Option o_dnd_spring_delay;
164 static Option o_dnd_middle_menu;
166 void dnd_init()
168 XdndDirectSave0 = gdk_atom_intern("XdndDirectSave0", FALSE);
169 xa_text_plain = gdk_atom_intern("text/plain", FALSE);
170 text_uri_list = gdk_atom_intern("text/uri-list", FALSE);
171 application_octet_stream = gdk_atom_intern("application/octet-stream",
172 FALSE);
173 xa_string = gdk_atom_intern("STRING", FALSE);
175 option_add_int(&o_dnd_drag_to_icons, "dnd_drag_to_icons", 1);
176 option_add_int(&o_dnd_spring_open, "dnd_spring_open", 0);
177 option_add_int(&o_dnd_spring_delay, "dnd_spring_delay", 400);
178 option_add_int(&o_dnd_middle_menu, "dnd_middle_menu", TRUE);
181 /* SUPPORT FUNCTIONS */
183 /* Set the XdndDirectSave0 property on the source window for this context */
184 static void set_xds_prop(GdkDragContext *context, char *text)
186 gdk_property_change(context->source_window,
187 XdndDirectSave0,
188 xa_text_plain, 8,
189 GDK_PROP_MODE_REPLACE,
190 text,
191 strlen(text));
194 static char *get_xds_prop(GdkDragContext *context)
196 guchar *prop_text;
197 gint length;
199 if (gdk_property_get(context->source_window,
200 XdndDirectSave0,
201 xa_text_plain,
202 0, MAXURILEN,
203 FALSE,
204 NULL, NULL,
205 &length, &prop_text) && prop_text)
207 /* Terminate the string */
208 prop_text = g_realloc(prop_text, length + 1);
209 prop_text[length] = '\0';
210 return prop_text;
213 return NULL;
216 /* Is the sender willing to supply this target type? */
217 gboolean provides(GdkDragContext *context, GdkAtom target)
219 GList *targets = context->targets;
221 while (targets && ((GdkAtom) targets->data != target))
222 targets = targets->next;
224 return targets != NULL;
227 /* Convert a list of URIs into a list of strings.
228 * Lines beginning with # are skipped.
229 * The text block passed in is zero terminated (after the final CRLF)
231 GList *uri_list_to_glist(const char *uri_list)
233 GList *list = NULL;
235 while (*uri_list)
237 char *linebreak;
238 char *uri;
239 int length;
241 linebreak = strchr(uri_list, 13);
243 if (!linebreak || linebreak[1] != 10)
245 delayed_error("uri_list_to_glist: %s",
246 _("Incorrect or missing line "
247 "break in text/uri-list data"));
248 return list;
251 length = linebreak - uri_list;
253 if (length && uri_list[0] != '#')
255 uri = g_strndup(uri_list, length);
256 list = g_list_append(list, uri);
259 uri_list = linebreak + 2;
262 return list;
265 /* DRAGGING FROM US */
267 /* The user has held the mouse button down over a group of item and moved -
268 * start a drag. 'uri_list' is copied, so you can delete it straight away.
270 void drag_selection(GtkWidget *widget, GdkEventMotion *event, guchar *uri_list)
272 GdkDragContext *context;
273 GdkDragAction actions;
274 GtkTargetList *target_list;
275 GtkTargetEntry target_table[] = {
276 {"text/uri-list", 0, TARGET_URI_LIST},
279 if (event->state & GDK_BUTTON1_MASK)
280 actions = GDK_ACTION_COPY | GDK_ACTION_MOVE
281 | GDK_ACTION_LINK | GDK_ACTION_ASK;
282 else
284 if (o_dnd_middle_menu.int_value)
285 actions = GDK_ACTION_ASK;
286 else
287 actions = GDK_ACTION_MOVE;
290 target_list = gtk_target_list_new(target_table, 1);
292 context = gtk_drag_begin(widget,
293 target_list,
294 actions,
295 (event->state & GDK_BUTTON1_MASK) ? 1 :
296 (event->state & GDK_BUTTON2_MASK) ? 2 : 3,
297 (GdkEvent *) event);
299 g_dataset_set_data_full(context, "uri_list",
300 g_strdup(uri_list), g_free);
302 gtk_drag_set_icon_pixbuf(context, im_multiple->pixbuf, 0, 0);
305 /* Copy/Load this item into another directory/application */
306 void drag_one_item(GtkWidget *widget,
307 GdkEventMotion *event,
308 const guchar *full_path,
309 DirItem *item,
310 MaskedPixmap *image)
312 guchar *uri;
313 GdkDragContext *context;
314 GdkDragAction actions;
315 GtkTargetList *target_list;
316 GtkTargetEntry target_table[] = {
317 {"text/uri-list", 0, TARGET_URI_LIST},
318 {"application/octet-stream", 0, TARGET_RAW},
319 {"", 0, TARGET_RAW},
322 g_return_if_fail(full_path != NULL);
323 g_return_if_fail(item != NULL);
325 if (!image)
326 image = item->image;
328 if (item->base_type == TYPE_FILE)
330 MIME_type *t = item->mime_type;
332 target_table[2].target = g_strconcat(t->media_type, "/",
333 t->subtype, NULL);
334 target_list = gtk_target_list_new(target_table, 3);
335 g_free(target_table[2].target);
337 else
338 target_list = gtk_target_list_new(target_table, 1);
340 if (event->state & GDK_BUTTON1_MASK)
341 actions = GDK_ACTION_COPY | GDK_ACTION_ASK
342 | GDK_ACTION_MOVE | GDK_ACTION_LINK;
343 else
345 if (o_dnd_middle_menu.int_value)
346 actions = GDK_ACTION_ASK;
347 else
348 actions = GDK_ACTION_MOVE;
351 context = gtk_drag_begin(widget,
352 target_list,
353 actions,
354 (event->state & GDK_BUTTON1_MASK) ? 1 :
355 (event->state & GDK_BUTTON2_MASK) ? 2 : 3,
356 (GdkEvent *) event);
358 g_dataset_set_data_full(context, "full_path",
359 g_strdup(full_path), g_free);
360 uri = g_strconcat("file://", our_host_name_for_dnd(),
361 full_path, "\r\n", NULL);
362 g_dataset_set_data_full(context, "uri_list", uri, g_free);
364 g_return_if_fail(image != NULL);
366 gtk_drag_set_icon_pixbuf(context, image->pixbuf, 0, 0);
369 static void drag_end(GtkWidget *widget,
370 GdkDragContext *context,
371 FilerWindow *filer_window)
373 collection_set_autoscroll(filer_window->collection, FALSE);
374 if (filer_window->temp_item_selected)
376 collection_clear_selection(filer_window->collection);
377 filer_window->temp_item_selected = FALSE;
381 /* Called when a remote app wants us to send it some data.
382 * TODO: Maybe we should handle errors better (ie, let the remote app know
383 * the drag has failed)?
385 void drag_data_get(GtkWidget *widget,
386 GdkDragContext *context,
387 GtkSelectionData *selection_data,
388 guint info,
389 guint32 time,
390 gpointer data)
392 char *to_send = "E"; /* Default to sending an error */
393 long to_send_length = 1;
394 gboolean delete_once_sent = FALSE;
395 GdkAtom type;
396 guchar *path;
398 type = gdk_x11_xatom_to_atom(XA_STRING);
400 switch (info)
402 case TARGET_RAW:
403 path = g_dataset_get_data(context, "full_path");
404 if (path && load_file(path, &to_send, &to_send_length))
406 delete_once_sent = TRUE;
407 type = selection_data->target;
408 break;
410 g_warning("drag_data_get: Can't find path!\n");
411 return;
412 case TARGET_URI_LIST:
413 to_send = g_dataset_get_data(context, "uri_list");
414 to_send_length = strlen(to_send);
415 type = text_uri_list; /* (needed for xine) */
416 delete_once_sent = FALSE;
417 break;
418 default:
419 delayed_error("drag_data_get: %s",
420 _("Internal error - bad info type"));
421 break;
424 gtk_selection_data_set(selection_data,
425 type,
427 to_send,
428 to_send_length);
430 if (delete_once_sent)
431 g_free(to_send);
434 /* DRAGGING TO US */
436 /* Set up this widget as a drop-target.
437 * Does not attach any motion handlers.
439 void make_drop_target(GtkWidget *widget, GtkDestDefaults defaults)
441 GtkTargetEntry target_table[] =
443 {"text/uri-list", 0, TARGET_URI_LIST},
444 {"XdndDirectSave0", 0, TARGET_XDS},
445 {"application/octet-stream", 0, TARGET_RAW},
448 gtk_drag_dest_set(widget,
449 defaults,
450 target_table,
451 sizeof(target_table) / sizeof(*target_table),
452 GDK_ACTION_COPY | GDK_ACTION_ASK | GDK_ACTION_MOVE
453 | GDK_ACTION_LINK | GDK_ACTION_PRIVATE);
455 g_signal_connect(widget, "drag_drop", G_CALLBACK(drag_drop), NULL);
456 g_signal_connect(widget, "drag_data_received",
457 G_CALLBACK(drag_data_received), NULL);
460 /* Set up this filer window as a drop target. Called once, when the
461 * filer window is first created.
463 void drag_set_dest(FilerWindow *filer_window)
465 GtkWidget *widget = GTK_WIDGET(filer_window->collection);
467 make_drop_target(widget, 0);
469 g_signal_connect(widget, "drag_motion",
470 G_CALLBACK(drag_motion), filer_window);
471 g_signal_connect(widget, "drag_leave",
472 G_CALLBACK(drag_leave), filer_window);
473 g_signal_connect(widget, "drag_end",
474 G_CALLBACK(drag_end), filer_window);
477 /* Like drag_set_dest, but for a pinboard-type widget */
478 void drag_set_pinboard_dest(GtkWidget *widget)
480 GtkTargetEntry target_table[] = {
481 {"text/uri-list", 0, TARGET_URI_LIST},
484 gtk_drag_dest_set(widget,
485 GTK_DEST_DEFAULT_DROP,
486 target_table,
487 sizeof(target_table) / sizeof(*target_table),
488 GDK_ACTION_LINK);
489 g_signal_connect(widget, "drag_data_received",
490 G_CALLBACK(desktop_drag_data_received), NULL);
493 /* Called during the drag when the mouse is in a widget registered
494 * as a drop target. Returns TRUE if we can accept the drop.
496 static gboolean drag_motion(GtkWidget *widget,
497 GdkDragContext *context,
498 gint x,
499 gint y,
500 guint time,
501 FilerWindow *filer_window)
503 DirItem *item;
504 int item_number;
505 GdkDragAction action = context->suggested_action;
506 char *new_path = NULL;
507 char *type = NULL;
508 gboolean retval = FALSE;
510 if (filer_window->collection->auto_scroll == -1)
511 collection_set_autoscroll(filer_window->collection, TRUE);
513 if (o_dnd_drag_to_icons.int_value)
514 item_number = collection_get_item(filer_window->collection,
515 x, y);
516 else
517 item_number = -1;
519 item = item_number >= 0
520 ? (DirItem *) filer_window->collection->items[item_number].data
521 : NULL;
523 if (item && filer_window->collection->items[item_number].selected)
524 type = NULL;
525 else
526 type = dnd_motion_item(context, &item);
528 if (!type)
529 item = NULL;
531 /* Don't allow drops to non-writeable directories. BUT, still
532 * allow drops on non-writeable SUBdirectories so that we can
533 * do the spring-open thing.
535 if (item && type == drop_dest_dir &&
536 !(item->flags & ITEM_FLAG_APPDIR))
538 #if 0
539 /* XXX: This is needed so that directories don't
540 * spring open while we scroll. Should go in
541 * collection.c, I think.
543 GtkObject *vadj = GTK_OBJECT(filer_window->collection->vadj);
545 /* Subdir: prepare for spring-open */
546 if (scrolled_adj != vadj)
548 if (scrolled_adj)
549 gtk_signal_disconnect(scrolled_adj,
550 scrolled_signal);
551 scrolled_adj = vadj;
552 scrolled_signal = gtk_signal_connect(
553 scrolled_adj,
554 "value_changed",
555 GTK_SIGNAL_FUNC(scrolled),
556 filer_window->collection);
558 #endif
559 dnd_spring_load(context, filer_window);
561 else
562 dnd_spring_abort();
564 if (item)
566 collection_set_cursor_item(filer_window->collection,
567 item_number);
569 else
571 collection_set_cursor_item(filer_window->collection, -1);
573 /* Disallow background drops within a single window */
574 if (type && gtk_drag_get_source_widget(context) == widget)
575 type = NULL;
578 if (type)
580 if (item)
581 new_path = make_path(filer_window->sym_path,
582 item->leafname)->str;
583 else
584 new_path = filer_window->sym_path;
587 g_dataset_set_data(context, "drop_dest_type", type);
588 if (type)
590 gdk_drag_status(context, action, time);
591 g_dataset_set_data_full(context, "drop_dest_path",
592 g_strdup(new_path), g_free);
593 retval = TRUE;
596 return retval;
599 /* item is the item the file is held over, NULL for directory background.
600 * 'item' may be NULL on exit if the drop should be treated as onto the
601 * background. Disallow drags to a selected icon before calling this.
603 * Returns NULL to reject the drop, or drop_dest_prog/drop_dest_dir to
604 * accept. Build the path based on item.
606 guchar *dnd_motion_item(GdkDragContext *context, DirItem **item_p)
608 DirItem *item = *item_p;
610 if (item)
612 /* If we didn't drop onto a directory, application or
613 * executable file then act as though the drop is to the
614 * window background.
616 if (item->base_type != TYPE_DIRECTORY
617 && !(item->mime_type == application_executable))
619 item = NULL;
620 *item_p = NULL;
624 if (!item)
626 /* Drop onto the window background */
628 return drop_dest_dir;
631 /* Drop onto a program/directory of some sort */
633 if (item->base_type == TYPE_DIRECTORY &&
634 !(item->flags & ITEM_FLAG_APPDIR))
636 /* A normal directory */
637 if (provides(context, text_uri_list) ||
638 provides(context, XdndDirectSave0))
639 return drop_dest_dir;
641 else
643 if (provides(context, text_uri_list) ||
644 provides(context, application_octet_stream))
645 return drop_dest_prog;
648 return NULL;
651 /* Remove highlights */
652 static void drag_leave(GtkWidget *widget,
653 GdkDragContext *context,
654 guint32 time,
655 FilerWindow *filer_window)
657 collection_set_autoscroll(filer_window->collection, FALSE);
658 collection_set_cursor_item(filer_window->collection, -1);
659 dnd_spring_abort();
660 if (scrolled_adj)
662 g_signal_handler_disconnect(scrolled_adj, scrolled_signal);
663 scrolled_adj = NULL;
667 /* User has tried to drop some data on us. Decide what format we would
668 * like the data in.
670 static gboolean drag_drop(GtkWidget *widget,
671 GdkDragContext *context,
672 gint x,
673 gint y,
674 guint time,
675 gpointer data)
677 const char *error = NULL;
678 char *leafname = NULL;
679 GdkAtom target = GDK_NONE;
680 char *dest_path;
681 char *dest_type = NULL;
683 dest_path = g_dataset_get_data(context, "drop_dest_path");
684 dest_type = g_dataset_get_data(context, "drop_dest_type");
686 g_return_val_if_fail(dest_path != NULL, TRUE);
688 if (dest_type == drop_dest_dir && provides(context, XdndDirectSave0))
690 leafname = get_xds_prop(context);
691 if (leafname)
693 if (strchr(leafname, '/'))
695 error = _("XDS protocol error: "
696 "leafname may not contain '/'\n");
697 g_free(leafname);
699 leafname = NULL;
701 else
703 GString *uri;
705 uri = g_string_new(NULL);
706 g_string_sprintf(uri, "file://%s%s",
707 our_host_name_for_dnd(),
708 make_path(dest_path,
709 leafname)->str);
710 set_xds_prop(context, uri->str);
711 g_string_free(uri, TRUE);
713 target = XdndDirectSave0;
714 g_dataset_set_data_full(context, "leafname",
715 leafname, g_free);
718 else
719 error = _(
720 "XdndDirectSave0 target provided, but the atom "
721 "XdndDirectSave0 (type text/plain) did not "
722 "contain a leafname\n");
724 else if (provides(context, text_uri_list))
725 target = text_uri_list;
726 else if (provides(context, application_octet_stream))
727 target = application_octet_stream;
728 else
730 if (dest_type == drop_dest_dir)
731 error = _("Sorry - I require a target type of "
732 "text/uri-list or XdndDirectSave0.");
733 else
734 error = _("Sorry - I require a target type of "
735 "text/uri-list or application/octet-stream.");
738 if (error)
740 gtk_drag_finish(context, FALSE, FALSE, time); /* Failure */
742 delayed_error("%s", error);
744 else
745 gtk_drag_get_data(widget, context, target, time);
747 return TRUE;
750 /* Called when a text/uri-list arrives */
751 static void desktop_drag_data_received(GtkWidget *widget,
752 GdkDragContext *context,
753 gint x,
754 gint y,
755 GtkSelectionData *selection_data,
756 guint info,
757 guint32 time,
758 FilerWindow *filer_window)
760 GList *uris, *next;
761 gint dx, dy;
763 if (!selection_data->data)
765 /* Timeout? */
766 return;
769 if (pinboard_drag_in_progress)
771 pinboard_move_icons();
772 return;
775 gdk_window_get_position(widget->window, &dx, &dy);
776 x += dx;
777 y += dy;
779 uris = uri_list_to_glist(selection_data->data);
781 for (next = uris; next; next = next->next)
783 const guchar *path;
785 path = get_local_path((gchar *) next->data);
786 if (path)
788 pinboard_pin(path, NULL, x, y);
789 x += 64;
792 g_free(next->data);
795 if (uris)
796 g_list_free(uris);
799 /* Called when some data arrives from the remote app (which we asked for
800 * in drag_drop).
802 static void drag_data_received(GtkWidget *widget,
803 GdkDragContext *context,
804 gint x,
805 gint y,
806 GtkSelectionData *selection_data,
807 guint info,
808 guint32 time,
809 gpointer user_data)
811 if (!selection_data->data)
813 /* Timeout? */
814 gtk_drag_finish(context, FALSE, FALSE, time); /* Failure */
815 return;
818 switch (info)
820 case TARGET_XDS:
821 got_data_xds_reply(widget, context,
822 selection_data, time);
823 break;
824 case TARGET_RAW:
825 got_data_raw(widget, context, selection_data, time);
826 break;
827 case TARGET_URI_LIST:
828 got_uri_list(widget, context, selection_data, time);
829 break;
830 default:
831 gtk_drag_finish(context, FALSE, FALSE, time);
832 delayed_error("drag_data_received: %s",
833 _("Unknown target"));
834 break;
838 static void got_data_xds_reply(GtkWidget *widget,
839 GdkDragContext *context,
840 GtkSelectionData *selection_data,
841 guint32 time)
843 gboolean mark_unsafe = TRUE;
844 char response = *selection_data->data;
845 const char *error = NULL;
846 char *dest_path;
848 dest_path = g_dataset_get_data(context, "drop_dest_path");
850 if (selection_data->length != 1)
851 response = '?';
853 if (response == 'F')
855 /* Sender couldn't save there - ask for another
856 * type if possible.
858 if (provides(context, application_octet_stream))
860 mark_unsafe = FALSE; /* Wait and see */
862 gtk_drag_get_data(widget, context,
863 application_octet_stream, time);
865 else
866 error = _("Remote app can't or won't send me "
867 "the data - sorry");
869 else if (response == 'S')
871 /* Success - data is saved */
872 mark_unsafe = FALSE; /* It really is safe */
873 gtk_drag_finish(context, TRUE, FALSE, time);
875 refresh_dirs(dest_path);
877 else if (response != 'E')
879 error = _("XDS protocol error: "
880 "return code should be 'S', 'F' or 'E'\n");
882 /* else: error has been reported by the sender */
884 if (mark_unsafe)
886 set_xds_prop(context, "");
887 /* Unsave also implies that the drag failed */
888 gtk_drag_finish(context, FALSE, FALSE, time);
891 if (error)
892 delayed_error("%s", error);
895 static void got_data_raw(GtkWidget *widget,
896 GdkDragContext *context,
897 GtkSelectionData *selection_data,
898 guint32 time)
900 const char *leafname;
901 int fd;
902 const char *error = NULL;
903 const char *dest_path;
905 g_return_if_fail(selection_data->data != NULL);
907 dest_path = g_dataset_get_data(context, "drop_dest_path");
909 if (context->action == GDK_ACTION_ASK)
911 gtk_drag_finish(context, FALSE, FALSE, time); /* Failure */
912 delayed_error(_("Sorry, can't display a menu of actions "
913 "for a remote file / raw data."));
914 return;
917 if (g_dataset_get_data(context, "drop_dest_type") == drop_dest_prog)
919 /* The data needs to be sent to an application */
920 run_with_data(dest_path,
921 selection_data->data, selection_data->length);
922 gtk_drag_finish(context, TRUE, FALSE, time); /* Success! */
923 return;
926 leafname = g_dataset_get_data(context, "leafname");
927 if (!leafname)
928 leafname = _("UntitledData");
930 fd = open(make_path(dest_path, leafname)->str,
931 O_WRONLY | O_CREAT | O_EXCL | O_NOCTTY,
932 S_IRUSR | S_IRGRP | S_IROTH |
933 S_IWUSR | S_IWGRP | S_IWOTH);
935 if (fd == -1)
936 error = g_strerror(errno);
937 else
939 if (write(fd,
940 selection_data->data,
941 selection_data->length) == -1)
942 error = g_strerror(errno);
944 if (close(fd) == -1 && !error)
945 error = g_strerror(errno);
947 refresh_dirs(dest_path);
950 if (error)
952 if (provides(context, XdndDirectSave0))
953 set_xds_prop(context, "");
954 gtk_drag_finish(context, FALSE, FALSE, time); /* Failure */
955 delayed_error(_("Error saving file: %s"), error);
957 else
958 gtk_drag_finish(context, TRUE, FALSE, time); /* Success! */
961 /* We've got a list of URIs from somewhere (probably another filer window).
962 * If the files are on the local machine then try to copy them ourselves,
963 * otherwise, if there was only one file and application/octet-stream was
964 * provided, get the data via the X server.
966 static void got_uri_list(GtkWidget *widget,
967 GdkDragContext *context,
968 GtkSelectionData *selection_data,
969 guint32 time)
971 GList *uri_list;
972 const char *error = NULL;
973 GList *next_uri;
974 gboolean send_reply = TRUE;
975 char *dest_path;
976 char *type;
978 dest_path = g_dataset_get_data(context, "drop_dest_path");
979 type = g_dataset_get_data(context, "drop_dest_type");
981 g_return_if_fail(dest_path != NULL);
983 uri_list = uri_list_to_glist(selection_data->data);
985 if (!uri_list)
986 error = _("No URIs in the text/uri-list (nothing to do!)");
987 else if (context->action != GDK_ACTION_ASK && type == drop_dest_prog)
988 run_with_files(dest_path, uri_list);
989 else if ((!uri_list->next) && (!get_local_path(uri_list->data)))
991 /* There is one URI in the list, and it's not on the local
992 * machine. Get it via the X server if possible.
995 if (provides(context, application_octet_stream))
997 char *leaf;
998 leaf = strrchr(uri_list->data, '/');
999 if (leaf)
1000 leaf++;
1001 else
1002 leaf = uri_list->data;
1003 g_dataset_set_data_full(context, "leafname",
1004 g_strdup(leaf), g_free);
1005 gtk_drag_get_data(widget, context,
1006 application_octet_stream, time);
1007 send_reply = FALSE;
1009 else
1010 error = _("Can't get data from remote machine "
1011 "(application/octet-stream not provided)");
1013 else
1015 GList *local_paths = NULL;
1016 GList *next;
1018 /* Either one local URI, or a list. If everything in the list
1019 * isn't local then we are stuck.
1022 for (next_uri = uri_list; next_uri; next_uri = next_uri->next)
1024 const char *path;
1026 path = get_local_path((char *) next_uri->data);
1028 if (path)
1029 local_paths = g_list_append(local_paths,
1030 g_strdup(path));
1031 else
1032 error = _("Some of these files are on a "
1033 "different machine - they will be "
1034 "ignored - sorry");
1037 if (!local_paths)
1039 error = _("None of these files are on the local "
1040 "machine - I can't operate on multiple "
1041 "remote files - sorry.");
1043 else if (context->action == GDK_ACTION_ASK)
1044 prompt_action(local_paths, dest_path);
1045 else if (context->action == GDK_ACTION_MOVE)
1046 action_move(local_paths, dest_path, NULL, -1);
1047 else if (context->action == GDK_ACTION_COPY)
1048 action_copy(local_paths, dest_path, NULL, -1);
1049 else if (context->action == GDK_ACTION_LINK)
1050 action_link(local_paths, dest_path, NULL);
1051 else
1052 error = _("Unknown action requested");
1054 for (next = local_paths; next; next = next->next)
1055 g_free(next->data);
1056 g_list_free(local_paths);
1059 if (error)
1061 gtk_drag_finish(context, FALSE, FALSE, time); /* Failure */
1062 delayed_error(_("Error getting file list: %s"), error);
1064 else if (send_reply)
1065 gtk_drag_finish(context, TRUE, FALSE, time); /* Success! */
1067 next_uri = uri_list;
1068 while (next_uri)
1070 g_free(next_uri->data);
1071 next_uri = next_uri->next;
1073 g_list_free(uri_list);
1076 /* Called when an item from the ACTION_ASK menu is chosen */
1077 static void menuitem_response(gpointer data, guint action, GtkWidget *widget)
1079 if (action == MENU_MOVE)
1080 action_move(prompt_local_paths, prompt_dest_path, NULL, -1);
1081 else if (action == MENU_COPY)
1082 action_copy(prompt_local_paths, prompt_dest_path, NULL, -1);
1083 else if (action == MENU_LINK)
1084 action_link(prompt_local_paths, prompt_dest_path, NULL);
1085 else if (action == MENU_SET_ICON)
1087 if (g_list_length(prompt_local_paths) == 1)
1088 set_icon_path(prompt_dest_path,
1089 (char*) prompt_local_paths->data);
1090 else
1091 delayed_error(
1092 _("You can't use multiple files with Set Icon!"));
1096 /* When some local files are dropped somewhere with ACTION_ASK, this
1097 * function is called to display the menu.
1099 static void prompt_action(GList *paths, gchar *dest)
1101 GList *next;
1103 if (prompt_local_paths)
1105 g_list_foreach(prompt_local_paths, (GFunc) g_free, NULL);
1106 g_list_free(prompt_local_paths);
1107 g_free(prompt_dest_path);
1109 prompt_dest_path = NULL;
1110 prompt_local_paths = NULL;
1113 /* Make a copy of the arguments */
1114 for (next = paths; next; next = next->next)
1115 prompt_local_paths = g_list_append(prompt_local_paths,
1116 g_strdup((gchar *) next->data));
1117 prompt_dest_path = g_strdup(dest);
1119 if (!dnd_menu)
1121 GtkItemFactory *item_factory;
1123 item_factory = menu_create(menu_def,
1124 sizeof(menu_def) / sizeof(*menu_def),
1125 "<dnd>", NULL);
1126 dnd_menu = gtk_item_factory_get_widget(item_factory, "<dnd>");
1129 /* Shade 'Set Icon' if there are multiple files */
1130 menu_set_items_shaded(dnd_menu, g_list_length(paths) != 1, 4, 1);
1132 show_popup_menu(dnd_menu, gtk_get_current_event(), 1);
1136 /* SPRING-LOADING */
1138 /* This is the code that makes directories pop open if you hold a
1139 * file over them...
1141 * First, call dnd_spring_load(context) to arm the system.
1142 * After a timeout (1/2 a second) the dest_path directory will be
1143 * opened in a new window, unless dnd_spring_abort is called first.
1146 static gint spring_timeout = -1;
1147 static GdkDragContext *spring_context = NULL;
1148 static FilerWindow *spring_window = NULL;
1149 static FilerWindow *spring_src_window = NULL;
1151 void dnd_spring_load(GdkDragContext *context, FilerWindow *src_win)
1153 g_return_if_fail(context != NULL);
1155 if (!o_dnd_spring_open.int_value)
1156 return;
1158 if (spring_context)
1159 dnd_spring_abort();
1161 spring_context = context;
1162 g_object_ref(spring_context);
1163 spring_src_window = src_win;
1164 spring_timeout = gtk_timeout_add(
1165 o_dnd_spring_delay.int_value, spring_now, NULL);
1168 void dnd_spring_abort(void)
1170 if (!spring_context)
1171 return;
1173 g_object_unref(spring_context);
1174 spring_context = NULL;
1175 gtk_timeout_remove(spring_timeout);
1178 /* If all mod keys are released, no buttons are pressed, and the
1179 * mouse is outside the spring window, then close it.
1181 static gboolean spring_check_idle(gpointer data)
1183 int p_x, p_y;
1185 if (!spring_window)
1186 return FALSE;
1188 if (!get_pointer_xy(&p_x, &p_y))
1191 GdkWindow *win = spring_window->window->window;
1192 int x, y;
1193 int w, h;
1195 gdk_window_get_position(win, &x, &y);
1196 gdk_window_get_size(win, &w, &h);
1198 if (p_x < x || p_x > x + w || p_y < y || p_y > y + h)
1202 gtk_widget_destroy(spring_window->window);
1203 return FALSE; /* Got it! */
1206 return TRUE; /* Try again later */
1209 static gboolean spring_now(gpointer data)
1211 gboolean old_unique = o_unique_filer_windows.int_value;
1212 guchar *dest_path;
1213 gint x, y;
1215 g_return_val_if_fail(spring_context != NULL, FALSE);
1217 dest_path = g_dataset_get_data(spring_context, "drop_dest_path");
1218 g_return_val_if_fail(dest_path != NULL, FALSE);
1221 * Note: Due to a bug in gtk, if a window disappears during
1222 * a drag and the pointer moves over where the window was,
1223 * the sender crashes! Therefore, do not close any windows
1224 * while dragging! (fixed in later versions)
1227 if (spring_window)
1228 gtk_widget_destroy(spring_window->window);
1231 get_pointer_xy(&x, &y);
1233 o_unique_filer_windows.int_value = FALSE; /* XXX: yuck! */
1234 if (spring_window)
1236 collection_set_cursor_item(spring_window->collection, -1);
1237 filer_change_to(spring_window, dest_path, NULL);
1238 /* DON'T move the window. Gtk+ sometimes doesn't
1239 * notice :-(
1242 else
1244 spring_window = filer_opendir(dest_path, spring_src_window);
1245 if (spring_window)
1247 gtk_timeout_add(500, spring_check_idle, NULL);
1248 g_signal_connect(spring_window->window, "destroy",
1249 G_CALLBACK(spring_win_destroyed), NULL);
1250 centre_window(spring_window->window->window, x, y);
1253 o_unique_filer_windows.int_value = old_unique;
1255 dnd_spring_abort();
1257 return FALSE;
1260 static void spring_win_destroyed(GtkWidget *widget, gpointer data)
1262 spring_window = NULL;
1265 /* HANDLING MOTION EVENTS */
1267 /* If not-NULL, then this widget has a grab */
1268 static GtkWidget *motion_widget = NULL;
1270 /* If TRUE, we must gdk_pointer_ungrab() too when finishing */
1271 static gboolean motion_pointer_grab = FALSE;
1273 /* Call this on a button press event. It stores the mouse position
1274 * as the start of the new drag and returns TRUE if all is well.
1275 * Further motions events are disabled at this point - you must
1276 * then call dnd_motion_start() to set the type of motion expected.
1277 * Grabs the widget on the first press.
1279 * If the system is not ready to handle a motion event (because a
1280 * button is already held down?) it does nothing and returns FALSE.
1282 * If the event is not a single click then it simply returns TRUE.
1284 gboolean dnd_motion_press(GtkWidget *widget, GdkEventButton *event)
1286 if (event->type != GDK_BUTTON_PRESS)
1287 return TRUE; /* Not a click event! */
1289 motion_buttons_pressed++;
1290 if (motion_buttons_pressed == 1)
1292 /* g_print("[ grab! ]\n"); */
1293 gtk_grab_add(widget);
1294 motion_widget = widget;
1297 if (motion_state != MOTION_NONE)
1298 return FALSE; /* Ignore clicks - we're busy! */
1300 motion_state = MOTION_DISABLED;
1301 drag_start_x = event->x_root;
1302 drag_start_y = event->y_root;
1304 return TRUE;
1307 /* After the button press event, decide what kind of motion is expected.
1308 * If you don't call this then the motion system is disabled - call
1309 * dnd_motion_release() to reset it.
1311 * Note: If you open a popup menu or start DND call dnd_motion_ungrab()
1312 * instead.
1314 void dnd_motion_start(MotionType motion)
1316 g_return_if_fail(motion_state == MOTION_DISABLED);
1318 motion_state = motion;
1321 /* Call this on a button release event. If some buttons are still pressed,
1322 * returns TRUE and does nothing.
1324 * Otherwise, it resets the motion system to be ready again and returns TRUE.
1326 * If the motion system wasn't being used (MOTION_NONE) then it does nothing
1327 * and returns FALSE - process the release event yourself as it isn't part
1328 * of a motion. This also happens if a motion was primed but never happened.
1330 gboolean dnd_motion_release(GdkEventButton *event)
1332 MotionType motion = motion_state;
1333 int dx, dy;
1335 if (motion_buttons_pressed == 0)
1336 return TRUE; /* We were disabled */
1338 if (motion_buttons_pressed == 1)
1339 dnd_motion_ungrab();
1340 else
1342 motion_buttons_pressed--;
1343 return TRUE;
1346 if (motion == MOTION_REPOSITION || motion == MOTION_DISABLED)
1347 return TRUE; /* Already done something - eat the event */
1349 /* Eat release events that happen too far from the click
1350 * source. Otherwise, allow the caller to treat this as a click
1351 * that never became a motion.
1353 dx = event->x_root - drag_start_x;
1354 dy = event->y_root - drag_start_y;
1356 return ABS(dx) > 5 || ABS(dy) > 5;
1359 /* Use this to disable the motion system. The system will be reset once
1360 * all mouse buttons are released.
1362 void dnd_motion_disable(void)
1364 g_return_if_fail(motion_state != MOTION_NONE &&
1365 motion_state != MOTION_DISABLED);
1367 motion_state = MOTION_DISABLED;
1370 /* Use this if something else is going to grab the pointer so that
1371 * we won't get any more motion or release events.
1373 void dnd_motion_ungrab(void)
1375 if (motion_buttons_pressed > 0)
1377 if (motion_pointer_grab)
1379 gdk_pointer_ungrab(GDK_CURRENT_TIME);
1380 motion_pointer_grab = FALSE;
1381 /* g_print("[ ungrab_pointer ]\n"); */
1383 gtk_grab_remove(motion_widget);
1384 motion_widget = NULL;
1385 motion_buttons_pressed = 0;
1386 /* g_print("[ ungrab ]\n"); */
1389 motion_state = MOTION_NONE;
1392 /* Call this on motion events. If the mouse position is far enough
1393 * from the click position, returns TRUE and does dnd_motion_ungrab().
1394 * You should then start regular drag-and-drop.
1396 * Otherwise, returns FALSE.
1398 gboolean dnd_motion_moved(GdkEventMotion *event)
1400 int dx, dy;
1402 dx = event->x_root - drag_start_x;
1403 dy = event->y_root - drag_start_y;
1405 if (ABS(dx) <= 5 && ABS(dy) <= 5)
1406 return FALSE; /* Not far enough */
1408 dnd_motion_ungrab();
1410 return TRUE;
1413 /* Normally, the X server will automatically grab the pointer on a
1414 * button press and ungrab on release. However, if the grab widget
1415 * is reparented then call this to re-aquire the grab.
1417 void dnd_motion_grab_pointer(void)
1419 g_return_if_fail(motion_widget != NULL);
1421 gdk_pointer_grab(motion_widget->window, FALSE,
1422 GDK_POINTER_MOTION_MASK |
1423 GDK_BUTTON_RELEASE_MASK,
1424 FALSE, NULL, GDK_CURRENT_TIME);
1426 motion_pointer_grab = TRUE;