r496: Many internal changes to the Options system.
[rox-filer.git] / ROX-Filer / src / dnd.c
blob3139ef75deb123e21165c63ed11235f173d1bd96
1 /*
2 * $Id$
4 * ROX-Filer, filer for the ROX desktop project
5 * Copyright (C) 2000, Thomas Leonard, <tal197@users.sourceforge.net>.
7 * This program is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License as published by the Free
9 * Software Foundation; either version 2 of the License, or (at your option)
10 * any later version.
12 * This program is distributed in the hope that it will be useful, but WITHOUT
13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
15 * more details.
17 * You should have received a copy of the GNU General Public License along with
18 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
19 * Place, Suite 330, Boston, MA 02111-1307 USA
22 /* dnd.c - code for handling drag and drop */
24 #include "config.h"
26 #include <stdlib.h>
27 #include <stdio.h>
28 #include <fcntl.h>
29 #include <errno.h>
30 #include <string.h>
31 #include <sys/param.h>
33 #include <X11/Xlib.h>
34 #include <X11/Xatom.h>
35 #include <gtk/gtk.h>
36 #include "collection.h"
38 #include "global.h"
40 #include "dnd.h"
41 #include "type.h"
42 #include "filer.h"
43 #include "action.h"
44 #include "pixmaps.h"
45 #include "gui_support.h"
46 #include "support.h"
47 #include "options.h"
48 #include "run.h"
49 #include "pinboard.h"
50 #include "dir.h"
52 #define MAXURILEN 4096 /* Longest URI to allow */
54 gint drag_start_x, drag_start_y;
55 MotionType motion_state = MOTION_NONE;
57 /* This keeps track of how many mouse buttons are currently down.
58 * We add a grab when it does 0->1 and release it on 1<-0.
60 * It may also be set to zero to disable the motion system (eg,
61 * when popping up a menu).
63 gint motion_buttons_pressed = 0;
65 /* Static prototypes */
66 static void set_xds_prop(GdkDragContext *context, char *text);
67 static gboolean drag_motion(GtkWidget *widget,
68 GdkDragContext *context,
69 gint x,
70 gint y,
71 guint time,
72 FilerWindow *filer_window);
73 static void drag_leave(GtkWidget *widget,
74 GdkDragContext *context,
75 guint32 time,
76 FilerWindow *filer_window);
77 static void desktop_drag_data_received(GtkWidget *widget,
78 GdkDragContext *context,
79 gint x,
80 gint y,
81 GtkSelectionData *selection_data,
82 guint info,
83 guint32 time,
84 FilerWindow *filer_window);
85 static void got_data_xds_reply(GtkWidget *widget,
86 GdkDragContext *context,
87 GtkSelectionData *selection_data,
88 guint32 time);
89 static void got_data_raw(GtkWidget *widget,
90 GdkDragContext *context,
91 GtkSelectionData *selection_data,
92 guint32 time);
93 static void got_uri_list(GtkWidget *widget,
94 GdkDragContext *context,
95 GtkSelectionData *selection_data,
96 guint32 time);
97 static void drag_end(GtkWidget *widget,
98 GdkDragContext *context,
99 FilerWindow *filer_window);
100 static gboolean drag_drop(GtkWidget *widget,
101 GdkDragContext *context,
102 gint x,
103 gint y,
104 guint time,
105 gpointer data);
106 static void drag_data_received(GtkWidget *widget,
107 GdkDragContext *context,
108 gint x,
109 gint y,
110 GtkSelectionData *selection_data,
111 guint info,
112 guint32 time,
113 gpointer user_data);
114 static gboolean spring_now(gpointer data);
115 static void spring_win_destroyed(GtkWidget *widget, gpointer data);
117 /* The handler of the signal handler for scroll events.
118 * This is used to cancel spring loading when autoscrolling is used.
120 static gint scrolled_signal = -1;
121 static GtkObject *scrolled_adj = NULL; /* The object watched */
123 /* Possible values for drop_dest_type (can also be NULL).
124 * In either case, drop_dest_path is the app/file/dir to use.
126 char *drop_dest_prog = "drop_dest_prog"; /* Run a program */
127 char *drop_dest_dir = "drop_dest_dir"; /* Save to path */
129 GdkAtom XdndDirectSave0;
130 GdkAtom xa_text_plain;
131 GdkAtom text_uri_list;
132 GdkAtom application_octet_stream;
134 void dnd_init()
136 XdndDirectSave0 = gdk_atom_intern("XdndDirectSave0", FALSE);
137 xa_text_plain = gdk_atom_intern("text/plain", FALSE);
138 text_uri_list = gdk_atom_intern("text/uri-list", FALSE);
139 application_octet_stream = gdk_atom_intern("application/octet-stream",
140 FALSE);
142 option_add_int("dnd_drag_to_icons", 1, NULL);
143 option_add_int("dnd_spring_open", 0, NULL);
144 option_add_int("dnd_spring_delay", 400, NULL);
147 /* SUPPORT FUNCTIONS */
149 /* Set the XdndDirectSave0 property on the source window for this context */
150 static void set_xds_prop(GdkDragContext *context, char *text)
152 gdk_property_change(context->source_window,
153 XdndDirectSave0,
154 xa_text_plain, 8,
155 GDK_PROP_MODE_REPLACE,
156 text,
157 strlen(text));
160 static char *get_xds_prop(GdkDragContext *context)
162 guchar *prop_text;
163 gint length;
165 if (gdk_property_get(context->source_window,
166 XdndDirectSave0,
167 xa_text_plain,
168 0, MAXURILEN,
169 FALSE,
170 NULL, NULL,
171 &length, &prop_text) && prop_text)
173 /* Terminate the string */
174 prop_text = g_realloc(prop_text, length + 1);
175 prop_text[length] = '\0';
176 return prop_text;
179 return NULL;
182 /* Is the sender willing to supply this target type? */
183 gboolean provides(GdkDragContext *context, GdkAtom target)
185 GList *targets = context->targets;
187 while (targets && ((GdkAtom) targets->data != target))
188 targets = targets->next;
190 return targets != NULL;
193 /* Convert a list of URIs into a list of strings.
194 * Lines beginning with # are skipped.
195 * The text block passed in is zero terminated (after the final CRLF)
197 GSList *uri_list_to_gslist(char *uri_list)
199 GSList *list = NULL;
201 while (*uri_list)
203 char *linebreak;
204 char *uri;
205 int length;
207 linebreak = strchr(uri_list, 13);
209 if (!linebreak || linebreak[1] != 10)
211 delayed_error("uri_list_to_gslist",
212 _("Incorrect or missing line "
213 "break in text/uri-list data"));
214 return list;
217 length = linebreak - uri_list;
219 if (length && uri_list[0] != '#')
221 uri = g_malloc(sizeof(char) * (length + 1));
222 strncpy(uri, uri_list, length);
223 uri[length] = 0;
224 list = g_slist_append(list, uri);
227 uri_list = linebreak + 2;
230 return list;
233 /* DRAGGING FROM US */
235 /* The user has held the mouse button down over a group of item and moved -
236 * start a drag. 'uri_list' is copied, so you can delete it straight away.
238 void drag_selection(GtkWidget *widget, GdkEventMotion *event, guchar *uri_list)
240 GdkDragContext *context;
241 GdkDragAction actions;
242 GtkTargetList *target_list;
243 GtkTargetEntry target_table[] = {
244 {"text/uri-list", 0, TARGET_URI_LIST},
247 if (event->state & GDK_BUTTON1_MASK)
248 actions = GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK;
249 else
250 actions = GDK_ACTION_MOVE;
252 target_list = gtk_target_list_new(target_table, 1);
254 context = gtk_drag_begin(widget,
255 target_list,
256 actions,
257 (event->state & GDK_BUTTON1_MASK) ? 1 :
258 (event->state & GDK_BUTTON2_MASK) ? 2 : 3,
259 (GdkEvent *) event);
261 g_dataset_set_data_full(context, "uri_list",
262 g_strdup(uri_list), g_free);
264 gtk_drag_set_icon_pixmap(context,
265 gtk_widget_get_colormap(widget),
266 im_multiple->pixmap,
267 im_multiple->mask,
268 0, 0);
271 /* Copy/Load this item into another directory/application */
272 void drag_one_item(GtkWidget *widget,
273 GdkEventMotion *event,
274 guchar *full_path,
275 DirItem *item)
277 guchar *uri;
278 GdkDragContext *context;
279 GdkDragAction actions;
280 GtkTargetList *target_list;
281 GtkTargetEntry target_table[] = {
282 {"text/uri-list", 0, TARGET_URI_LIST},
283 {"application/octet-stream", 0, TARGET_RAW},
284 {"", 0, TARGET_RAW},
287 g_return_if_fail(full_path != NULL);
288 g_return_if_fail(item != NULL);
290 if (item->base_type == TYPE_FILE)
292 MIME_type *t = item->mime_type;
294 target_table[2].target = g_strconcat(t->media_type, "/",
295 t->subtype, NULL);
296 target_list = gtk_target_list_new(target_table, 3);
297 g_free(target_table[2].target);
299 else
300 target_list = gtk_target_list_new(target_table, 1);
302 if (event->state & GDK_BUTTON1_MASK)
303 actions = GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK;
304 else
305 actions = GDK_ACTION_MOVE;
307 context = gtk_drag_begin(widget,
308 target_list,
309 actions,
310 (event->state & GDK_BUTTON1_MASK) ? 1 :
311 (event->state & GDK_BUTTON2_MASK) ? 2 : 3,
312 (GdkEvent *) event);
314 g_dataset_set_data_full(context, "full_path",
315 g_strdup(full_path), g_free);
316 uri = g_strconcat("file://", our_host_name(), full_path, "\r\n", NULL);
317 g_dataset_set_data_full(context, "uri_list",
318 uri, g_free);
320 gtk_drag_set_icon_pixmap(context,
321 gtk_widget_get_colormap(widget),
322 item->image->pixmap,
323 item->image->mask,
324 0, 0);
327 static void drag_end(GtkWidget *widget,
328 GdkDragContext *context,
329 FilerWindow *filer_window)
331 collection_set_autoscroll(filer_window->collection, FALSE);
332 if (filer_window->temp_item_selected)
334 collection_clear_selection(filer_window->collection);
335 filer_window->temp_item_selected = FALSE;
339 /* Called when a remote app wants us to send it some data.
340 * TODO: Maybe we should handle errors better (ie, let the remote app know
341 * the drag has failed)?
343 void drag_data_get(GtkWidget *widget,
344 GdkDragContext *context,
345 GtkSelectionData *selection_data,
346 guint info,
347 guint32 time,
348 gpointer data)
350 char *to_send = "E"; /* Default to sending an error */
351 long to_send_length = 1;
352 gboolean delete_once_sent = FALSE;
353 GdkAtom type = XA_STRING;
354 guchar *path;
356 switch (info)
358 case TARGET_RAW:
359 path = g_dataset_get_data(context, "full_path");
360 if (path && load_file(path, &to_send, &to_send_length))
362 delete_once_sent = TRUE;
363 type = selection_data->target;
364 break;
366 g_warning("drag_data_get: Can't find path!\n");
367 return;
368 case TARGET_URI_LIST:
369 to_send = g_dataset_get_data(context, "uri_list");
370 to_send_length = strlen(to_send);
371 type = text_uri_list; /* (needed for xine) */
372 delete_once_sent = FALSE;
373 break;
374 default:
375 delayed_error("drag_data_get",
376 _("Internal error - bad info type"));
377 break;
380 gtk_selection_data_set(selection_data,
381 type,
383 to_send,
384 to_send_length);
386 if (delete_once_sent)
387 g_free(to_send);
390 /* DRAGGING TO US */
392 /* Set up this widget as a drop-target.
393 * Does not attach any motion handlers.
395 void make_drop_target(GtkWidget *widget, GtkDestDefaults defaults)
397 GtkTargetEntry target_table[] =
399 {"text/uri-list", 0, TARGET_URI_LIST},
400 {"XdndDirectSave0", 0, TARGET_XDS},
401 {"application/octet-stream", 0, TARGET_RAW},
404 gtk_drag_dest_set(widget,
405 defaults,
406 target_table,
407 sizeof(target_table) / sizeof(*target_table),
408 GDK_ACTION_COPY | GDK_ACTION_MOVE
409 | GDK_ACTION_LINK | GDK_ACTION_PRIVATE);
411 gtk_signal_connect(GTK_OBJECT(widget), "drag_drop",
412 GTK_SIGNAL_FUNC(drag_drop), NULL);
413 gtk_signal_connect(GTK_OBJECT(widget), "drag_data_received",
414 GTK_SIGNAL_FUNC(drag_data_received), NULL);
417 /* Set up this filer window as a drop target. Called once, when the
418 * filer window is first created.
420 void drag_set_dest(FilerWindow *filer_window)
422 GtkWidget *widget = GTK_WIDGET(filer_window->collection);
424 make_drop_target(widget, 0);
426 gtk_signal_connect(GTK_OBJECT(widget), "drag_motion",
427 GTK_SIGNAL_FUNC(drag_motion), filer_window);
428 gtk_signal_connect(GTK_OBJECT(widget), "drag_leave",
429 GTK_SIGNAL_FUNC(drag_leave), filer_window);
430 gtk_signal_connect(GTK_OBJECT(widget), "drag_end",
431 GTK_SIGNAL_FUNC(drag_end), filer_window);
434 /* Like drag_set_dest, but for a pinboard-type widget.
435 * You must ensure that dnd events reach this widget (eg with
436 * setup_xdnd_proxy() for the root window).
438 void drag_set_pinboard_dest(GtkWidget *widget)
440 GtkTargetEntry target_table[] = {
441 {"text/uri-list", 0, TARGET_URI_LIST},
444 gtk_drag_dest_set(widget,
445 GTK_DEST_DEFAULT_DROP,
446 target_table,
447 sizeof(target_table) / sizeof(*target_table),
448 GDK_ACTION_LINK);
449 gtk_signal_connect(GTK_OBJECT(widget), "drag_data_received",
450 (GtkSignalFunc) desktop_drag_data_received,
451 NULL);
454 static void scrolled(GtkAdjustment *adj, Collection *collection)
456 collection_set_cursor_item(collection, -1);
457 dnd_spring_abort();
460 /* Called during the drag when the mouse is in a widget registered
461 * as a drop target. Returns TRUE if we can accept the drop.
463 static gboolean drag_motion(GtkWidget *widget,
464 GdkDragContext *context,
465 gint x,
466 gint y,
467 guint time,
468 FilerWindow *filer_window)
470 DirItem *item;
471 int item_number;
472 GdkDragAction action = context->suggested_action;
473 char *new_path = NULL;
474 char *type = NULL;
475 gboolean retval = FALSE;
477 if (filer_window->collection->auto_scroll == -1)
478 collection_set_autoscroll(filer_window->collection, TRUE);
480 if (option_get_int("dnd_drag_to_icons"))
481 item_number = collection_get_item(filer_window->collection,
482 x, y);
483 else
484 item_number = -1;
486 item = item_number >= 0
487 ? (DirItem *) filer_window->collection->items[item_number].data
488 : NULL;
490 if (item && filer_window->collection->items[item_number].selected)
491 type = NULL;
492 else
493 type = dnd_motion_item(context, &item);
495 if (!type)
496 item = NULL;
498 /* Don't allow drops to non-writeable directories. BUT, still
499 * allow drops on non-writeable SUBdirectories so that we can
500 * do the spring-open thing.
502 if (item && type == drop_dest_dir &&
503 !(item->flags & ITEM_FLAG_APPDIR))
505 GtkObject *vadj = GTK_OBJECT(filer_window->collection->vadj);
507 /* Subdir: prepare for spring-open */
508 if (scrolled_adj != vadj)
510 if (scrolled_adj)
511 gtk_signal_disconnect(scrolled_adj,
512 scrolled_signal);
513 scrolled_adj = vadj;
514 scrolled_signal = gtk_signal_connect(
515 scrolled_adj,
516 "value_changed",
517 GTK_SIGNAL_FUNC(scrolled),
518 filer_window->collection);
520 dnd_spring_load(context);
522 else
523 dnd_spring_abort();
525 if (item)
527 collection_set_cursor_item(filer_window->collection,
528 item_number);
530 else
532 collection_set_cursor_item(filer_window->collection, -1);
534 /* Disallow background drops within a single window */
535 if (type && gtk_drag_get_source_widget(context) == widget)
536 type = NULL;
539 if (type)
541 if (item)
542 new_path = make_path(filer_window->path,
543 item->leafname)->str;
544 else
545 new_path = filer_window->path;
548 g_dataset_set_data(context, "drop_dest_type", type);
549 if (type)
551 gdk_drag_status(context, action, time);
552 g_dataset_set_data_full(context, "drop_dest_path",
553 g_strdup(new_path), g_free);
554 retval = TRUE;
557 return retval;
560 /* item is the item the file is held over, NULL for directory background.
561 * 'item' may be NULL on exit if the drop should be treated as onto the
562 * background. Disallow drags to a selected icon before calling this.
564 * Returns NULL to reject the drop, or drop_dest_prog/drop_dest_dir to
565 * accept. Build the path based on item.
567 guchar *dnd_motion_item(GdkDragContext *context, DirItem **item_p)
569 DirItem *item = *item_p;
571 if (item)
573 /* If we didn't drop onto a directory, application or
574 * executable file then act as though the drop is to the
575 * window background.
577 if (item->base_type != TYPE_DIRECTORY
578 && !(item->mime_type == &special_exec))
580 item = NULL;
581 *item_p = NULL;
585 if (!item)
587 /* Drop onto the window background */
589 return drop_dest_dir;
592 /* Drop onto a program/directory of some sort */
594 if (item->base_type == TYPE_DIRECTORY &&
595 !(item->flags & ITEM_FLAG_APPDIR))
597 /* A normal directory */
598 if (provides(context, text_uri_list) ||
599 provides(context, XdndDirectSave0))
600 return drop_dest_dir;
602 else
604 if (provides(context, text_uri_list) ||
605 provides(context, application_octet_stream))
606 return drop_dest_prog;
609 return NULL;
612 /* Remove highlights */
613 static void drag_leave(GtkWidget *widget,
614 GdkDragContext *context,
615 guint32 time,
616 FilerWindow *filer_window)
618 collection_set_autoscroll(filer_window->collection, FALSE);
619 collection_set_cursor_item(filer_window->collection, -1);
620 dnd_spring_abort();
621 if (scrolled_adj)
623 gtk_signal_disconnect(scrolled_adj,
624 scrolled_signal);
625 scrolled_adj = NULL;
629 /* User has tried to drop some data on us. Decide what format we would
630 * like the data in.
632 static gboolean drag_drop(GtkWidget *widget,
633 GdkDragContext *context,
634 gint x,
635 gint y,
636 guint time,
637 gpointer data)
639 char *error = NULL;
640 char *leafname = NULL;
641 GdkAtom target = GDK_NONE;
642 char *dest_path;
643 char *dest_type = NULL;
645 dest_path = g_dataset_get_data(context, "drop_dest_path");
646 dest_type = g_dataset_get_data(context, "drop_dest_type");
648 g_return_val_if_fail(dest_path != NULL, TRUE);
650 if (dest_type == drop_dest_dir && provides(context, XdndDirectSave0))
652 leafname = get_xds_prop(context);
653 if (leafname)
655 if (strchr(leafname, '/'))
657 error = _("XDS protocol error: "
658 "leafname may not contain '/'\n");
659 g_free(leafname);
661 leafname = NULL;
663 else
665 GString *uri;
667 uri = g_string_new(NULL);
668 g_string_sprintf(uri, "file://%s%s",
669 our_host_name(),
670 make_path(dest_path,
671 leafname)->str);
672 set_xds_prop(context, uri->str);
673 g_string_free(uri, TRUE);
675 target = XdndDirectSave0;
676 g_dataset_set_data_full(context, "leafname",
677 leafname, g_free);
680 else
681 error = _(
682 "XdndDirectSave0 target provided, but the atom "
683 "XdndDirectSave0 (type text/plain) did not "
684 "contain a leafname\n");
686 else if (provides(context, text_uri_list))
687 target = text_uri_list;
688 else if (provides(context, application_octet_stream))
689 target = application_octet_stream;
690 else
692 if (dest_type == drop_dest_dir)
693 error = _("Sorry - I require a target type of "
694 "text/uri-list or XdndDirectSave0.");
695 else
696 error = _("Sorry - I require a target type of "
697 "text/uri-list or application/octet-stream.");
700 if (error)
702 gtk_drag_finish(context, FALSE, FALSE, time); /* Failure */
704 delayed_error(PROJECT, error);
706 else
707 gtk_drag_get_data(widget, context, target, time);
709 return TRUE;
712 /* Called when a text/uri-list arrives */
713 static void desktop_drag_data_received(GtkWidget *widget,
714 GdkDragContext *context,
715 gint x,
716 gint y,
717 GtkSelectionData *selection_data,
718 guint info,
719 guint32 time,
720 FilerWindow *filer_window)
722 GSList *uris, *next;
723 gint dx, dy;
725 if (!selection_data->data)
727 /* Timeout? */
728 return;
731 gdk_window_get_position(widget->window, &dx, &dy);
732 x += dx;
733 y += dy;
735 uris = uri_list_to_gslist(selection_data->data);
737 for (next = uris; next; next = next->next)
739 guchar *path;
741 path = get_local_path((gchar *) next->data);
742 if (path)
744 pinboard_pin(path, NULL, x, y);
745 x += 64;
748 g_free(next->data);
751 if (uris)
752 g_slist_free(uris);
755 /* Called when some data arrives from the remote app (which we asked for
756 * in drag_drop).
758 static void drag_data_received(GtkWidget *widget,
759 GdkDragContext *context,
760 gint x,
761 gint y,
762 GtkSelectionData *selection_data,
763 guint info,
764 guint32 time,
765 gpointer user_data)
767 if (!selection_data->data)
769 /* Timeout? */
770 gtk_drag_finish(context, FALSE, FALSE, time); /* Failure */
771 return;
774 switch (info)
776 case TARGET_XDS:
777 got_data_xds_reply(widget, context,
778 selection_data, time);
779 break;
780 case TARGET_RAW:
781 got_data_raw(widget, context, selection_data, time);
782 break;
783 case TARGET_URI_LIST:
784 got_uri_list(widget, context, selection_data, time);
785 break;
786 default:
787 gtk_drag_finish(context, FALSE, FALSE, time);
788 delayed_error("drag_data_received",
789 _("Unknown target"));
790 break;
794 static void got_data_xds_reply(GtkWidget *widget,
795 GdkDragContext *context,
796 GtkSelectionData *selection_data,
797 guint32 time)
799 gboolean mark_unsafe = TRUE;
800 char response = *selection_data->data;
801 char *error = NULL;
802 char *dest_path;
804 dest_path = g_dataset_get_data(context, "drop_dest_path");
806 if (selection_data->length != 1)
807 response = '?';
809 if (response == 'F')
811 /* Sender couldn't save there - ask for another
812 * type if possible.
814 if (provides(context, application_octet_stream))
816 mark_unsafe = FALSE; /* Wait and see */
818 gtk_drag_get_data(widget, context,
819 application_octet_stream, time);
821 else
822 error = _("Remote app can't or won't send me "
823 "the data - sorry");
825 else if (response == 'S')
827 /* Success - data is saved */
828 mark_unsafe = FALSE; /* It really is safe */
829 gtk_drag_finish(context, TRUE, FALSE, time);
831 refresh_dirs(dest_path);
833 else if (response != 'E')
835 error = _("XDS protocol error: "
836 "return code should be 'S', 'F' or 'E'\n");
838 /* else: error has been reported by the sender */
840 if (mark_unsafe)
842 set_xds_prop(context, "");
843 /* Unsave also implies that the drag failed */
844 gtk_drag_finish(context, FALSE, FALSE, time);
847 if (error)
848 delayed_error(PROJECT, error);
851 static void got_data_raw(GtkWidget *widget,
852 GdkDragContext *context,
853 GtkSelectionData *selection_data,
854 guint32 time)
856 char *leafname;
857 int fd;
858 char *error = NULL;
859 char *dest_path;
861 g_return_if_fail(selection_data->data != NULL);
863 dest_path = g_dataset_get_data(context, "drop_dest_path");
865 if (g_dataset_get_data(context, "drop_dest_type") == drop_dest_prog)
867 /* The data needs to be sent to an application */
868 run_with_data(dest_path,
869 selection_data->data, selection_data->length);
870 gtk_drag_finish(context, TRUE, FALSE, time); /* Success! */
871 return;
874 leafname = g_dataset_get_data(context, "leafname");
875 if (!leafname)
876 leafname = _("UntitledData");
878 fd = open(make_path(dest_path, leafname)->str,
879 O_WRONLY | O_CREAT | O_EXCL | O_NOCTTY,
880 S_IRUSR | S_IRGRP | S_IROTH |
881 S_IWUSR | S_IWGRP | S_IWOTH);
883 if (fd == -1)
884 error = g_strerror(errno);
885 else
887 if (write(fd,
888 selection_data->data,
889 selection_data->length) == -1)
890 error = g_strerror(errno);
892 if (close(fd) == -1 && !error)
893 error = g_strerror(errno);
895 refresh_dirs(dest_path);
898 if (error)
900 if (provides(context, XdndDirectSave0))
901 set_xds_prop(context, "");
902 gtk_drag_finish(context, FALSE, FALSE, time); /* Failure */
903 delayed_error(_("Error saving file"), error);
905 else
906 gtk_drag_finish(context, TRUE, FALSE, time); /* Success! */
909 /* We've got a list of URIs from somewhere (probably another filer window).
910 * If the files are on the local machine then try to copy them ourselves,
911 * otherwise, if there was only one file and application/octet-stream was
912 * provided, get the data via the X server.
914 static void got_uri_list(GtkWidget *widget,
915 GdkDragContext *context,
916 GtkSelectionData *selection_data,
917 guint32 time)
919 GSList *uri_list;
920 char *error = NULL;
921 GSList *next_uri;
922 gboolean send_reply = TRUE;
923 char *dest_path;
924 char *type;
926 dest_path = g_dataset_get_data(context, "drop_dest_path");
927 type = g_dataset_get_data(context, "drop_dest_type");
929 g_return_if_fail(dest_path != NULL);
931 uri_list = uri_list_to_gslist(selection_data->data);
933 if (!uri_list)
934 error = _("No URIs in the text/uri-list (nothing to do!)");
935 else if (type == drop_dest_prog)
936 run_with_files(dest_path, uri_list);
937 else if ((!uri_list->next) && (!get_local_path(uri_list->data)))
939 /* There is one URI in the list, and it's not on the local
940 * machine. Get it via the X server if possible.
943 if (provides(context, application_octet_stream))
945 char *leaf;
946 leaf = strrchr(uri_list->data, '/');
947 if (leaf)
948 leaf++;
949 else
950 leaf = uri_list->data;
951 g_dataset_set_data_full(context, "leafname",
952 g_strdup(leaf), g_free);
953 gtk_drag_get_data(widget, context,
954 application_octet_stream, time);
955 send_reply = FALSE;
957 else
958 error = _("Can't get data from remote machine "
959 "(application/octet-stream not provided)");
961 else
963 GSList *local_paths = NULL;
964 GSList *next;
966 /* Either one local URI, or a list. If everything in the list
967 * isn't local then we are stuck.
970 for (next_uri = uri_list; next_uri; next_uri = next_uri->next)
972 char *path;
974 path = get_local_path((char *) next_uri->data);
976 if (path)
977 local_paths = g_slist_append(local_paths,
978 g_strdup(path));
979 else
980 error = _("Some of these files are on a "
981 "different machine - they will be "
982 "ignored - sorry");
985 if (!local_paths)
987 error = _("None of these files are on the local "
988 "machine - I can't operate on multiple "
989 "remote files - sorry.");
991 else if (context->action == GDK_ACTION_MOVE)
992 action_move(local_paths, dest_path, NULL);
993 else if (context->action == GDK_ACTION_COPY)
994 action_copy(local_paths, dest_path, NULL);
995 else if (context->action == GDK_ACTION_LINK)
996 action_link(local_paths, dest_path);
997 else
998 error = _("Unknown action requested");
1000 for (next = local_paths; next; next = next->next)
1001 g_free(next->data);
1002 g_slist_free(local_paths);
1005 if (error)
1007 gtk_drag_finish(context, FALSE, FALSE, time); /* Failure */
1008 delayed_error(_("Error getting file list"), error);
1010 else if (send_reply)
1011 gtk_drag_finish(context, TRUE, FALSE, time); /* Success! */
1013 next_uri = uri_list;
1014 while (next_uri)
1016 g_free(next_uri->data);
1017 next_uri = next_uri->next;
1019 g_slist_free(uri_list);
1023 /* SPRING-LOADING */
1025 /* This is the code that makes directories pop open if you hold a
1026 * file over them...
1028 * First, call dnd_spring_load(context) to arm the system.
1029 * After a timeout (1/2 a second) the dest_path directory will be
1030 * opened in a new window, unless dnd_spring_abort is called first.
1033 static gint spring_timeout = -1;
1034 static GdkDragContext *spring_context = NULL;
1035 static FilerWindow *spring_window = NULL;
1037 void dnd_spring_load(GdkDragContext *context)
1039 g_return_if_fail(context != NULL);
1041 if (!option_get_int("dnd_spring_open"))
1042 return;
1044 if (spring_context)
1045 dnd_spring_abort();
1047 spring_context = context;
1048 gdk_drag_context_ref(spring_context);
1049 spring_timeout = gtk_timeout_add(
1050 option_get_int("dnd_spring_delay"), spring_now, NULL);
1053 void dnd_spring_abort(void)
1055 if (!spring_context)
1056 return;
1058 gdk_drag_context_unref(spring_context);
1059 spring_context = NULL;
1060 gtk_timeout_remove(spring_timeout);
1063 /* If all mod keys are released, no buttons are pressed, and the
1064 * mouse is outside the spring window, then close it.
1066 static gboolean spring_check_idle(gpointer data)
1068 int p_x, p_y;
1070 if (!spring_window)
1071 return FALSE;
1073 if (!get_pointer_xy(&p_x, &p_y))
1076 GdkWindow *win = spring_window->window->window;
1077 int x, y;
1078 int w, h;
1080 gdk_window_get_position(win, &x, &y);
1081 gdk_window_get_size(win, &w, &h);
1083 if (p_x < x || p_x > x + w || p_y < y || p_y > y + h)
1087 gtk_widget_destroy(spring_window->window);
1088 return FALSE; /* Got it! */
1091 return TRUE; /* Try again later */
1094 static gboolean spring_now(gpointer data)
1096 gboolean old_unique = o_unique_filer_windows;
1097 guchar *dest_path;
1098 gint x, y;
1100 g_return_val_if_fail(spring_context != NULL, FALSE);
1102 dest_path = g_dataset_get_data(spring_context, "drop_dest_path");
1103 g_return_val_if_fail(dest_path != NULL, FALSE);
1106 * XXX: Due to a bug in gtk, if a window disappears during
1107 * a drag and the pointer moves over where the window was,
1108 * the sender crashes! Therefore, do not close any windows
1109 * while dragging! (fixed in later versions)
1112 if (spring_window)
1113 gtk_widget_destroy(spring_window->window);
1116 get_pointer_xy(&x, &y);
1118 o_unique_filer_windows = FALSE;
1119 if (spring_window)
1121 collection_set_cursor_item(spring_window->collection, -1);
1122 filer_change_to(spring_window, dest_path, NULL);
1123 /* DON'T move the window. Gtk+ sometimes doesn't
1124 * notice :-(
1127 else
1129 spring_window = filer_opendir(dest_path);
1130 gtk_timeout_add(500, spring_check_idle, NULL);
1131 gtk_signal_connect(GTK_OBJECT(spring_window->window), "destroy",
1132 GTK_SIGNAL_FUNC(spring_win_destroyed), NULL);
1133 if (spring_window)
1134 centre_window(spring_window->window->window, x, y);
1136 o_unique_filer_windows = old_unique;
1138 dnd_spring_abort();
1140 return FALSE;
1143 static void spring_win_destroyed(GtkWidget *widget, gpointer data)
1145 spring_window = NULL;
1148 /* HANDLING MOTION EVENTS */
1150 /* If not-NULL, then this widget has a grab */
1151 static GtkWidget *motion_widget = NULL;
1153 /* If TRUE, we must gdk_pointer_ungrab() too when finishing */
1154 static gboolean motion_pointer_grab = FALSE;
1156 /* Call this on a button press event. It stores the mouse position
1157 * as the start of the new drag and returns TRUE if all is well.
1158 * Further motions events are disabled at this point - you must
1159 * then call dnd_motion_start() to set the type of motion expected.
1160 * Grabs the widget on the first press.
1162 * If the system is not ready to handle a motion event (because a
1163 * button is already held down?) it does nothing and returns FALSE.
1165 * If the event is not a single click then it simply returns TRUE.
1167 gboolean dnd_motion_press(GtkWidget *widget, GdkEventButton *event)
1169 if (event->type != GDK_BUTTON_PRESS)
1170 return TRUE; /* Not a click event! */
1172 motion_buttons_pressed++;
1173 if (motion_buttons_pressed == 1)
1175 /* g_print("[ grab! ]\n"); */
1176 gtk_grab_add(widget);
1177 motion_widget = widget;
1180 if (motion_state != MOTION_NONE)
1181 return FALSE; /* Ignore clicks - we're busy! */
1183 motion_state = MOTION_DISABLED;
1184 drag_start_x = event->x_root;
1185 drag_start_y = event->y_root;
1187 return TRUE;
1190 /* After the button press event, decide what kind of motion is expected.
1191 * If you don't call this then the motion system is disabled - call
1192 * dnd_motion_release() to reset it.
1194 * Note: If you open a popup menu or start DND call dnd_motion_ungrab()
1195 * instead.
1197 void dnd_motion_start(MotionType motion)
1199 g_return_if_fail(motion_state == MOTION_DISABLED);
1201 motion_state = motion;
1204 /* Call this on a button release event. If some buttons are still pressed,
1205 * returns TRUE and does nothing.
1207 * Otherwise, it resets the motion system to be ready again and returns TRUE.
1209 * If the motion system wasn't being used (MOTION_NONE) then it does nothing
1210 * and returns FALSE - process the release event yourself as it isn't part
1211 * of a motion. This also happens if a motion was primed but never happened.
1213 gboolean dnd_motion_release(GdkEventButton *event)
1215 MotionType motion = motion_state;
1216 int dx, dy;
1218 if (motion_buttons_pressed == 0)
1219 return TRUE; /* We were disabled */
1221 if (motion_buttons_pressed == 1)
1222 dnd_motion_ungrab();
1223 else
1225 motion_buttons_pressed--;
1226 return TRUE;
1229 if (motion == MOTION_REPOSITION || motion == MOTION_DISABLED)
1230 return TRUE; /* Already done something - eat the event */
1232 /* Eat release events that happen too far from the click
1233 * source. Otherwise, allow the caller to treat this as a click
1234 * that never became a motion.
1236 dx = event->x_root - drag_start_x;
1237 dy = event->y_root - drag_start_y;
1239 return ABS(dx) > 5 || ABS(dy) > 5;
1242 /* Use this to disable the motion system. The system will be reset once
1243 * all mouse buttons are released.
1245 void dnd_motion_disable(void)
1247 g_return_if_fail(motion_state != MOTION_NONE &&
1248 motion_state != MOTION_DISABLED);
1250 motion_state = MOTION_DISABLED;
1253 /* Use this if something else is going to grab the pointer so that
1254 * we won't get any more motion or release events.
1256 void dnd_motion_ungrab(void)
1258 if (motion_buttons_pressed > 0)
1260 if (motion_pointer_grab)
1262 gdk_pointer_ungrab(GDK_CURRENT_TIME);
1263 motion_pointer_grab = FALSE;
1264 /* g_print("[ ungrab_pointer ]\n"); */
1266 gtk_grab_remove(motion_widget);
1267 motion_widget = NULL;
1268 motion_buttons_pressed = 0;
1269 /* g_print("[ ungrab ]\n"); */
1272 motion_state = MOTION_NONE;
1275 /* Call this on motion events. If the mouse position is far enough
1276 * from the click position, returns TRUE and does dnd_motion_ungrab().
1277 * You should then start regular drag-and-drop.
1279 * Otherwise, returns FALSE.
1281 gboolean dnd_motion_moved(GdkEventMotion *event)
1283 int dx, dy;
1285 dx = event->x_root - drag_start_x;
1286 dy = event->y_root - drag_start_y;
1288 if (ABS(dx) <= 5 && ABS(dy) <= 5)
1289 return FALSE; /* Not far enough */
1291 dnd_motion_ungrab();
1293 return TRUE;
1296 /* Normally, the X server will automatically grab the pointer on a
1297 * button press and ungrab on release. However, if the grab widget
1298 * is reparented then call this to re-aquire the grab.
1300 void dnd_motion_grab_pointer(void)
1302 g_return_if_fail(motion_widget != NULL);
1304 gdk_pointer_grab(motion_widget->window, FALSE,
1305 GDK_POINTER_MOTION_MASK |
1306 GDK_BUTTON_RELEASE_MASK,
1307 FALSE, NULL, GDK_CURRENT_TIME);
1309 motion_pointer_grab = TRUE;