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 "view_iface.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
, const char *text
);
74 static void desktop_drag_data_received(GtkWidget
*widget
,
75 GdkDragContext
*context
,
78 GtkSelectionData
*selection_data
,
81 FilerWindow
*filer_window
);
82 static void got_data_xds_reply(GtkWidget
*widget
,
83 GdkDragContext
*context
,
84 GtkSelectionData
*selection_data
,
86 static void got_data_raw(GtkWidget
*widget
,
87 GdkDragContext
*context
,
88 GtkSelectionData
*selection_data
,
90 static void got_uri_list(GtkWidget
*widget
,
91 GdkDragContext
*context
,
92 GtkSelectionData
*selection_data
,
94 static gboolean
drag_drop(GtkWidget
*widget
,
95 GdkDragContext
*context
,
100 static void drag_data_received(GtkWidget
*widget
,
101 GdkDragContext
*context
,
104 GtkSelectionData
*selection_data
,
108 static gboolean
spring_now(gpointer data
);
109 static void spring_win_destroyed(GtkWidget
*widget
, gpointer data
);
110 static void menuitem_response(gpointer data
, guint action
, GtkWidget
*widget
);
111 static void prompt_action(GList
*paths
, gchar
*dest
);
122 static GtkItemFactoryEntry menu_def
[] = {
123 {N_("Copy"), NULL
, menuitem_response
, MENU_COPY
, NULL
},
124 {N_("Move"), NULL
, menuitem_response
, MENU_MOVE
, NULL
},
125 {N_("Link"), NULL
, menuitem_response
, MENU_LINK
, NULL
},
126 {"", NULL
, NULL
, 0, "<Separator>"},
127 {N_("Set Icon"), NULL
, menuitem_response
, MENU_SET_ICON
, NULL
},
129 static GtkWidget
*dnd_menu
= NULL
;
131 /* Possible values for drop_dest_type (can also be NULL).
132 * In either case, drop_dest_path is the app/file/dir to use.
134 const char *drop_dest_prog
= "drop_dest_prog"; /* Run a program */
135 const char *drop_dest_dir
= "drop_dest_dir"; /* Save to path */
137 GdkAtom XdndDirectSave0
;
138 GdkAtom xa_text_plain
;
139 GdkAtom text_uri_list
;
140 GdkAtom application_octet_stream
;
141 GdkAtom xa_string
; /* Not actually used for DnD, but the others are here! */
143 Option o_dnd_drag_to_icons
;
144 Option o_dnd_spring_open
;
145 static Option o_dnd_spring_delay
;
146 static Option o_dnd_middle_menu
;
150 XdndDirectSave0
= gdk_atom_intern("XdndDirectSave0", FALSE
);
151 xa_text_plain
= gdk_atom_intern("text/plain", FALSE
);
152 text_uri_list
= gdk_atom_intern("text/uri-list", FALSE
);
153 application_octet_stream
= gdk_atom_intern("application/octet-stream",
155 xa_string
= gdk_atom_intern("STRING", FALSE
);
157 option_add_int(&o_dnd_drag_to_icons
, "dnd_drag_to_icons", 1);
158 option_add_int(&o_dnd_spring_open
, "dnd_spring_open", 0);
159 option_add_int(&o_dnd_spring_delay
, "dnd_spring_delay", 400);
160 option_add_int(&o_dnd_middle_menu
, "dnd_middle_menu", TRUE
);
163 /* SUPPORT FUNCTIONS */
165 /* Set the XdndDirectSave0 property on the source window for this context */
166 static void set_xds_prop(GdkDragContext
*context
, const char *text
)
168 gdk_property_change(context
->source_window
,
171 GDK_PROP_MODE_REPLACE
,
176 static char *get_xds_prop(GdkDragContext
*context
)
181 if (gdk_property_get(context
->source_window
,
187 &length
, &prop_text
) && prop_text
)
189 /* Terminate the string */
190 prop_text
= g_realloc(prop_text
, length
+ 1);
191 prop_text
[length
] = '\0';
198 /* Is the sender willing to supply this target type? */
199 gboolean
provides(GdkDragContext
*context
, GdkAtom target
)
201 GList
*targets
= context
->targets
;
203 while (targets
&& ((GdkAtom
) targets
->data
!= target
))
204 targets
= targets
->next
;
206 return targets
!= NULL
;
209 /* Convert a list of URIs into a list of strings.
210 * Lines beginning with # are skipped.
211 * The text block passed in is zero terminated (after the final CRLF)
213 GList
*uri_list_to_glist(const char *uri_list
)
223 linebreak
= strchr(uri_list
, 13);
225 if (!linebreak
|| linebreak
[1] != 10)
227 delayed_error("uri_list_to_glist: %s",
228 _("Incorrect or missing line "
229 "break in text/uri-list data"));
233 length
= linebreak
- uri_list
;
235 if (length
&& uri_list
[0] != '#')
237 uri
= g_strndup(uri_list
, length
);
238 list
= g_list_append(list
, uri
);
241 uri_list
= linebreak
+ 2;
247 /* DRAGGING FROM US */
249 /* The user has held the mouse button down over a group of item and moved -
250 * start a drag. 'uri_list' is copied, so you can delete it straight away.
252 void drag_selection(GtkWidget
*widget
, GdkEventMotion
*event
, guchar
*uri_list
)
255 GdkDragContext
*context
;
256 GdkDragAction actions
;
257 GtkTargetList
*target_list
;
258 GtkTargetEntry target_table
[] = {
259 {"text/uri-list", 0, TARGET_URI_LIST
},
262 if (event
->state
& GDK_BUTTON1_MASK
)
263 actions
= GDK_ACTION_COPY
| GDK_ACTION_MOVE
264 | GDK_ACTION_LINK
| GDK_ACTION_ASK
;
267 if (o_dnd_middle_menu
.int_value
)
268 actions
= GDK_ACTION_ASK
;
270 actions
= GDK_ACTION_MOVE
;
273 target_list
= gtk_target_list_new(target_table
, 1);
275 context
= gtk_drag_begin(widget
,
278 (event
->state
& GDK_BUTTON1_MASK
) ? 1 :
279 (event
->state
& GDK_BUTTON2_MASK
) ? 2 : 3,
282 g_dataset_set_data_full(context
, "uri_list",
283 g_strdup(uri_list
), g_free
);
285 pixbuf
= gtk_widget_render_icon(widget
, GTK_STOCK_DND_MULTIPLE
,
286 GTK_ICON_SIZE_DIALOG
, NULL
);
287 gtk_drag_set_icon_pixbuf(context
, pixbuf
, 0, 0);
288 g_object_unref(pixbuf
);
291 /* Copy/Load this item into another directory/application */
292 void drag_one_item(GtkWidget
*widget
,
293 GdkEventMotion
*event
,
294 const guchar
*full_path
,
299 GdkDragContext
*context
;
300 GdkDragAction actions
;
301 GtkTargetList
*target_list
;
302 GtkTargetEntry target_table
[] = {
303 {"text/uri-list", 0, TARGET_URI_LIST
},
304 {"application/octet-stream", 0, TARGET_RAW
},
308 g_return_if_fail(full_path
!= NULL
);
309 g_return_if_fail(item
!= NULL
);
314 if (item
->base_type
== TYPE_FILE
)
316 MIME_type
*t
= item
->mime_type
;
318 target_table
[2].target
= g_strconcat(t
->media_type
, "/",
320 target_list
= gtk_target_list_new(target_table
, 3);
321 g_free(target_table
[2].target
);
324 target_list
= gtk_target_list_new(target_table
, 1);
326 if (event
->state
& GDK_BUTTON1_MASK
)
327 actions
= GDK_ACTION_COPY
| GDK_ACTION_ASK
328 | GDK_ACTION_MOVE
| GDK_ACTION_LINK
;
331 if (o_dnd_middle_menu
.int_value
)
332 actions
= GDK_ACTION_ASK
;
334 actions
= GDK_ACTION_MOVE
;
337 context
= gtk_drag_begin(widget
,
340 (event
->state
& GDK_BUTTON1_MASK
) ? 1 :
341 (event
->state
& GDK_BUTTON2_MASK
) ? 2 : 3,
344 g_dataset_set_data_full(context
, "full_path",
345 g_strdup(full_path
), g_free
);
346 uri
= g_strconcat("file://", our_host_name_for_dnd(),
347 full_path
, "\r\n", NULL
);
348 g_dataset_set_data_full(context
, "uri_list", uri
, g_free
);
350 g_return_if_fail(image
!= NULL
);
352 gtk_drag_set_icon_pixbuf(context
, image
->pixbuf
, 0, 0);
355 /* Called when a remote app wants us to send it some data.
356 * TODO: Maybe we should handle errors better (ie, let the remote app know
357 * the drag has failed)?
359 void drag_data_get(GtkWidget
*widget
,
360 GdkDragContext
*context
,
361 GtkSelectionData
*selection_data
,
366 char *to_send
= "E"; /* Default to sending an error */
367 long to_send_length
= 1;
368 gboolean delete_once_sent
= FALSE
;
372 type
= gdk_x11_xatom_to_atom(XA_STRING
);
377 path
= g_dataset_get_data(context
, "full_path");
378 if (path
&& load_file(path
, &to_send
, &to_send_length
))
380 delete_once_sent
= TRUE
;
381 type
= selection_data
->target
;
384 g_warning("drag_data_get: Can't find path!\n");
386 case TARGET_URI_LIST
:
387 to_send
= g_dataset_get_data(context
, "uri_list");
388 to_send_length
= strlen(to_send
);
389 type
= text_uri_list
; /* (needed for xine) */
390 delete_once_sent
= FALSE
;
393 delayed_error("drag_data_get: %s",
394 _("Internal error - bad info type"));
398 gtk_selection_data_set(selection_data
,
404 if (delete_once_sent
)
410 /* Set up this widget as a drop-target.
411 * Does not attach any motion handlers.
413 void make_drop_target(GtkWidget
*widget
, GtkDestDefaults defaults
)
415 GtkTargetEntry target_table
[] =
417 {"text/uri-list", 0, TARGET_URI_LIST
},
418 {"XdndDirectSave0", 0, TARGET_XDS
},
419 {"application/octet-stream", 0, TARGET_RAW
},
422 gtk_drag_dest_set(widget
,
425 sizeof(target_table
) / sizeof(*target_table
),
426 GDK_ACTION_COPY
| GDK_ACTION_ASK
| GDK_ACTION_MOVE
427 | GDK_ACTION_LINK
| GDK_ACTION_PRIVATE
);
429 g_signal_connect(widget
, "drag_drop", G_CALLBACK(drag_drop
), NULL
);
430 g_signal_connect(widget
, "drag_data_received",
431 G_CALLBACK(drag_data_received
), NULL
);
434 /* Like drag_set_dest, but for a pinboard-type widget */
435 void drag_set_pinboard_dest(GtkWidget
*widget
)
437 GtkTargetEntry target_table
[] = {
438 {"text/uri-list", 0, TARGET_URI_LIST
},
441 gtk_drag_dest_set(widget
,
442 GTK_DEST_DEFAULT_DROP
,
444 sizeof(target_table
) / sizeof(*target_table
),
446 g_signal_connect(widget
, "drag_data_received",
447 G_CALLBACK(desktop_drag_data_received
), NULL
);
450 /* item is the item the file is held over, NULL for directory background.
451 * 'item' may be NULL on exit if the drop should be treated as onto the
452 * background. Disallow drags to a selected icon before calling this.
454 * Returns NULL to reject the drop, or drop_dest_prog/drop_dest_dir to
455 * accept. Build the path based on item.
457 const guchar
*dnd_motion_item(GdkDragContext
*context
, DirItem
**item_p
)
459 DirItem
*item
= *item_p
;
463 /* If we didn't drop onto a directory, application or
464 * executable file then act as though the drop is to the
467 if (item
->base_type
!= TYPE_DIRECTORY
468 && !(item
->mime_type
== application_executable
))
477 /* Drop onto the window background */
479 return drop_dest_dir
;
482 /* Drop onto a program/directory of some sort */
484 if (item
->base_type
== TYPE_DIRECTORY
&&
485 !(item
->flags
& ITEM_FLAG_APPDIR
))
487 /* A normal directory */
488 if (provides(context
, text_uri_list
) ||
489 provides(context
, XdndDirectSave0
))
490 return drop_dest_dir
;
494 if (provides(context
, text_uri_list
) ||
495 provides(context
, application_octet_stream
))
496 return drop_dest_prog
;
502 /* User has tried to drop some data on us. Decide what format we would
505 static gboolean
drag_drop(GtkWidget
*widget
,
506 GdkDragContext
*context
,
512 const char *error
= NULL
;
513 char *leafname
= NULL
;
514 GdkAtom target
= GDK_NONE
;
516 char *dest_type
= NULL
;
518 dest_path
= g_dataset_get_data(context
, "drop_dest_path");
519 dest_type
= g_dataset_get_data(context
, "drop_dest_type");
521 g_return_val_if_fail(dest_path
!= NULL
, TRUE
);
523 if (dest_type
== drop_dest_dir
&& provides(context
, XdndDirectSave0
))
525 leafname
= get_xds_prop(context
);
528 if (strchr(leafname
, '/'))
530 error
= _("XDS protocol error: "
531 "leafname may not contain '/'\n");
532 null_g_free(&leafname
);
538 uri
= g_string_new(NULL
);
539 g_string_printf(uri
, "file://%s%s",
540 our_host_name_for_dnd(),
543 set_xds_prop(context
, uri
->str
);
544 g_string_free(uri
, TRUE
);
546 target
= XdndDirectSave0
;
547 g_dataset_set_data_full(context
, "leafname",
553 "XdndDirectSave0 target provided, but the atom "
554 "XdndDirectSave0 (type text/plain) did not "
555 "contain a leafname\n");
557 else if (provides(context
, text_uri_list
))
558 target
= text_uri_list
;
559 else if (provides(context
, application_octet_stream
))
560 target
= application_octet_stream
;
563 if (dest_type
== drop_dest_dir
)
564 error
= _("Sorry - I require a target type of "
565 "text/uri-list or XdndDirectSave0.");
567 error
= _("Sorry - I require a target type of "
568 "text/uri-list or application/octet-stream.");
573 gtk_drag_finish(context
, FALSE
, FALSE
, time
); /* Failure */
575 delayed_error("%s", error
);
578 gtk_drag_get_data(widget
, context
, target
, time
);
583 /* Called when a text/uri-list arrives */
584 static void desktop_drag_data_received(GtkWidget
*widget
,
585 GdkDragContext
*context
,
588 GtkSelectionData
*selection_data
,
591 FilerWindow
*filer_window
)
596 if (!selection_data
->data
)
602 if (pinboard_drag_in_progress
)
604 pinboard_move_icons();
608 gdk_window_get_position(widget
->window
, &dx
, &dy
);
612 uris
= uri_list_to_glist(selection_data
->data
);
614 for (next
= uris
; next
; next
= next
->next
)
618 path
= get_local_path((gchar
*) next
->data
);
621 pinboard_pin(path
, NULL
, x
, y
, NULL
);
632 /* Called when some data arrives from the remote app (which we asked for
635 static void drag_data_received(GtkWidget
*widget
,
636 GdkDragContext
*context
,
639 GtkSelectionData
*selection_data
,
644 if (!selection_data
->data
)
647 gtk_drag_finish(context
, FALSE
, FALSE
, time
); /* Failure */
654 got_data_xds_reply(widget
, context
,
655 selection_data
, time
);
658 got_data_raw(widget
, context
, selection_data
, time
);
660 case TARGET_URI_LIST
:
661 got_uri_list(widget
, context
, selection_data
, time
);
664 gtk_drag_finish(context
, FALSE
, FALSE
, time
);
665 delayed_error("drag_data_received: %s",
666 _("Unknown target"));
671 static void got_data_xds_reply(GtkWidget
*widget
,
672 GdkDragContext
*context
,
673 GtkSelectionData
*selection_data
,
676 gboolean mark_unsafe
= TRUE
;
677 char response
= *selection_data
->data
;
678 const char *error
= NULL
;
681 dest_path
= g_dataset_get_data(context
, "drop_dest_path");
683 if (selection_data
->length
!= 1)
688 /* Sender couldn't save there - ask for another
691 if (provides(context
, application_octet_stream
))
693 mark_unsafe
= FALSE
; /* Wait and see */
695 gtk_drag_get_data(widget
, context
,
696 application_octet_stream
, time
);
699 error
= _("Remote app can't or won't send me "
702 else if (response
== 'S')
704 /* Success - data is saved */
705 mark_unsafe
= FALSE
; /* It really is safe */
706 gtk_drag_finish(context
, TRUE
, FALSE
, time
);
708 refresh_dirs(dest_path
);
710 else if (response
!= 'E')
712 error
= _("XDS protocol error: "
713 "return code should be 'S', 'F' or 'E'\n");
715 /* else: error has been reported by the sender */
719 set_xds_prop(context
, "");
720 /* Unsave also implies that the drag failed */
721 gtk_drag_finish(context
, FALSE
, FALSE
, time
);
725 delayed_error("%s", error
);
728 static void got_data_raw(GtkWidget
*widget
,
729 GdkDragContext
*context
,
730 GtkSelectionData
*selection_data
,
733 const char *leafname
;
735 const char *error
= NULL
;
736 const char *dest_path
;
738 g_return_if_fail(selection_data
->data
!= NULL
);
740 dest_path
= g_dataset_get_data(context
, "drop_dest_path");
742 if (context
->action
== GDK_ACTION_ASK
)
744 gtk_drag_finish(context
, FALSE
, FALSE
, time
); /* Failure */
745 delayed_error(_("Sorry, can't display a menu of actions "
746 "for a remote file / raw data."));
750 if (g_dataset_get_data(context
, "drop_dest_type") == drop_dest_prog
)
752 /* The data needs to be sent to an application */
753 run_with_data(dest_path
,
754 selection_data
->data
, selection_data
->length
);
755 gtk_drag_finish(context
, TRUE
, FALSE
, time
); /* Success! */
759 leafname
= g_dataset_get_data(context
, "leafname");
761 leafname
= _("UntitledData");
763 fd
= open(make_path(dest_path
, leafname
)->str
,
764 O_WRONLY
| O_CREAT
| O_EXCL
| O_NOCTTY
,
765 S_IRUSR
| S_IRGRP
| S_IROTH
|
766 S_IWUSR
| S_IWGRP
| S_IWOTH
);
769 error
= g_strerror(errno
);
773 selection_data
->data
,
774 selection_data
->length
) == -1)
775 error
= g_strerror(errno
);
777 if (close(fd
) == -1 && !error
)
778 error
= g_strerror(errno
);
780 refresh_dirs(dest_path
);
785 if (provides(context
, XdndDirectSave0
))
786 set_xds_prop(context
, "");
787 gtk_drag_finish(context
, FALSE
, FALSE
, time
); /* Failure */
788 delayed_error(_("Error saving file: %s"), error
);
791 gtk_drag_finish(context
, TRUE
, FALSE
, time
); /* Success! */
794 /* We've got a list of URIs from somewhere (probably another filer window).
795 * If the files are on the local machine then try to copy them ourselves,
796 * otherwise, if there was only one file and application/octet-stream was
797 * provided, get the data via the X server.
799 static void got_uri_list(GtkWidget
*widget
,
800 GdkDragContext
*context
,
801 GtkSelectionData
*selection_data
,
805 const char *error
= NULL
;
807 gboolean send_reply
= TRUE
;
811 dest_path
= g_dataset_get_data(context
, "drop_dest_path");
812 type
= g_dataset_get_data(context
, "drop_dest_type");
814 g_return_if_fail(dest_path
!= NULL
);
816 uri_list
= uri_list_to_glist(selection_data
->data
);
819 error
= _("No URIs in the text/uri-list (nothing to do!)");
820 else if (context
->action
!= GDK_ACTION_ASK
&& type
== drop_dest_prog
)
821 run_with_files(dest_path
, uri_list
);
822 else if ((!uri_list
->next
) && (!get_local_path(uri_list
->data
)))
824 /* There is one URI in the list, and it's not on the local
825 * machine. Get it via the X server if possible.
828 if (provides(context
, application_octet_stream
))
831 leaf
= strrchr(uri_list
->data
, '/');
835 leaf
= uri_list
->data
;
836 g_dataset_set_data_full(context
, "leafname",
837 g_strdup(leaf
), g_free
);
838 gtk_drag_get_data(widget
, context
,
839 application_octet_stream
, time
);
843 error
= _("Can't get data from remote machine "
844 "(application/octet-stream not provided)");
848 GList
*local_paths
= NULL
;
850 /* Either one local URI, or a list. If everything in the list
851 * isn't local then we are stuck.
854 for (next_uri
= uri_list
; next_uri
; next_uri
= next_uri
->next
)
858 path
= get_local_path((char *) next_uri
->data
);
861 local_paths
= g_list_append(local_paths
,
864 error
= _("Some of these files are on a "
865 "different machine - they will be "
871 error
= _("None of these files are on the local "
872 "machine - I can't operate on multiple "
873 "remote files - sorry.");
875 else if (context
->action
== GDK_ACTION_ASK
)
876 prompt_action(local_paths
, dest_path
);
877 else if (context
->action
== GDK_ACTION_MOVE
)
878 action_move(local_paths
, dest_path
, NULL
, -1);
879 else if (context
->action
== GDK_ACTION_COPY
)
880 action_copy(local_paths
, dest_path
, NULL
, -1);
881 else if (context
->action
== GDK_ACTION_LINK
)
882 action_link(local_paths
, dest_path
, NULL
);
884 error
= _("Unknown action requested");
886 destroy_glist(&local_paths
);
891 gtk_drag_finish(context
, FALSE
, FALSE
, time
); /* Failure */
892 delayed_error(_("Error getting file list: %s"), error
);
895 gtk_drag_finish(context
, TRUE
, FALSE
, time
); /* Success! */
897 destroy_glist(&uri_list
);
900 /* Called when an item from the ACTION_ASK menu is chosen */
901 static void menuitem_response(gpointer data
, guint action
, GtkWidget
*widget
)
903 if (action
== MENU_MOVE
)
904 action_move(prompt_local_paths
, prompt_dest_path
, NULL
, -1);
905 else if (action
== MENU_COPY
)
906 action_copy(prompt_local_paths
, prompt_dest_path
, NULL
, -1);
907 else if (action
== MENU_LINK
)
908 action_link(prompt_local_paths
, prompt_dest_path
, NULL
);
909 else if (action
== MENU_SET_ICON
)
911 if (g_list_length(prompt_local_paths
) == 1)
912 set_icon_path(prompt_dest_path
,
913 (char*) prompt_local_paths
->data
);
916 _("You can't use multiple files with Set Icon!"));
920 /* When some local files are dropped somewhere with ACTION_ASK, this
921 * function is called to display the menu.
923 static void prompt_action(GList
*paths
, gchar
*dest
)
927 if (prompt_local_paths
)
929 destroy_glist(&prompt_local_paths
);
930 null_g_free(&prompt_dest_path
);
933 /* Make a copy of the arguments */
934 for (next
= paths
; next
; next
= next
->next
)
935 prompt_local_paths
= g_list_append(prompt_local_paths
,
936 g_strdup((gchar
*) next
->data
));
937 prompt_dest_path
= g_strdup(dest
);
941 GtkItemFactory
*item_factory
;
943 item_factory
= menu_create(menu_def
,
944 sizeof(menu_def
) / sizeof(*menu_def
),
946 dnd_menu
= gtk_item_factory_get_widget(item_factory
, "<dnd>");
949 /* Shade 'Set Icon' if there are multiple files */
950 menu_set_items_shaded(dnd_menu
, g_list_length(paths
) != 1, 4, 1);
952 show_popup_menu(dnd_menu
, gtk_get_current_event(), 1);
958 /* This is the code that makes directories pop open if you hold a
961 * First, call dnd_spring_load(context) to arm the system.
962 * After a timeout (1/2 a second) the dest_path directory will be
963 * opened in a new window, unless dnd_spring_abort is called first.
966 static gint spring_timeout
= -1;
967 static GdkDragContext
*spring_context
= NULL
;
968 static FilerWindow
*spring_window
= NULL
;
969 static FilerWindow
*spring_src_window
= NULL
;
971 void dnd_spring_load(GdkDragContext
*context
, FilerWindow
*src_win
)
973 g_return_if_fail(context
!= NULL
);
975 if (!o_dnd_spring_open
.int_value
)
981 spring_context
= context
;
982 g_object_ref(spring_context
);
983 spring_src_window
= src_win
;
984 spring_timeout
= gtk_timeout_add(
985 o_dnd_spring_delay
.int_value
, spring_now
, NULL
);
988 void dnd_spring_abort(void)
993 g_object_unref(spring_context
);
994 spring_context
= NULL
;
995 gtk_timeout_remove(spring_timeout
);
998 /* If all mod keys are released, no buttons are pressed, and the
999 * mouse is outside the spring window, then close it.
1001 static gboolean
spring_check_idle(gpointer data
)
1008 if (!get_pointer_xy(&p_x
, &p_y
))
1011 GdkWindow *win = spring_window->window->window;
1015 gdk_window_get_position(win, &x, &y);
1016 gdk_window_get_size(win, &w, &h);
1018 if (p_x < x || p_x > x + w || p_y < y || p_y > y + h)
1022 gtk_widget_destroy(spring_window
->window
);
1023 return FALSE
; /* Got it! */
1026 return TRUE
; /* Try again later */
1029 static gboolean
spring_now(gpointer data
)
1031 gboolean old_unique
= o_unique_filer_windows
.int_value
;
1035 g_return_val_if_fail(spring_context
!= NULL
, FALSE
);
1037 dest_path
= g_dataset_get_data(spring_context
, "drop_dest_path");
1038 g_return_val_if_fail(dest_path
!= NULL
, FALSE
);
1041 * Note: Due to a bug in gtk, if a window disappears during
1042 * a drag and the pointer moves over where the window was,
1043 * the sender crashes! Therefore, do not close any windows
1044 * while dragging! (fixed in later versions)
1048 gtk_widget_destroy(spring_window->window);
1051 get_pointer_xy(&x
, &y
);
1053 o_unique_filer_windows
.int_value
= FALSE
; /* XXX: yuck! */
1056 view_cursor_to_iter(spring_window
->view
, NULL
);
1057 filer_change_to(spring_window
, dest_path
, NULL
);
1058 /* DON'T move the window. Gtk+ sometimes doesn't
1064 spring_window
= filer_opendir(dest_path
, spring_src_window
, NULL
);
1067 gtk_timeout_add(500, spring_check_idle
, NULL
);
1068 g_signal_connect(spring_window
->window
, "destroy",
1069 G_CALLBACK(spring_win_destroyed
), NULL
);
1070 centre_window(spring_window
->window
->window
, x
, y
);
1073 o_unique_filer_windows
.int_value
= old_unique
;
1080 static void spring_win_destroyed(GtkWidget
*widget
, gpointer data
)
1082 spring_window
= NULL
;
1085 /* HANDLING MOTION EVENTS */
1087 /* If not-NULL, then this widget has a grab */
1088 static GtkWidget
*motion_widget
= NULL
;
1090 /* If TRUE, we must gdk_pointer_ungrab() too when finishing */
1091 static gboolean motion_pointer_grab
= FALSE
;
1093 /* Call this on a button press event. It stores the mouse position
1094 * as the start of the new drag and returns TRUE if all is well.
1095 * Further motions events are disabled at this point - you must
1096 * then call dnd_motion_start() to set the type of motion expected.
1097 * Grabs the widget on the first press.
1099 * If the system is not ready to handle a motion event (because a
1100 * button is already held down?) it does nothing and returns FALSE.
1102 * If the event is not a single click then it simply returns TRUE.
1104 gboolean
dnd_motion_press(GtkWidget
*widget
, GdkEventButton
*event
)
1106 if (event
->type
!= GDK_BUTTON_PRESS
)
1107 return TRUE
; /* Not a click event! */
1109 motion_buttons_pressed
++;
1110 if (motion_buttons_pressed
== 1)
1112 /* g_print("[ grab! ]\n"); */
1113 gtk_grab_add(widget
);
1114 motion_widget
= widget
;
1117 if (motion_state
!= MOTION_NONE
)
1118 return FALSE
; /* Ignore clicks - we're busy! */
1120 motion_state
= MOTION_DISABLED
;
1121 drag_start_x
= event
->x_root
;
1122 drag_start_y
= event
->y_root
;
1127 /* After the button press event, decide what kind of motion is expected.
1128 * If you don't call this then the motion system is disabled - call
1129 * dnd_motion_release() to reset it.
1131 * Note: If you open a popup menu or start DND call dnd_motion_ungrab()
1134 void dnd_motion_start(MotionType motion
)
1136 g_return_if_fail(motion_state
== MOTION_DISABLED
);
1138 motion_state
= motion
;
1141 /* Call this on a button release event. If some buttons are still pressed,
1142 * returns TRUE and does nothing.
1144 * Otherwise, it resets the motion system to be ready again and returns TRUE.
1146 * If the motion system wasn't being used (MOTION_NONE) then it does nothing
1147 * and returns FALSE - process the release event yourself as it isn't part
1148 * of a motion. This also happens if a motion was primed but never happened.
1150 gboolean
dnd_motion_release(GdkEventButton
*event
)
1152 MotionType motion
= motion_state
;
1155 if (motion_buttons_pressed
== 0)
1156 return TRUE
; /* We were disabled */
1158 if (motion_buttons_pressed
== 1)
1159 dnd_motion_ungrab();
1162 motion_buttons_pressed
--;
1166 if (motion
== MOTION_REPOSITION
|| motion
== MOTION_DISABLED
)
1167 return TRUE
; /* Already done something - eat the event */
1169 /* Eat release events that happen too far from the click
1170 * source. Otherwise, allow the caller to treat this as a click
1171 * that never became a motion.
1173 dx
= event
->x_root
- drag_start_x
;
1174 dy
= event
->y_root
- drag_start_y
;
1176 return ABS(dx
) > 5 || ABS(dy
) > 5;
1179 /* Use this to disable the motion system. The system will be reset once
1180 * all mouse buttons are released.
1182 void dnd_motion_disable(void)
1184 g_return_if_fail(motion_state
!= MOTION_NONE
&&
1185 motion_state
!= MOTION_DISABLED
);
1187 motion_state
= MOTION_DISABLED
;
1190 /* Use this if something else is going to grab the pointer so that
1191 * we won't get any more motion or release events.
1193 void dnd_motion_ungrab(void)
1195 if (motion_buttons_pressed
> 0)
1197 if (motion_pointer_grab
)
1199 gdk_pointer_ungrab(GDK_CURRENT_TIME
);
1200 motion_pointer_grab
= FALSE
;
1201 /* g_print("[ ungrab_pointer ]\n"); */
1203 gtk_grab_remove(motion_widget
);
1204 motion_widget
= NULL
;
1205 motion_buttons_pressed
= 0;
1206 /* g_print("[ ungrab ]\n"); */
1209 motion_state
= MOTION_NONE
;
1212 /* Call this on motion events. If the mouse position is far enough
1213 * from the click position, returns TRUE and does dnd_motion_ungrab().
1214 * You should then start regular drag-and-drop.
1216 * Otherwise, returns FALSE.
1218 gboolean
dnd_motion_moved(GdkEventMotion
*event
)
1222 dx
= event
->x_root
- drag_start_x
;
1223 dy
= event
->y_root
- drag_start_y
;
1225 if (ABS(dx
) <= 5 && ABS(dy
) <= 5)
1226 return FALSE
; /* Not far enough */
1228 dnd_motion_ungrab();
1233 /* Normally, the X server will automatically grab the pointer on a
1234 * button press and ungrab on release. However, if the grab widget
1235 * is reparented then call this to re-aquire the grab.
1237 void dnd_motion_grab_pointer(void)
1239 g_return_if_fail(motion_widget
!= NULL
);
1241 gdk_pointer_grab(motion_widget
->window
, FALSE
,
1242 GDK_POINTER_MOTION_MASK
|
1243 GDK_BUTTON_RELEASE_MASK
,
1244 FALSE
, NULL
, GDK_CURRENT_TIME
);
1246 motion_pointer_grab
= TRUE
;