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)
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>
40 #include "collection.h"
46 #include "gui_support.h"
53 #include "usericons.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
,
79 FilerWindow
*filer_window
);
80 static void drag_leave(GtkWidget
*widget
,
81 GdkDragContext
*context
,
83 FilerWindow
*filer_window
);
84 static void desktop_drag_data_received(GtkWidget
*widget
,
85 GdkDragContext
*context
,
88 GtkSelectionData
*selection_data
,
91 FilerWindow
*filer_window
);
92 static void got_data_xds_reply(GtkWidget
*widget
,
93 GdkDragContext
*context
,
94 GtkSelectionData
*selection_data
,
96 static void got_data_raw(GtkWidget
*widget
,
97 GdkDragContext
*context
,
98 GtkSelectionData
*selection_data
,
100 static void got_uri_list(GtkWidget
*widget
,
101 GdkDragContext
*context
,
102 GtkSelectionData
*selection_data
,
104 static void drag_end(GtkWidget
*widget
,
105 GdkDragContext
*context
,
106 FilerWindow
*filer_window
);
107 static gboolean
drag_drop(GtkWidget
*widget
,
108 GdkDragContext
*context
,
113 static void drag_data_received(GtkWidget
*widget
,
114 GdkDragContext
*context
,
117 GtkSelectionData
*selection_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
);
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 gint 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
;
168 GtkItemFactory
*item_factory
;
170 XdndDirectSave0
= gdk_atom_intern("XdndDirectSave0", FALSE
);
171 xa_text_plain
= gdk_atom_intern("text/plain", FALSE
);
172 text_uri_list
= gdk_atom_intern("text/uri-list", FALSE
);
173 application_octet_stream
= gdk_atom_intern("application/octet-stream",
175 xa_string
= gdk_atom_intern("STRING", FALSE
);
177 option_add_int(&o_dnd_drag_to_icons
, "dnd_drag_to_icons", 1);
178 option_add_int(&o_dnd_spring_open
, "dnd_spring_open", 0);
179 option_add_int(&o_dnd_spring_delay
, "dnd_spring_delay", 400);
180 option_add_int(&o_dnd_middle_menu
, "dnd_middle_menu", TRUE
);
182 item_factory
= menu_create(menu_def
,
183 sizeof(menu_def
) / sizeof(*menu_def
),
185 dnd_menu
= gtk_item_factory_get_widget(item_factory
, "<dnd>");
188 /* SUPPORT FUNCTIONS */
190 /* Set the XdndDirectSave0 property on the source window for this context */
191 static void set_xds_prop(GdkDragContext
*context
, char *text
)
193 gdk_property_change(context
->source_window
,
196 GDK_PROP_MODE_REPLACE
,
201 static char *get_xds_prop(GdkDragContext
*context
)
206 if (gdk_property_get(context
->source_window
,
212 &length
, &prop_text
) && prop_text
)
214 /* Terminate the string */
215 prop_text
= g_realloc(prop_text
, length
+ 1);
216 prop_text
[length
] = '\0';
223 /* Is the sender willing to supply this target type? */
224 gboolean
provides(GdkDragContext
*context
, GdkAtom target
)
226 GList
*targets
= context
->targets
;
228 while (targets
&& ((GdkAtom
) targets
->data
!= target
))
229 targets
= targets
->next
;
231 return targets
!= NULL
;
234 /* Convert a list of URIs into a list of strings.
235 * Lines beginning with # are skipped.
236 * The text block passed in is zero terminated (after the final CRLF)
238 GList
*uri_list_to_glist(char *uri_list
)
248 linebreak
= strchr(uri_list
, 13);
250 if (!linebreak
|| linebreak
[1] != 10)
252 delayed_error("uri_list_to_glist: %s",
253 _("Incorrect or missing line "
254 "break in text/uri-list data"));
258 length
= linebreak
- uri_list
;
260 if (length
&& uri_list
[0] != '#')
262 uri
= g_malloc(sizeof(char) * (length
+ 1));
263 strncpy(uri
, uri_list
, length
);
265 list
= g_list_append(list
, uri
);
268 uri_list
= linebreak
+ 2;
274 /* DRAGGING FROM US */
276 /* The user has held the mouse button down over a group of item and moved -
277 * start a drag. 'uri_list' is copied, so you can delete it straight away.
279 void drag_selection(GtkWidget
*widget
, GdkEventMotion
*event
, guchar
*uri_list
)
281 GdkDragContext
*context
;
282 GdkDragAction actions
;
283 GtkTargetList
*target_list
;
284 GtkTargetEntry target_table
[] = {
285 {"text/uri-list", 0, TARGET_URI_LIST
},
288 if (event
->state
& GDK_BUTTON1_MASK
)
289 actions
= GDK_ACTION_COPY
| GDK_ACTION_MOVE
290 | GDK_ACTION_LINK
| GDK_ACTION_ASK
;
293 if (o_dnd_middle_menu
.int_value
)
294 actions
= GDK_ACTION_ASK
;
296 actions
= GDK_ACTION_MOVE
;
299 target_list
= gtk_target_list_new(target_table
, 1);
301 context
= gtk_drag_begin(widget
,
304 (event
->state
& GDK_BUTTON1_MASK
) ? 1 :
305 (event
->state
& GDK_BUTTON2_MASK
) ? 2 : 3,
308 g_dataset_set_data_full(context
, "uri_list",
309 g_strdup(uri_list
), g_free
);
311 gtk_drag_set_icon_pixmap(context
,
312 gtk_widget_get_colormap(widget
),
318 /* Copy/Load this item into another directory/application */
319 void drag_one_item(GtkWidget
*widget
,
320 GdkEventMotion
*event
,
326 GdkDragContext
*context
;
327 GdkDragAction actions
;
328 GtkTargetList
*target_list
;
329 GtkTargetEntry target_table
[] = {
330 {"text/uri-list", 0, TARGET_URI_LIST
},
331 {"application/octet-stream", 0, TARGET_RAW
},
335 g_return_if_fail(full_path
!= NULL
);
336 g_return_if_fail(item
!= NULL
);
341 if (item
->base_type
== TYPE_FILE
)
343 MIME_type
*t
= item
->mime_type
;
345 target_table
[2].target
= g_strconcat(t
->media_type
, "/",
347 target_list
= gtk_target_list_new(target_table
, 3);
348 g_free(target_table
[2].target
);
351 target_list
= gtk_target_list_new(target_table
, 1);
353 if (event
->state
& GDK_BUTTON1_MASK
)
354 actions
= GDK_ACTION_COPY
| GDK_ACTION_ASK
355 | GDK_ACTION_MOVE
| GDK_ACTION_LINK
;
358 if (o_dnd_middle_menu
.int_value
)
359 actions
= GDK_ACTION_ASK
;
361 actions
= GDK_ACTION_MOVE
;
364 context
= gtk_drag_begin(widget
,
367 (event
->state
& GDK_BUTTON1_MASK
) ? 1 :
368 (event
->state
& GDK_BUTTON2_MASK
) ? 2 : 3,
371 g_dataset_set_data_full(context
, "full_path",
372 g_strdup(full_path
), g_free
);
373 uri
= g_strconcat("file://", our_host_name_for_dnd(),
374 full_path
, "\r\n", NULL
);
375 g_dataset_set_data_full(context
, "uri_list", uri
, g_free
);
377 g_return_if_fail(image
!= NULL
);
379 gtk_drag_set_icon_pixmap(context
,
380 gtk_widget_get_colormap(widget
),
381 image
->pixmap
, image
->mask
, 0, 0);
384 static void drag_end(GtkWidget
*widget
,
385 GdkDragContext
*context
,
386 FilerWindow
*filer_window
)
388 collection_set_autoscroll(filer_window
->collection
, FALSE
);
389 if (filer_window
->temp_item_selected
)
391 collection_clear_selection(filer_window
->collection
);
392 filer_window
->temp_item_selected
= FALSE
;
396 /* Called when a remote app wants us to send it some data.
397 * TODO: Maybe we should handle errors better (ie, let the remote app know
398 * the drag has failed)?
400 void drag_data_get(GtkWidget
*widget
,
401 GdkDragContext
*context
,
402 GtkSelectionData
*selection_data
,
407 char *to_send
= "E"; /* Default to sending an error */
408 long to_send_length
= 1;
409 gboolean delete_once_sent
= FALSE
;
413 type
= gdk_x11_xatom_to_atom(XA_STRING
);
418 path
= g_dataset_get_data(context
, "full_path");
419 if (path
&& load_file(path
, &to_send
, &to_send_length
))
421 delete_once_sent
= TRUE
;
422 type
= selection_data
->target
;
425 g_warning("drag_data_get: Can't find path!\n");
427 case TARGET_URI_LIST
:
428 to_send
= g_dataset_get_data(context
, "uri_list");
429 to_send_length
= strlen(to_send
);
430 type
= text_uri_list
; /* (needed for xine) */
431 delete_once_sent
= FALSE
;
434 delayed_error("drag_data_get: %s",
435 _("Internal error - bad info type"));
439 gtk_selection_data_set(selection_data
,
445 if (delete_once_sent
)
451 /* Set up this widget as a drop-target.
452 * Does not attach any motion handlers.
454 void make_drop_target(GtkWidget
*widget
, GtkDestDefaults defaults
)
456 GtkTargetEntry target_table
[] =
458 {"text/uri-list", 0, TARGET_URI_LIST
},
459 {"XdndDirectSave0", 0, TARGET_XDS
},
460 {"application/octet-stream", 0, TARGET_RAW
},
463 gtk_drag_dest_set(widget
,
466 sizeof(target_table
) / sizeof(*target_table
),
467 GDK_ACTION_COPY
| GDK_ACTION_ASK
| GDK_ACTION_MOVE
468 | GDK_ACTION_LINK
| GDK_ACTION_PRIVATE
);
470 gtk_signal_connect(GTK_OBJECT(widget
), "drag_drop",
471 GTK_SIGNAL_FUNC(drag_drop
), NULL
);
472 gtk_signal_connect(GTK_OBJECT(widget
), "drag_data_received",
473 GTK_SIGNAL_FUNC(drag_data_received
), NULL
);
476 /* Set up this filer window as a drop target. Called once, when the
477 * filer window is first created.
479 void drag_set_dest(FilerWindow
*filer_window
)
481 GtkWidget
*widget
= GTK_WIDGET(filer_window
->collection
);
483 make_drop_target(widget
, 0);
485 gtk_signal_connect(GTK_OBJECT(widget
), "drag_motion",
486 GTK_SIGNAL_FUNC(drag_motion
), filer_window
);
487 gtk_signal_connect(GTK_OBJECT(widget
), "drag_leave",
488 GTK_SIGNAL_FUNC(drag_leave
), filer_window
);
489 gtk_signal_connect(GTK_OBJECT(widget
), "drag_end",
490 GTK_SIGNAL_FUNC(drag_end
), filer_window
);
493 /* Like drag_set_dest, but for a pinboard-type widget.
494 * You must ensure that dnd events reach this widget (eg with
495 * setup_xdnd_proxy() for the root window).
497 void drag_set_pinboard_dest(GtkWidget
*widget
)
499 GtkTargetEntry target_table
[] = {
500 {"text/uri-list", 0, TARGET_URI_LIST
},
503 gtk_drag_dest_set(widget
,
504 GTK_DEST_DEFAULT_DROP
,
506 sizeof(target_table
) / sizeof(*target_table
),
508 gtk_signal_connect(GTK_OBJECT(widget
), "drag_data_received",
509 (GtkSignalFunc
) desktop_drag_data_received
,
514 static void scrolled(GtkAdjustment
*adj
, Collection
*collection
)
516 collection_set_cursor_item(collection
, -1);
521 /* Called during the drag when the mouse is in a widget registered
522 * as a drop target. Returns TRUE if we can accept the drop.
524 static gboolean
drag_motion(GtkWidget
*widget
,
525 GdkDragContext
*context
,
529 FilerWindow
*filer_window
)
533 GdkDragAction action
= context
->suggested_action
;
534 char *new_path
= NULL
;
536 gboolean retval
= FALSE
;
538 if (filer_window
->collection
->auto_scroll
== -1)
539 collection_set_autoscroll(filer_window
->collection
, TRUE
);
541 if (o_dnd_drag_to_icons
.int_value
)
542 item_number
= collection_get_item(filer_window
->collection
,
547 item
= item_number
>= 0
548 ? (DirItem
*) filer_window
->collection
->items
[item_number
].data
551 if (item
&& filer_window
->collection
->items
[item_number
].selected
)
554 type
= dnd_motion_item(context
, &item
);
559 /* Don't allow drops to non-writeable directories. BUT, still
560 * allow drops on non-writeable SUBdirectories so that we can
561 * do the spring-open thing.
563 if (item
&& type
== drop_dest_dir
&&
564 !(item
->flags
& ITEM_FLAG_APPDIR
))
567 /* XXX: Do we still need this under 2.0? */
568 GtkObject
*vadj
= GTK_OBJECT(filer_window
->collection
->vadj
);
570 /* Subdir: prepare for spring-open */
571 if (scrolled_adj
!= vadj
)
574 gtk_signal_disconnect(scrolled_adj
,
577 scrolled_signal
= gtk_signal_connect(
580 GTK_SIGNAL_FUNC(scrolled
),
581 filer_window
->collection
);
584 dnd_spring_load(context
, filer_window
);
591 collection_set_cursor_item(filer_window
->collection
,
596 collection_set_cursor_item(filer_window
->collection
, -1);
598 /* Disallow background drops within a single window */
599 if (type
&& gtk_drag_get_source_widget(context
) == widget
)
606 new_path
= make_path(filer_window
->path
,
607 item
->leafname
)->str
;
609 new_path
= filer_window
->path
;
612 g_dataset_set_data(context
, "drop_dest_type", type
);
615 gdk_drag_status(context
, action
, time
);
616 g_dataset_set_data_full(context
, "drop_dest_path",
617 g_strdup(new_path
), g_free
);
624 /* item is the item the file is held over, NULL for directory background.
625 * 'item' may be NULL on exit if the drop should be treated as onto the
626 * background. Disallow drags to a selected icon before calling this.
628 * Returns NULL to reject the drop, or drop_dest_prog/drop_dest_dir to
629 * accept. Build the path based on item.
631 guchar
*dnd_motion_item(GdkDragContext
*context
, DirItem
**item_p
)
633 DirItem
*item
= *item_p
;
637 /* If we didn't drop onto a directory, application or
638 * executable file then act as though the drop is to the
641 if (item
->base_type
!= TYPE_DIRECTORY
642 && !(item
->mime_type
== special_exec
))
651 /* Drop onto the window background */
653 return drop_dest_dir
;
656 /* Drop onto a program/directory of some sort */
658 if (item
->base_type
== TYPE_DIRECTORY
&&
659 !(item
->flags
& ITEM_FLAG_APPDIR
))
661 /* A normal directory */
662 if (provides(context
, text_uri_list
) ||
663 provides(context
, XdndDirectSave0
))
664 return drop_dest_dir
;
668 if (provides(context
, text_uri_list
) ||
669 provides(context
, application_octet_stream
))
670 return drop_dest_prog
;
676 /* Remove highlights */
677 static void drag_leave(GtkWidget
*widget
,
678 GdkDragContext
*context
,
680 FilerWindow
*filer_window
)
682 collection_set_autoscroll(filer_window
->collection
, FALSE
);
683 collection_set_cursor_item(filer_window
->collection
, -1);
687 gtk_signal_disconnect(scrolled_adj
,
693 /* User has tried to drop some data on us. Decide what format we would
696 static gboolean
drag_drop(GtkWidget
*widget
,
697 GdkDragContext
*context
,
704 char *leafname
= NULL
;
705 GdkAtom target
= GDK_NONE
;
707 char *dest_type
= NULL
;
709 dest_path
= g_dataset_get_data(context
, "drop_dest_path");
710 dest_type
= g_dataset_get_data(context
, "drop_dest_type");
712 g_return_val_if_fail(dest_path
!= NULL
, TRUE
);
714 if (dest_type
== drop_dest_dir
&& provides(context
, XdndDirectSave0
))
716 leafname
= get_xds_prop(context
);
719 if (strchr(leafname
, '/'))
721 error
= _("XDS protocol error: "
722 "leafname may not contain '/'\n");
731 uri
= g_string_new(NULL
);
732 g_string_sprintf(uri
, "file://%s%s",
733 our_host_name_for_dnd(),
736 set_xds_prop(context
, uri
->str
);
737 g_string_free(uri
, TRUE
);
739 target
= XdndDirectSave0
;
740 g_dataset_set_data_full(context
, "leafname",
746 "XdndDirectSave0 target provided, but the atom "
747 "XdndDirectSave0 (type text/plain) did not "
748 "contain a leafname\n");
750 else if (provides(context
, text_uri_list
))
751 target
= text_uri_list
;
752 else if (provides(context
, application_octet_stream
))
753 target
= application_octet_stream
;
756 if (dest_type
== drop_dest_dir
)
757 error
= _("Sorry - I require a target type of "
758 "text/uri-list or XdndDirectSave0.");
760 error
= _("Sorry - I require a target type of "
761 "text/uri-list or application/octet-stream.");
766 gtk_drag_finish(context
, FALSE
, FALSE
, time
); /* Failure */
768 delayed_error("%s", error
);
771 gtk_drag_get_data(widget
, context
, target
, time
);
776 /* Called when a text/uri-list arrives */
777 static void desktop_drag_data_received(GtkWidget
*widget
,
778 GdkDragContext
*context
,
781 GtkSelectionData
*selection_data
,
784 FilerWindow
*filer_window
)
789 if (!selection_data
->data
)
795 if (pinboard_drag_in_progress
)
797 pinboard_move_icons();
801 gdk_window_get_position(widget
->window
, &dx
, &dy
);
805 uris
= uri_list_to_glist(selection_data
->data
);
807 for (next
= uris
; next
; next
= next
->next
)
811 path
= get_local_path((gchar
*) next
->data
);
814 pinboard_pin(path
, NULL
, x
, y
);
825 /* Called when some data arrives from the remote app (which we asked for
828 static void drag_data_received(GtkWidget
*widget
,
829 GdkDragContext
*context
,
832 GtkSelectionData
*selection_data
,
837 if (!selection_data
->data
)
840 gtk_drag_finish(context
, FALSE
, FALSE
, time
); /* Failure */
847 got_data_xds_reply(widget
, context
,
848 selection_data
, time
);
851 got_data_raw(widget
, context
, selection_data
, time
);
853 case TARGET_URI_LIST
:
854 got_uri_list(widget
, context
, selection_data
, time
);
857 gtk_drag_finish(context
, FALSE
, FALSE
, time
);
858 delayed_error("drag_data_received: %s",
859 _("Unknown target"));
864 static void got_data_xds_reply(GtkWidget
*widget
,
865 GdkDragContext
*context
,
866 GtkSelectionData
*selection_data
,
869 gboolean mark_unsafe
= TRUE
;
870 char response
= *selection_data
->data
;
874 dest_path
= g_dataset_get_data(context
, "drop_dest_path");
876 if (selection_data
->length
!= 1)
881 /* Sender couldn't save there - ask for another
884 if (provides(context
, application_octet_stream
))
886 mark_unsafe
= FALSE
; /* Wait and see */
888 gtk_drag_get_data(widget
, context
,
889 application_octet_stream
, time
);
892 error
= _("Remote app can't or won't send me "
895 else if (response
== 'S')
897 /* Success - data is saved */
898 mark_unsafe
= FALSE
; /* It really is safe */
899 gtk_drag_finish(context
, TRUE
, FALSE
, time
);
901 refresh_dirs(dest_path
);
903 else if (response
!= 'E')
905 error
= _("XDS protocol error: "
906 "return code should be 'S', 'F' or 'E'\n");
908 /* else: error has been reported by the sender */
912 set_xds_prop(context
, "");
913 /* Unsave also implies that the drag failed */
914 gtk_drag_finish(context
, FALSE
, FALSE
, time
);
918 delayed_error("%s", error
);
921 static void got_data_raw(GtkWidget
*widget
,
922 GdkDragContext
*context
,
923 GtkSelectionData
*selection_data
,
931 g_return_if_fail(selection_data
->data
!= NULL
);
933 dest_path
= g_dataset_get_data(context
, "drop_dest_path");
935 if (context
->action
== GDK_ACTION_ASK
)
937 gtk_drag_finish(context
, FALSE
, FALSE
, time
); /* Failure */
938 delayed_error(_("Sorry, can't display a menu of actions "
939 "for a remote file / raw data."));
943 if (g_dataset_get_data(context
, "drop_dest_type") == drop_dest_prog
)
945 /* The data needs to be sent to an application */
946 run_with_data(dest_path
,
947 selection_data
->data
, selection_data
->length
);
948 gtk_drag_finish(context
, TRUE
, FALSE
, time
); /* Success! */
952 leafname
= g_dataset_get_data(context
, "leafname");
954 leafname
= _("UntitledData");
956 fd
= open(make_path(dest_path
, leafname
)->str
,
957 O_WRONLY
| O_CREAT
| O_EXCL
| O_NOCTTY
,
958 S_IRUSR
| S_IRGRP
| S_IROTH
|
959 S_IWUSR
| S_IWGRP
| S_IWOTH
);
962 error
= g_strerror(errno
);
966 selection_data
->data
,
967 selection_data
->length
) == -1)
968 error
= g_strerror(errno
);
970 if (close(fd
) == -1 && !error
)
971 error
= g_strerror(errno
);
973 refresh_dirs(dest_path
);
978 if (provides(context
, XdndDirectSave0
))
979 set_xds_prop(context
, "");
980 gtk_drag_finish(context
, FALSE
, FALSE
, time
); /* Failure */
981 delayed_error(_("Error saving file: %s"), error
);
984 gtk_drag_finish(context
, TRUE
, FALSE
, time
); /* Success! */
987 /* We've got a list of URIs from somewhere (probably another filer window).
988 * If the files are on the local machine then try to copy them ourselves,
989 * otherwise, if there was only one file and application/octet-stream was
990 * provided, get the data via the X server.
992 static void got_uri_list(GtkWidget
*widget
,
993 GdkDragContext
*context
,
994 GtkSelectionData
*selection_data
,
1000 gboolean send_reply
= TRUE
;
1004 dest_path
= g_dataset_get_data(context
, "drop_dest_path");
1005 type
= g_dataset_get_data(context
, "drop_dest_type");
1007 g_return_if_fail(dest_path
!= NULL
);
1009 uri_list
= uri_list_to_glist(selection_data
->data
);
1012 error
= _("No URIs in the text/uri-list (nothing to do!)");
1013 else if (context
->action
!= GDK_ACTION_ASK
&& type
== drop_dest_prog
)
1014 run_with_files(dest_path
, uri_list
);
1015 else if ((!uri_list
->next
) && (!get_local_path(uri_list
->data
)))
1017 /* There is one URI in the list, and it's not on the local
1018 * machine. Get it via the X server if possible.
1021 if (provides(context
, application_octet_stream
))
1024 leaf
= strrchr(uri_list
->data
, '/');
1028 leaf
= uri_list
->data
;
1029 g_dataset_set_data_full(context
, "leafname",
1030 g_strdup(leaf
), g_free
);
1031 gtk_drag_get_data(widget
, context
,
1032 application_octet_stream
, time
);
1036 error
= _("Can't get data from remote machine "
1037 "(application/octet-stream not provided)");
1041 GList
*local_paths
= NULL
;
1044 /* Either one local URI, or a list. If everything in the list
1045 * isn't local then we are stuck.
1048 for (next_uri
= uri_list
; next_uri
; next_uri
= next_uri
->next
)
1052 path
= get_local_path((char *) next_uri
->data
);
1055 local_paths
= g_list_append(local_paths
,
1058 error
= _("Some of these files are on a "
1059 "different machine - they will be "
1065 error
= _("None of these files are on the local "
1066 "machine - I can't operate on multiple "
1067 "remote files - sorry.");
1069 else if (context
->action
== GDK_ACTION_ASK
)
1070 prompt_action(local_paths
, dest_path
);
1071 else if (context
->action
== GDK_ACTION_MOVE
)
1072 action_move(local_paths
, dest_path
, NULL
, -1);
1073 else if (context
->action
== GDK_ACTION_COPY
)
1074 action_copy(local_paths
, dest_path
, NULL
, -1);
1075 else if (context
->action
== GDK_ACTION_LINK
)
1076 action_link(local_paths
, dest_path
, NULL
);
1078 error
= _("Unknown action requested");
1080 for (next
= local_paths
; next
; next
= next
->next
)
1082 g_list_free(local_paths
);
1087 gtk_drag_finish(context
, FALSE
, FALSE
, time
); /* Failure */
1088 delayed_error(_("Error getting file list: %s"), error
);
1090 else if (send_reply
)
1091 gtk_drag_finish(context
, TRUE
, FALSE
, time
); /* Success! */
1093 next_uri
= uri_list
;
1096 g_free(next_uri
->data
);
1097 next_uri
= next_uri
->next
;
1099 g_list_free(uri_list
);
1102 /* Called when an item from the ACTION_ASK menu is chosen */
1103 static void menuitem_response(gpointer data
, guint action
, GtkWidget
*widget
)
1105 if (action
== MENU_MOVE
)
1106 action_move(prompt_local_paths
, prompt_dest_path
, NULL
, -1);
1107 else if (action
== MENU_COPY
)
1108 action_copy(prompt_local_paths
, prompt_dest_path
, NULL
, -1);
1109 else if (action
== MENU_LINK
)
1110 action_link(prompt_local_paths
, prompt_dest_path
, NULL
);
1111 else if (action
== MENU_SET_ICON
)
1113 if (g_list_length(prompt_local_paths
) == 1)
1114 set_icon_path(prompt_dest_path
,
1115 (char*) prompt_local_paths
->data
);
1118 _("You can't use multiple files with Set Icon!"));
1122 /* When some local files are dropped somewhere with ACTION_ASK, this
1123 * function is called to display the menu.
1125 static void prompt_action(GList
*paths
, gchar
*dest
)
1129 if (prompt_local_paths
)
1131 g_list_foreach(prompt_local_paths
, (GFunc
) g_free
, NULL
);
1132 g_list_free(prompt_local_paths
);
1133 g_free(prompt_dest_path
);
1135 prompt_dest_path
= NULL
;
1136 prompt_local_paths
= NULL
;
1139 /* Make a copy of the arguments */
1140 for (next
= paths
; next
; next
= next
->next
)
1141 prompt_local_paths
= g_list_append(prompt_local_paths
,
1142 g_strdup((gchar
*) next
->data
));
1143 prompt_dest_path
= g_strdup(dest
);
1145 /* Shade 'Set Icon' if there are multiple files */
1146 menu_set_items_shaded(dnd_menu
, g_list_length(paths
) != 1, 4, 1);
1148 show_popup_menu(dnd_menu
, gtk_get_current_event(), 1);
1152 /* SPRING-LOADING */
1154 /* This is the code that makes directories pop open if you hold a
1157 * First, call dnd_spring_load(context) to arm the system.
1158 * After a timeout (1/2 a second) the dest_path directory will be
1159 * opened in a new window, unless dnd_spring_abort is called first.
1162 static gint spring_timeout
= -1;
1163 static GdkDragContext
*spring_context
= NULL
;
1164 static FilerWindow
*spring_window
= NULL
;
1165 static FilerWindow
*spring_src_window
= NULL
;
1167 void dnd_spring_load(GdkDragContext
*context
, FilerWindow
*src_win
)
1169 g_return_if_fail(context
!= NULL
);
1171 if (!o_dnd_spring_open
.int_value
)
1177 spring_context
= context
;
1178 gdk_drag_context_ref(spring_context
);
1179 spring_src_window
= src_win
;
1180 spring_timeout
= gtk_timeout_add(
1181 o_dnd_spring_delay
.int_value
, spring_now
, NULL
);
1184 void dnd_spring_abort(void)
1186 if (!spring_context
)
1189 gdk_drag_context_unref(spring_context
);
1190 spring_context
= NULL
;
1191 gtk_timeout_remove(spring_timeout
);
1194 /* If all mod keys are released, no buttons are pressed, and the
1195 * mouse is outside the spring window, then close it.
1197 static gboolean
spring_check_idle(gpointer data
)
1204 if (!get_pointer_xy(&p_x
, &p_y
))
1207 GdkWindow *win = spring_window->window->window;
1211 gdk_window_get_position(win, &x, &y);
1212 gdk_window_get_size(win, &w, &h);
1214 if (p_x < x || p_x > x + w || p_y < y || p_y > y + h)
1218 gtk_widget_destroy(spring_window
->window
);
1219 return FALSE
; /* Got it! */
1222 return TRUE
; /* Try again later */
1225 static gboolean
spring_now(gpointer data
)
1227 gboolean old_unique
= o_unique_filer_windows
.int_value
;
1231 g_return_val_if_fail(spring_context
!= NULL
, FALSE
);
1233 dest_path
= g_dataset_get_data(spring_context
, "drop_dest_path");
1234 g_return_val_if_fail(dest_path
!= NULL
, FALSE
);
1237 * Note: Due to a bug in gtk, if a window disappears during
1238 * a drag and the pointer moves over where the window was,
1239 * the sender crashes! Therefore, do not close any windows
1240 * while dragging! (fixed in later versions)
1244 gtk_widget_destroy(spring_window->window);
1247 get_pointer_xy(&x
, &y
);
1249 o_unique_filer_windows
.int_value
= FALSE
; /* XXX: yuck! */
1252 collection_set_cursor_item(spring_window
->collection
, -1);
1253 filer_change_to(spring_window
, dest_path
, NULL
);
1254 /* DON'T move the window. Gtk+ sometimes doesn't
1260 spring_window
= filer_opendir(dest_path
, spring_src_window
);
1263 gtk_timeout_add(500, spring_check_idle
, NULL
);
1264 gtk_signal_connect(GTK_OBJECT(spring_window
->window
),
1266 GTK_SIGNAL_FUNC(spring_win_destroyed
),
1268 centre_window(spring_window
->window
->window
, x
, y
);
1271 o_unique_filer_windows
.int_value
= old_unique
;
1278 static void spring_win_destroyed(GtkWidget
*widget
, gpointer data
)
1280 spring_window
= NULL
;
1283 /* HANDLING MOTION EVENTS */
1285 /* If not-NULL, then this widget has a grab */
1286 static GtkWidget
*motion_widget
= NULL
;
1288 /* If TRUE, we must gdk_pointer_ungrab() too when finishing */
1289 static gboolean motion_pointer_grab
= FALSE
;
1291 /* Call this on a button press event. It stores the mouse position
1292 * as the start of the new drag and returns TRUE if all is well.
1293 * Further motions events are disabled at this point - you must
1294 * then call dnd_motion_start() to set the type of motion expected.
1295 * Grabs the widget on the first press.
1297 * If the system is not ready to handle a motion event (because a
1298 * button is already held down?) it does nothing and returns FALSE.
1300 * If the event is not a single click then it simply returns TRUE.
1302 gboolean
dnd_motion_press(GtkWidget
*widget
, GdkEventButton
*event
)
1304 if (event
->type
!= GDK_BUTTON_PRESS
)
1305 return TRUE
; /* Not a click event! */
1307 motion_buttons_pressed
++;
1308 if (motion_buttons_pressed
== 1)
1310 /* g_print("[ grab! ]\n"); */
1311 gtk_grab_add(widget
);
1312 motion_widget
= widget
;
1315 if (motion_state
!= MOTION_NONE
)
1316 return FALSE
; /* Ignore clicks - we're busy! */
1318 motion_state
= MOTION_DISABLED
;
1319 drag_start_x
= event
->x_root
;
1320 drag_start_y
= event
->y_root
;
1325 /* After the button press event, decide what kind of motion is expected.
1326 * If you don't call this then the motion system is disabled - call
1327 * dnd_motion_release() to reset it.
1329 * Note: If you open a popup menu or start DND call dnd_motion_ungrab()
1332 void dnd_motion_start(MotionType motion
)
1334 g_return_if_fail(motion_state
== MOTION_DISABLED
);
1336 motion_state
= motion
;
1339 /* Call this on a button release event. If some buttons are still pressed,
1340 * returns TRUE and does nothing.
1342 * Otherwise, it resets the motion system to be ready again and returns TRUE.
1344 * If the motion system wasn't being used (MOTION_NONE) then it does nothing
1345 * and returns FALSE - process the release event yourself as it isn't part
1346 * of a motion. This also happens if a motion was primed but never happened.
1348 gboolean
dnd_motion_release(GdkEventButton
*event
)
1350 MotionType motion
= motion_state
;
1353 if (motion_buttons_pressed
== 0)
1354 return TRUE
; /* We were disabled */
1356 if (motion_buttons_pressed
== 1)
1357 dnd_motion_ungrab();
1360 motion_buttons_pressed
--;
1364 if (motion
== MOTION_REPOSITION
|| motion
== MOTION_DISABLED
)
1365 return TRUE
; /* Already done something - eat the event */
1367 /* Eat release events that happen too far from the click
1368 * source. Otherwise, allow the caller to treat this as a click
1369 * that never became a motion.
1371 dx
= event
->x_root
- drag_start_x
;
1372 dy
= event
->y_root
- drag_start_y
;
1374 return ABS(dx
) > 5 || ABS(dy
) > 5;
1377 /* Use this to disable the motion system. The system will be reset once
1378 * all mouse buttons are released.
1380 void dnd_motion_disable(void)
1382 g_return_if_fail(motion_state
!= MOTION_NONE
&&
1383 motion_state
!= MOTION_DISABLED
);
1385 motion_state
= MOTION_DISABLED
;
1388 /* Use this if something else is going to grab the pointer so that
1389 * we won't get any more motion or release events.
1391 void dnd_motion_ungrab(void)
1393 if (motion_buttons_pressed
> 0)
1395 if (motion_pointer_grab
)
1397 gdk_pointer_ungrab(GDK_CURRENT_TIME
);
1398 motion_pointer_grab
= FALSE
;
1399 /* g_print("[ ungrab_pointer ]\n"); */
1401 gtk_grab_remove(motion_widget
);
1402 motion_widget
= NULL
;
1403 motion_buttons_pressed
= 0;
1404 /* g_print("[ ungrab ]\n"); */
1407 motion_state
= MOTION_NONE
;
1410 /* Call this on motion events. If the mouse position is far enough
1411 * from the click position, returns TRUE and does dnd_motion_ungrab().
1412 * You should then start regular drag-and-drop.
1414 * Otherwise, returns FALSE.
1416 gboolean
dnd_motion_moved(GdkEventMotion
*event
)
1420 dx
= event
->x_root
- drag_start_x
;
1421 dy
= event
->y_root
- drag_start_y
;
1423 if (ABS(dx
) <= 5 && ABS(dy
) <= 5)
1424 return FALSE
; /* Not far enough */
1426 dnd_motion_ungrab();
1431 /* Normally, the X server will automatically grab the pointer on a
1432 * button press and ungrab on release. However, if the grab widget
1433 * is reparented then call this to re-aquire the grab.
1435 void dnd_motion_grab_pointer(void)
1437 g_return_if_fail(motion_widget
!= NULL
);
1439 gdk_pointer_grab(motion_widget
->window
, FALSE
,
1440 GDK_POINTER_MOTION_MASK
|
1441 GDK_BUTTON_RELEASE_MASK
,
1442 FALSE
, NULL
, GDK_CURRENT_TIME
);
1444 motion_pointer_grab
= TRUE
;