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)
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
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 */
31 #include <sys/param.h>
34 #include <X11/Xatom.h>
36 #include "collection.h"
45 #include "gui_support.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
,
72 FilerWindow
*filer_window
);
73 static void drag_leave(GtkWidget
*widget
,
74 GdkDragContext
*context
,
76 FilerWindow
*filer_window
);
77 static void desktop_drag_data_received(GtkWidget
*widget
,
78 GdkDragContext
*context
,
81 GtkSelectionData
*selection_data
,
84 FilerWindow
*filer_window
);
85 static void got_data_xds_reply(GtkWidget
*widget
,
86 GdkDragContext
*context
,
87 GtkSelectionData
*selection_data
,
89 static void got_data_raw(GtkWidget
*widget
,
90 GdkDragContext
*context
,
91 GtkSelectionData
*selection_data
,
93 static void got_uri_list(GtkWidget
*widget
,
94 GdkDragContext
*context
,
95 GtkSelectionData
*selection_data
,
97 static void drag_end(GtkWidget
*widget
,
98 GdkDragContext
*context
,
99 FilerWindow
*filer_window
);
100 static gboolean
drag_drop(GtkWidget
*widget
,
101 GdkDragContext
*context
,
106 static void drag_data_received(GtkWidget
*widget
,
107 GdkDragContext
*context
,
110 GtkSelectionData
*selection_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
;
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",
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
,
155 GDK_PROP_MODE_REPLACE
,
160 static char *get_xds_prop(GdkDragContext
*context
)
165 if (gdk_property_get(context
->source_window
,
171 &length
, &prop_text
) && prop_text
)
173 /* Terminate the string */
174 prop_text
= g_realloc(prop_text
, length
+ 1);
175 prop_text
[length
] = '\0';
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
)
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"));
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
);
224 list
= g_slist_append(list
, uri
);
227 uri_list
= linebreak
+ 2;
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
;
250 actions
= GDK_ACTION_MOVE
;
252 target_list
= gtk_target_list_new(target_table
, 1);
254 context
= gtk_drag_begin(widget
,
257 (event
->state
& GDK_BUTTON1_MASK
) ? 1 :
258 (event
->state
& GDK_BUTTON2_MASK
) ? 2 : 3,
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
),
271 /* Copy/Load this item into another directory/application */
272 void drag_one_item(GtkWidget
*widget
,
273 GdkEventMotion
*event
,
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
},
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
, "/",
296 target_list
= gtk_target_list_new(target_table
, 3);
297 g_free(target_table
[2].target
);
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
;
305 actions
= GDK_ACTION_MOVE
;
307 context
= gtk_drag_begin(widget
,
310 (event
->state
& GDK_BUTTON1_MASK
) ? 1 :
311 (event
->state
& GDK_BUTTON2_MASK
) ? 2 : 3,
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",
320 gtk_drag_set_icon_pixmap(context
,
321 gtk_widget_get_colormap(widget
),
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
,
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
;
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
;
366 g_warning("drag_data_get: Can't find path!\n");
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
;
375 delayed_error("drag_data_get",
376 _("Internal error - bad info type"));
380 gtk_selection_data_set(selection_data
,
386 if (delete_once_sent
)
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
,
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
,
447 sizeof(target_table
) / sizeof(*target_table
),
449 gtk_signal_connect(GTK_OBJECT(widget
), "drag_data_received",
450 (GtkSignalFunc
) desktop_drag_data_received
,
454 static void scrolled(GtkAdjustment
*adj
, Collection
*collection
)
456 collection_set_cursor_item(collection
, -1);
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
,
468 FilerWindow
*filer_window
)
472 GdkDragAction action
= context
->suggested_action
;
473 char *new_path
= 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
,
486 item
= item_number
>= 0
487 ? (DirItem
*) filer_window
->collection
->items
[item_number
].data
490 if (item
&& filer_window
->collection
->items
[item_number
].selected
)
493 type
= dnd_motion_item(context
, &item
);
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
)
511 gtk_signal_disconnect(scrolled_adj
,
514 scrolled_signal
= gtk_signal_connect(
517 GTK_SIGNAL_FUNC(scrolled
),
518 filer_window
->collection
);
520 dnd_spring_load(context
);
527 collection_set_cursor_item(filer_window
->collection
,
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
)
542 new_path
= make_path(filer_window
->path
,
543 item
->leafname
)->str
;
545 new_path
= filer_window
->path
;
548 g_dataset_set_data(context
, "drop_dest_type", 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
);
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
;
573 /* If we didn't drop onto a directory, application or
574 * executable file then act as though the drop is to the
577 if (item
->base_type
!= TYPE_DIRECTORY
578 && !(item
->mime_type
== &special_exec
))
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
;
604 if (provides(context
, text_uri_list
) ||
605 provides(context
, application_octet_stream
))
606 return drop_dest_prog
;
612 /* Remove highlights */
613 static void drag_leave(GtkWidget
*widget
,
614 GdkDragContext
*context
,
616 FilerWindow
*filer_window
)
618 collection_set_autoscroll(filer_window
->collection
, FALSE
);
619 collection_set_cursor_item(filer_window
->collection
, -1);
623 gtk_signal_disconnect(scrolled_adj
,
629 /* User has tried to drop some data on us. Decide what format we would
632 static gboolean
drag_drop(GtkWidget
*widget
,
633 GdkDragContext
*context
,
640 char *leafname
= NULL
;
641 GdkAtom target
= GDK_NONE
;
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
);
655 if (strchr(leafname
, '/'))
657 error
= _("XDS protocol error: "
658 "leafname may not contain '/'\n");
667 uri
= g_string_new(NULL
);
668 g_string_sprintf(uri
, "file://%s%s",
672 set_xds_prop(context
, uri
->str
);
673 g_string_free(uri
, TRUE
);
675 target
= XdndDirectSave0
;
676 g_dataset_set_data_full(context
, "leafname",
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
;
692 if (dest_type
== drop_dest_dir
)
693 error
= _("Sorry - I require a target type of "
694 "text/uri-list or XdndDirectSave0.");
696 error
= _("Sorry - I require a target type of "
697 "text/uri-list or application/octet-stream.");
702 gtk_drag_finish(context
, FALSE
, FALSE
, time
); /* Failure */
704 delayed_error(PROJECT
, error
);
707 gtk_drag_get_data(widget
, context
, target
, time
);
712 /* Called when a text/uri-list arrives */
713 static void desktop_drag_data_received(GtkWidget
*widget
,
714 GdkDragContext
*context
,
717 GtkSelectionData
*selection_data
,
720 FilerWindow
*filer_window
)
725 if (!selection_data
->data
)
731 gdk_window_get_position(widget
->window
, &dx
, &dy
);
735 uris
= uri_list_to_gslist(selection_data
->data
);
737 for (next
= uris
; next
; next
= next
->next
)
741 path
= get_local_path((gchar
*) next
->data
);
744 pinboard_pin(path
, NULL
, x
, y
);
755 /* Called when some data arrives from the remote app (which we asked for
758 static void drag_data_received(GtkWidget
*widget
,
759 GdkDragContext
*context
,
762 GtkSelectionData
*selection_data
,
767 if (!selection_data
->data
)
770 gtk_drag_finish(context
, FALSE
, FALSE
, time
); /* Failure */
777 got_data_xds_reply(widget
, context
,
778 selection_data
, time
);
781 got_data_raw(widget
, context
, selection_data
, time
);
783 case TARGET_URI_LIST
:
784 got_uri_list(widget
, context
, selection_data
, time
);
787 gtk_drag_finish(context
, FALSE
, FALSE
, time
);
788 delayed_error("drag_data_received",
789 _("Unknown target"));
794 static void got_data_xds_reply(GtkWidget
*widget
,
795 GdkDragContext
*context
,
796 GtkSelectionData
*selection_data
,
799 gboolean mark_unsafe
= TRUE
;
800 char response
= *selection_data
->data
;
804 dest_path
= g_dataset_get_data(context
, "drop_dest_path");
806 if (selection_data
->length
!= 1)
811 /* Sender couldn't save there - ask for another
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
);
822 error
= _("Remote app can't or won't send me "
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 */
842 set_xds_prop(context
, "");
843 /* Unsave also implies that the drag failed */
844 gtk_drag_finish(context
, FALSE
, FALSE
, time
);
848 delayed_error(PROJECT
, error
);
851 static void got_data_raw(GtkWidget
*widget
,
852 GdkDragContext
*context
,
853 GtkSelectionData
*selection_data
,
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! */
874 leafname
= g_dataset_get_data(context
, "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
);
884 error
= g_strerror(errno
);
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
);
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
);
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
,
922 gboolean send_reply
= TRUE
;
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
);
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
))
946 leaf
= strrchr(uri_list
->data
, '/');
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
);
958 error
= _("Can't get data from remote machine "
959 "(application/octet-stream not provided)");
963 GSList
*local_paths
= NULL
;
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
)
974 path
= get_local_path((char *) next_uri
->data
);
977 local_paths
= g_slist_append(local_paths
,
980 error
= _("Some of these files are on a "
981 "different machine - they will be "
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
);
998 error
= _("Unknown action requested");
1000 for (next
= local_paths
; next
; next
= next
->next
)
1002 g_slist_free(local_paths
);
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
;
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
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"))
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
)
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
)
1073 if (!get_pointer_xy(&p_x
, &p_y
))
1076 GdkWindow *win = spring_window->window->window;
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
;
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)
1113 gtk_widget_destroy(spring_window->window);
1116 get_pointer_xy(&x
, &y
);
1118 o_unique_filer_windows
= FALSE
;
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
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
);
1134 centre_window(spring_window
->window
->window
, x
, y
);
1136 o_unique_filer_windows
= old_unique
;
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
;
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()
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
;
1218 if (motion_buttons_pressed
== 0)
1219 return TRUE
; /* We were disabled */
1221 if (motion_buttons_pressed
== 1)
1222 dnd_motion_ungrab();
1225 motion_buttons_pressed
--;
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
)
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();
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
;