r1447: Middle button clicks on the pinboard are passed to the window manager.
[rox-filer.git] / ROX-Filer / src / dnd.c
blob5bdd374b8354f2d0c02b3b87cb910acf949fa63f
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: Do we still need this under 2.0? */
540 GtkObject *vadj = GTK_OBJECT(filer_window->collection->vadj);
542 /* Subdir: prepare for spring-open */
543 if (scrolled_adj != vadj)
545 if (scrolled_adj)
546 gtk_signal_disconnect(scrolled_adj,
547 scrolled_signal);
548 scrolled_adj = vadj;
549 scrolled_signal = gtk_signal_connect(
550 scrolled_adj,
551 "value_changed",
552 GTK_SIGNAL_FUNC(scrolled),
553 filer_window->collection);
555 #endif
556 dnd_spring_load(context, filer_window);
558 else
559 dnd_spring_abort();
561 if (item)
563 collection_set_cursor_item(filer_window->collection,
564 item_number);
566 else
568 collection_set_cursor_item(filer_window->collection, -1);
570 /* Disallow background drops within a single window */
571 if (type && gtk_drag_get_source_widget(context) == widget)
572 type = NULL;
575 if (type)
577 if (item)
578 new_path = make_path(filer_window->sym_path,
579 item->leafname)->str;
580 else
581 new_path = filer_window->sym_path;
584 g_dataset_set_data(context, "drop_dest_type", type);
585 if (type)
587 gdk_drag_status(context, action, time);
588 g_dataset_set_data_full(context, "drop_dest_path",
589 g_strdup(new_path), g_free);
590 retval = TRUE;
593 return retval;
596 /* item is the item the file is held over, NULL for directory background.
597 * 'item' may be NULL on exit if the drop should be treated as onto the
598 * background. Disallow drags to a selected icon before calling this.
600 * Returns NULL to reject the drop, or drop_dest_prog/drop_dest_dir to
601 * accept. Build the path based on item.
603 guchar *dnd_motion_item(GdkDragContext *context, DirItem **item_p)
605 DirItem *item = *item_p;
607 if (item)
609 /* If we didn't drop onto a directory, application or
610 * executable file then act as though the drop is to the
611 * window background.
613 if (item->base_type != TYPE_DIRECTORY
614 && !(item->mime_type == special_exec))
616 item = NULL;
617 *item_p = NULL;
621 if (!item)
623 /* Drop onto the window background */
625 return drop_dest_dir;
628 /* Drop onto a program/directory of some sort */
630 if (item->base_type == TYPE_DIRECTORY &&
631 !(item->flags & ITEM_FLAG_APPDIR))
633 /* A normal directory */
634 if (provides(context, text_uri_list) ||
635 provides(context, XdndDirectSave0))
636 return drop_dest_dir;
638 else
640 if (provides(context, text_uri_list) ||
641 provides(context, application_octet_stream))
642 return drop_dest_prog;
645 return NULL;
648 /* Remove highlights */
649 static void drag_leave(GtkWidget *widget,
650 GdkDragContext *context,
651 guint32 time,
652 FilerWindow *filer_window)
654 collection_set_autoscroll(filer_window->collection, FALSE);
655 collection_set_cursor_item(filer_window->collection, -1);
656 dnd_spring_abort();
657 if (scrolled_adj)
659 g_signal_handler_disconnect(scrolled_adj, scrolled_signal);
660 scrolled_adj = NULL;
664 /* User has tried to drop some data on us. Decide what format we would
665 * like the data in.
667 static gboolean drag_drop(GtkWidget *widget,
668 GdkDragContext *context,
669 gint x,
670 gint y,
671 guint time,
672 gpointer data)
674 const char *error = NULL;
675 char *leafname = NULL;
676 GdkAtom target = GDK_NONE;
677 char *dest_path;
678 char *dest_type = NULL;
680 dest_path = g_dataset_get_data(context, "drop_dest_path");
681 dest_type = g_dataset_get_data(context, "drop_dest_type");
683 g_return_val_if_fail(dest_path != NULL, TRUE);
685 if (dest_type == drop_dest_dir && provides(context, XdndDirectSave0))
687 leafname = get_xds_prop(context);
688 if (leafname)
690 if (strchr(leafname, '/'))
692 error = _("XDS protocol error: "
693 "leafname may not contain '/'\n");
694 g_free(leafname);
696 leafname = NULL;
698 else
700 GString *uri;
702 uri = g_string_new(NULL);
703 g_string_sprintf(uri, "file://%s%s",
704 our_host_name_for_dnd(),
705 make_path(dest_path,
706 leafname)->str);
707 set_xds_prop(context, uri->str);
708 g_string_free(uri, TRUE);
710 target = XdndDirectSave0;
711 g_dataset_set_data_full(context, "leafname",
712 leafname, g_free);
715 else
716 error = _(
717 "XdndDirectSave0 target provided, but the atom "
718 "XdndDirectSave0 (type text/plain) did not "
719 "contain a leafname\n");
721 else if (provides(context, text_uri_list))
722 target = text_uri_list;
723 else if (provides(context, application_octet_stream))
724 target = application_octet_stream;
725 else
727 if (dest_type == drop_dest_dir)
728 error = _("Sorry - I require a target type of "
729 "text/uri-list or XdndDirectSave0.");
730 else
731 error = _("Sorry - I require a target type of "
732 "text/uri-list or application/octet-stream.");
735 if (error)
737 gtk_drag_finish(context, FALSE, FALSE, time); /* Failure */
739 delayed_error("%s", error);
741 else
742 gtk_drag_get_data(widget, context, target, time);
744 return TRUE;
747 /* Called when a text/uri-list arrives */
748 static void desktop_drag_data_received(GtkWidget *widget,
749 GdkDragContext *context,
750 gint x,
751 gint y,
752 GtkSelectionData *selection_data,
753 guint info,
754 guint32 time,
755 FilerWindow *filer_window)
757 GList *uris, *next;
758 gint dx, dy;
760 if (!selection_data->data)
762 /* Timeout? */
763 return;
766 if (pinboard_drag_in_progress)
768 pinboard_move_icons();
769 return;
772 gdk_window_get_position(widget->window, &dx, &dy);
773 x += dx;
774 y += dy;
776 uris = uri_list_to_glist(selection_data->data);
778 for (next = uris; next; next = next->next)
780 const guchar *path;
782 path = get_local_path((gchar *) next->data);
783 if (path)
785 pinboard_pin(path, NULL, x, y);
786 x += 64;
789 g_free(next->data);
792 if (uris)
793 g_list_free(uris);
796 /* Called when some data arrives from the remote app (which we asked for
797 * in drag_drop).
799 static void drag_data_received(GtkWidget *widget,
800 GdkDragContext *context,
801 gint x,
802 gint y,
803 GtkSelectionData *selection_data,
804 guint info,
805 guint32 time,
806 gpointer user_data)
808 if (!selection_data->data)
810 /* Timeout? */
811 gtk_drag_finish(context, FALSE, FALSE, time); /* Failure */
812 return;
815 switch (info)
817 case TARGET_XDS:
818 got_data_xds_reply(widget, context,
819 selection_data, time);
820 break;
821 case TARGET_RAW:
822 got_data_raw(widget, context, selection_data, time);
823 break;
824 case TARGET_URI_LIST:
825 got_uri_list(widget, context, selection_data, time);
826 break;
827 default:
828 gtk_drag_finish(context, FALSE, FALSE, time);
829 delayed_error("drag_data_received: %s",
830 _("Unknown target"));
831 break;
835 static void got_data_xds_reply(GtkWidget *widget,
836 GdkDragContext *context,
837 GtkSelectionData *selection_data,
838 guint32 time)
840 gboolean mark_unsafe = TRUE;
841 char response = *selection_data->data;
842 const char *error = NULL;
843 char *dest_path;
845 dest_path = g_dataset_get_data(context, "drop_dest_path");
847 if (selection_data->length != 1)
848 response = '?';
850 if (response == 'F')
852 /* Sender couldn't save there - ask for another
853 * type if possible.
855 if (provides(context, application_octet_stream))
857 mark_unsafe = FALSE; /* Wait and see */
859 gtk_drag_get_data(widget, context,
860 application_octet_stream, time);
862 else
863 error = _("Remote app can't or won't send me "
864 "the data - sorry");
866 else if (response == 'S')
868 /* Success - data is saved */
869 mark_unsafe = FALSE; /* It really is safe */
870 gtk_drag_finish(context, TRUE, FALSE, time);
872 refresh_dirs(dest_path);
874 else if (response != 'E')
876 error = _("XDS protocol error: "
877 "return code should be 'S', 'F' or 'E'\n");
879 /* else: error has been reported by the sender */
881 if (mark_unsafe)
883 set_xds_prop(context, "");
884 /* Unsave also implies that the drag failed */
885 gtk_drag_finish(context, FALSE, FALSE, time);
888 if (error)
889 delayed_error("%s", error);
892 static void got_data_raw(GtkWidget *widget,
893 GdkDragContext *context,
894 GtkSelectionData *selection_data,
895 guint32 time)
897 const char *leafname;
898 int fd;
899 const char *error = NULL;
900 const char *dest_path;
902 g_return_if_fail(selection_data->data != NULL);
904 dest_path = g_dataset_get_data(context, "drop_dest_path");
906 if (context->action == GDK_ACTION_ASK)
908 gtk_drag_finish(context, FALSE, FALSE, time); /* Failure */
909 delayed_error(_("Sorry, can't display a menu of actions "
910 "for a remote file / raw data."));
911 return;
914 if (g_dataset_get_data(context, "drop_dest_type") == drop_dest_prog)
916 /* The data needs to be sent to an application */
917 run_with_data(dest_path,
918 selection_data->data, selection_data->length);
919 gtk_drag_finish(context, TRUE, FALSE, time); /* Success! */
920 return;
923 leafname = g_dataset_get_data(context, "leafname");
924 if (!leafname)
925 leafname = _("UntitledData");
927 fd = open(make_path(dest_path, leafname)->str,
928 O_WRONLY | O_CREAT | O_EXCL | O_NOCTTY,
929 S_IRUSR | S_IRGRP | S_IROTH |
930 S_IWUSR | S_IWGRP | S_IWOTH);
932 if (fd == -1)
933 error = g_strerror(errno);
934 else
936 if (write(fd,
937 selection_data->data,
938 selection_data->length) == -1)
939 error = g_strerror(errno);
941 if (close(fd) == -1 && !error)
942 error = g_strerror(errno);
944 refresh_dirs(dest_path);
947 if (error)
949 if (provides(context, XdndDirectSave0))
950 set_xds_prop(context, "");
951 gtk_drag_finish(context, FALSE, FALSE, time); /* Failure */
952 delayed_error(_("Error saving file: %s"), error);
954 else
955 gtk_drag_finish(context, TRUE, FALSE, time); /* Success! */
958 /* We've got a list of URIs from somewhere (probably another filer window).
959 * If the files are on the local machine then try to copy them ourselves,
960 * otherwise, if there was only one file and application/octet-stream was
961 * provided, get the data via the X server.
963 static void got_uri_list(GtkWidget *widget,
964 GdkDragContext *context,
965 GtkSelectionData *selection_data,
966 guint32 time)
968 GList *uri_list;
969 const char *error = NULL;
970 GList *next_uri;
971 gboolean send_reply = TRUE;
972 char *dest_path;
973 char *type;
975 dest_path = g_dataset_get_data(context, "drop_dest_path");
976 type = g_dataset_get_data(context, "drop_dest_type");
978 g_return_if_fail(dest_path != NULL);
980 uri_list = uri_list_to_glist(selection_data->data);
982 if (!uri_list)
983 error = _("No URIs in the text/uri-list (nothing to do!)");
984 else if (context->action != GDK_ACTION_ASK && type == drop_dest_prog)
985 run_with_files(dest_path, uri_list);
986 else if ((!uri_list->next) && (!get_local_path(uri_list->data)))
988 /* There is one URI in the list, and it's not on the local
989 * machine. Get it via the X server if possible.
992 if (provides(context, application_octet_stream))
994 char *leaf;
995 leaf = strrchr(uri_list->data, '/');
996 if (leaf)
997 leaf++;
998 else
999 leaf = uri_list->data;
1000 g_dataset_set_data_full(context, "leafname",
1001 g_strdup(leaf), g_free);
1002 gtk_drag_get_data(widget, context,
1003 application_octet_stream, time);
1004 send_reply = FALSE;
1006 else
1007 error = _("Can't get data from remote machine "
1008 "(application/octet-stream not provided)");
1010 else
1012 GList *local_paths = NULL;
1013 GList *next;
1015 /* Either one local URI, or a list. If everything in the list
1016 * isn't local then we are stuck.
1019 for (next_uri = uri_list; next_uri; next_uri = next_uri->next)
1021 const char *path;
1023 path = get_local_path((char *) next_uri->data);
1025 if (path)
1026 local_paths = g_list_append(local_paths,
1027 g_strdup(path));
1028 else
1029 error = _("Some of these files are on a "
1030 "different machine - they will be "
1031 "ignored - sorry");
1034 if (!local_paths)
1036 error = _("None of these files are on the local "
1037 "machine - I can't operate on multiple "
1038 "remote files - sorry.");
1040 else if (context->action == GDK_ACTION_ASK)
1041 prompt_action(local_paths, dest_path);
1042 else if (context->action == GDK_ACTION_MOVE)
1043 action_move(local_paths, dest_path, NULL, -1);
1044 else if (context->action == GDK_ACTION_COPY)
1045 action_copy(local_paths, dest_path, NULL, -1);
1046 else if (context->action == GDK_ACTION_LINK)
1047 action_link(local_paths, dest_path, NULL);
1048 else
1049 error = _("Unknown action requested");
1051 for (next = local_paths; next; next = next->next)
1052 g_free(next->data);
1053 g_list_free(local_paths);
1056 if (error)
1058 gtk_drag_finish(context, FALSE, FALSE, time); /* Failure */
1059 delayed_error(_("Error getting file list: %s"), error);
1061 else if (send_reply)
1062 gtk_drag_finish(context, TRUE, FALSE, time); /* Success! */
1064 next_uri = uri_list;
1065 while (next_uri)
1067 g_free(next_uri->data);
1068 next_uri = next_uri->next;
1070 g_list_free(uri_list);
1073 /* Called when an item from the ACTION_ASK menu is chosen */
1074 static void menuitem_response(gpointer data, guint action, GtkWidget *widget)
1076 if (action == MENU_MOVE)
1077 action_move(prompt_local_paths, prompt_dest_path, NULL, -1);
1078 else if (action == MENU_COPY)
1079 action_copy(prompt_local_paths, prompt_dest_path, NULL, -1);
1080 else if (action == MENU_LINK)
1081 action_link(prompt_local_paths, prompt_dest_path, NULL);
1082 else if (action == MENU_SET_ICON)
1084 if (g_list_length(prompt_local_paths) == 1)
1085 set_icon_path(prompt_dest_path,
1086 (char*) prompt_local_paths->data);
1087 else
1088 delayed_error(
1089 _("You can't use multiple files with Set Icon!"));
1093 /* When some local files are dropped somewhere with ACTION_ASK, this
1094 * function is called to display the menu.
1096 static void prompt_action(GList *paths, gchar *dest)
1098 GList *next;
1100 if (prompt_local_paths)
1102 g_list_foreach(prompt_local_paths, (GFunc) g_free, NULL);
1103 g_list_free(prompt_local_paths);
1104 g_free(prompt_dest_path);
1106 prompt_dest_path = NULL;
1107 prompt_local_paths = NULL;
1110 /* Make a copy of the arguments */
1111 for (next = paths; next; next = next->next)
1112 prompt_local_paths = g_list_append(prompt_local_paths,
1113 g_strdup((gchar *) next->data));
1114 prompt_dest_path = g_strdup(dest);
1116 if (!dnd_menu)
1118 GtkItemFactory *item_factory;
1120 item_factory = menu_create(menu_def,
1121 sizeof(menu_def) / sizeof(*menu_def),
1122 "<dnd>", NULL);
1123 dnd_menu = gtk_item_factory_get_widget(item_factory, "<dnd>");
1126 /* Shade 'Set Icon' if there are multiple files */
1127 menu_set_items_shaded(dnd_menu, g_list_length(paths) != 1, 4, 1);
1129 show_popup_menu(dnd_menu, gtk_get_current_event(), 1);
1133 /* SPRING-LOADING */
1135 /* This is the code that makes directories pop open if you hold a
1136 * file over them...
1138 * First, call dnd_spring_load(context) to arm the system.
1139 * After a timeout (1/2 a second) the dest_path directory will be
1140 * opened in a new window, unless dnd_spring_abort is called first.
1143 static gint spring_timeout = -1;
1144 static GdkDragContext *spring_context = NULL;
1145 static FilerWindow *spring_window = NULL;
1146 static FilerWindow *spring_src_window = NULL;
1148 void dnd_spring_load(GdkDragContext *context, FilerWindow *src_win)
1150 g_return_if_fail(context != NULL);
1152 if (!o_dnd_spring_open.int_value)
1153 return;
1155 if (spring_context)
1156 dnd_spring_abort();
1158 spring_context = context;
1159 g_object_ref(spring_context);
1160 spring_src_window = src_win;
1161 spring_timeout = gtk_timeout_add(
1162 o_dnd_spring_delay.int_value, spring_now, NULL);
1165 void dnd_spring_abort(void)
1167 if (!spring_context)
1168 return;
1170 g_object_unref(spring_context);
1171 spring_context = NULL;
1172 gtk_timeout_remove(spring_timeout);
1175 /* If all mod keys are released, no buttons are pressed, and the
1176 * mouse is outside the spring window, then close it.
1178 static gboolean spring_check_idle(gpointer data)
1180 int p_x, p_y;
1182 if (!spring_window)
1183 return FALSE;
1185 if (!get_pointer_xy(&p_x, &p_y))
1188 GdkWindow *win = spring_window->window->window;
1189 int x, y;
1190 int w, h;
1192 gdk_window_get_position(win, &x, &y);
1193 gdk_window_get_size(win, &w, &h);
1195 if (p_x < x || p_x > x + w || p_y < y || p_y > y + h)
1199 gtk_widget_destroy(spring_window->window);
1200 return FALSE; /* Got it! */
1203 return TRUE; /* Try again later */
1206 static gboolean spring_now(gpointer data)
1208 gboolean old_unique = o_unique_filer_windows.int_value;
1209 guchar *dest_path;
1210 gint x, y;
1212 g_return_val_if_fail(spring_context != NULL, FALSE);
1214 dest_path = g_dataset_get_data(spring_context, "drop_dest_path");
1215 g_return_val_if_fail(dest_path != NULL, FALSE);
1218 * Note: Due to a bug in gtk, if a window disappears during
1219 * a drag and the pointer moves over where the window was,
1220 * the sender crashes! Therefore, do not close any windows
1221 * while dragging! (fixed in later versions)
1224 if (spring_window)
1225 gtk_widget_destroy(spring_window->window);
1228 get_pointer_xy(&x, &y);
1230 o_unique_filer_windows.int_value = FALSE; /* XXX: yuck! */
1231 if (spring_window)
1233 collection_set_cursor_item(spring_window->collection, -1);
1234 filer_change_to(spring_window, dest_path, NULL);
1235 /* DON'T move the window. Gtk+ sometimes doesn't
1236 * notice :-(
1239 else
1241 spring_window = filer_opendir(dest_path, spring_src_window);
1242 if (spring_window)
1244 gtk_timeout_add(500, spring_check_idle, NULL);
1245 g_signal_connect(spring_window->window, "destroy",
1246 G_CALLBACK(spring_win_destroyed), NULL);
1247 centre_window(spring_window->window->window, x, y);
1250 o_unique_filer_windows.int_value = old_unique;
1252 dnd_spring_abort();
1254 return FALSE;
1257 static void spring_win_destroyed(GtkWidget *widget, gpointer data)
1259 spring_window = NULL;
1262 /* HANDLING MOTION EVENTS */
1264 /* If not-NULL, then this widget has a grab */
1265 static GtkWidget *motion_widget = NULL;
1267 /* If TRUE, we must gdk_pointer_ungrab() too when finishing */
1268 static gboolean motion_pointer_grab = FALSE;
1270 /* Call this on a button press event. It stores the mouse position
1271 * as the start of the new drag and returns TRUE if all is well.
1272 * Further motions events are disabled at this point - you must
1273 * then call dnd_motion_start() to set the type of motion expected.
1274 * Grabs the widget on the first press.
1276 * If the system is not ready to handle a motion event (because a
1277 * button is already held down?) it does nothing and returns FALSE.
1279 * If the event is not a single click then it simply returns TRUE.
1281 gboolean dnd_motion_press(GtkWidget *widget, GdkEventButton *event)
1283 if (event->type != GDK_BUTTON_PRESS)
1284 return TRUE; /* Not a click event! */
1286 motion_buttons_pressed++;
1287 if (motion_buttons_pressed == 1)
1289 /* g_print("[ grab! ]\n"); */
1290 gtk_grab_add(widget);
1291 motion_widget = widget;
1294 if (motion_state != MOTION_NONE)
1295 return FALSE; /* Ignore clicks - we're busy! */
1297 motion_state = MOTION_DISABLED;
1298 drag_start_x = event->x_root;
1299 drag_start_y = event->y_root;
1301 return TRUE;
1304 /* After the button press event, decide what kind of motion is expected.
1305 * If you don't call this then the motion system is disabled - call
1306 * dnd_motion_release() to reset it.
1308 * Note: If you open a popup menu or start DND call dnd_motion_ungrab()
1309 * instead.
1311 void dnd_motion_start(MotionType motion)
1313 g_return_if_fail(motion_state == MOTION_DISABLED);
1315 motion_state = motion;
1318 /* Call this on a button release event. If some buttons are still pressed,
1319 * returns TRUE and does nothing.
1321 * Otherwise, it resets the motion system to be ready again and returns TRUE.
1323 * If the motion system wasn't being used (MOTION_NONE) then it does nothing
1324 * and returns FALSE - process the release event yourself as it isn't part
1325 * of a motion. This also happens if a motion was primed but never happened.
1327 gboolean dnd_motion_release(GdkEventButton *event)
1329 MotionType motion = motion_state;
1330 int dx, dy;
1332 if (motion_buttons_pressed == 0)
1333 return TRUE; /* We were disabled */
1335 if (motion_buttons_pressed == 1)
1336 dnd_motion_ungrab();
1337 else
1339 motion_buttons_pressed--;
1340 return TRUE;
1343 if (motion == MOTION_REPOSITION || motion == MOTION_DISABLED)
1344 return TRUE; /* Already done something - eat the event */
1346 /* Eat release events that happen too far from the click
1347 * source. Otherwise, allow the caller to treat this as a click
1348 * that never became a motion.
1350 dx = event->x_root - drag_start_x;
1351 dy = event->y_root - drag_start_y;
1353 return ABS(dx) > 5 || ABS(dy) > 5;
1356 /* Use this to disable the motion system. The system will be reset once
1357 * all mouse buttons are released.
1359 void dnd_motion_disable(void)
1361 g_return_if_fail(motion_state != MOTION_NONE &&
1362 motion_state != MOTION_DISABLED);
1364 motion_state = MOTION_DISABLED;
1367 /* Use this if something else is going to grab the pointer so that
1368 * we won't get any more motion or release events.
1370 void dnd_motion_ungrab(void)
1372 if (motion_buttons_pressed > 0)
1374 if (motion_pointer_grab)
1376 gdk_pointer_ungrab(GDK_CURRENT_TIME);
1377 motion_pointer_grab = FALSE;
1378 /* g_print("[ ungrab_pointer ]\n"); */
1380 gtk_grab_remove(motion_widget);
1381 motion_widget = NULL;
1382 motion_buttons_pressed = 0;
1383 /* g_print("[ ungrab ]\n"); */
1386 motion_state = MOTION_NONE;
1389 /* Call this on motion events. If the mouse position is far enough
1390 * from the click position, returns TRUE and does dnd_motion_ungrab().
1391 * You should then start regular drag-and-drop.
1393 * Otherwise, returns FALSE.
1395 gboolean dnd_motion_moved(GdkEventMotion *event)
1397 int dx, dy;
1399 dx = event->x_root - drag_start_x;
1400 dy = event->y_root - drag_start_y;
1402 if (ABS(dx) <= 5 && ABS(dy) <= 5)
1403 return FALSE; /* Not far enough */
1405 dnd_motion_ungrab();
1407 return TRUE;
1410 /* Normally, the X server will automatically grab the pointer on a
1411 * button press and ungrab on release. However, if the grab widget
1412 * is reparented then call this to re-aquire the grab.
1414 void dnd_motion_grab_pointer(void)
1416 g_return_if_fail(motion_widget != NULL);
1418 gdk_pointer_grab(motion_widget->window, FALSE,
1419 GDK_POINTER_MOTION_MASK |
1420 GDK_BUTTON_RELEASE_MASK,
1421 FALSE, NULL, GDK_CURRENT_TIME);
1423 motion_pointer_grab = TRUE;