1 /* Drag and Drop functionality for the Midnight Commander
3 * Copyright (C) 1998 The Free Software Foundation
5 * Authors: Federico Mena <federico@nuclecu.unam.mx>
6 * Miguel de Icaza <miguel@nuclecu.unam.mx>
11 #include <sys/types.h>
14 #include "fileopctx.h"
19 #include "../vfs/vfs.h"
20 #include <gdk/gdkprivate.h>
25 /* Atoms for the DnD target types */
26 GdkAtom dnd_target_atoms
[TARGET_NTARGETS
];
32 * Initializes the dnd_target_atoms array by interning the DnD target atom names.
37 dnd_target_atoms
[TARGET_MC_DESKTOP_ICON
] =
38 gdk_atom_intern (TARGET_MC_DESKTOP_ICON_TYPE
, FALSE
);
40 dnd_target_atoms
[TARGET_URI_LIST
] =
41 gdk_atom_intern (TARGET_URI_LIST_TYPE
, FALSE
);
43 dnd_target_atoms
[TARGET_TEXT_PLAIN
] =
44 gdk_atom_intern (TARGET_TEXT_PLAIN_TYPE
, FALSE
);
46 dnd_target_atoms
[TARGET_URL
] =
47 gdk_atom_intern (TARGET_URL_TYPE
, FALSE
);
50 /* The menu of DnD actions */
51 static GnomeUIInfo actions
[] = {
52 GNOMEUIINFO_ITEM_NONE (N_("_Move here"), NULL
, NULL
),
53 GNOMEUIINFO_ITEM_NONE (N_("_Copy here"), NULL
, NULL
),
54 GNOMEUIINFO_ITEM_NONE (N_("_Link here"), NULL
, NULL
),
55 GNOMEUIINFO_SEPARATOR
,
56 GNOMEUIINFO_ITEM_NONE (N_("Cancel drag"), NULL
, NULL
),
60 /* Pops up a menu of actions to perform on dropped files */
62 get_action (GdkDragContext
*context
)
68 /* Create the menu and set the sensitivity of the items based on the
72 menu
= gnome_popup_menu_new (actions
);
74 gtk_widget_set_sensitive (actions
[0].widget
, (context
->actions
& GDK_ACTION_MOVE
) != 0);
75 gtk_widget_set_sensitive (actions
[1].widget
, (context
->actions
& GDK_ACTION_COPY
) != 0);
76 gtk_widget_set_sensitive (actions
[2].widget
, (context
->actions
& GDK_ACTION_LINK
) != 0);
78 a
= gnome_popup_menu_do_popup_modal (menu
, NULL
, NULL
, NULL
, NULL
);
82 action
= GDK_ACTION_MOVE
;
86 action
= GDK_ACTION_COPY
;
90 action
= GDK_ACTION_LINK
;
94 action
= GDK_ACTION_ASK
; /* Magic value to indicate cancellation */
97 gtk_widget_destroy (menu
);
102 /* Performs a drop action on the specified panel. Only supports copy and move
103 * operations. The files are moved or copied to the specified destination
107 perform_action_on_panel (WPanel
*source_panel
, GdkDragAction action
, char *destdir
)
110 case GDK_ACTION_COPY
:
111 panel_operate (source_panel
, OP_COPY
, destdir
, FALSE
);
114 case GDK_ACTION_MOVE
:
115 panel_operate (source_panel
, OP_MOVE
, destdir
, FALSE
);
119 g_assert_not_reached ();
124 update_one_panel_widget (source_panel
, FALSE
, UP_KEEPSEL
);
126 if (action
== GDK_ACTION_MOVE
)
127 panel_update_contents (source_panel
);
131 * Performs handling of symlinks via drag and drop. This should go
132 * away when operation windows support links.
135 perform_links (GList
*names
, char *destdir
)
140 for (; names
; names
= names
->next
) {
142 if (strncmp (name
, "file:", 5) == 0)
145 dest_name
= g_concat_dir_and_file (destdir
, x_basename (name
));
146 mc_symlink (name
, dest_name
);
151 /* Performs a drop action manually, by going through the list of files to
152 * operate on. The files are copied or moved to the specified directory. This
153 * should also encompass symlinking when the file operations window supports
157 perform_action (GList
*names
, GdkDragAction action
, char *destdir
)
165 ctx
= file_op_context_new ();
168 case GDK_ACTION_COPY
:
169 file_op_context_create_ui (ctx
, OP_COPY
, FALSE
);
172 case GDK_ACTION_MOVE
:
173 file_op_context_create_ui (ctx
, OP_MOVE
, FALSE
);
177 g_assert_not_reached ();
180 for (; names
; names
= names
->next
) {
182 if (strncmp (name
, "file:", 5) == 0)
185 dest_name
= g_concat_dir_and_file (destdir
, x_basename (name
));
188 result
= mc_lstat (name
, &s
);
191 /* FIXME: this error message sucks */
192 if (file_error (_("Could not stat %s\n%s"), dest_name
) != FILE_RETRY
)
198 if (S_ISDIR (s
.st_mode
)) {
199 if (action
== GDK_ACTION_COPY
)
210 if (action
== GDK_ACTION_COPY
)
221 } while (result
!= 0);
226 file_op_context_destroy (ctx
);
229 /* Drop a URI list on a directory. If the data comes from a panel, use the nice
230 * MC progress display; otherwise `do it by hand'.
233 drop_uri_list_on_directory (GdkDragContext
*context
, GtkSelectionData
*selection_data
,
234 GdkDragAction action
, char *destdir
)
236 WPanel
*source_panel
;
237 GtkWidget
*source_widget
;
240 source_panel
= gdnd_find_panel_by_drag_context (context
, &source_widget
);
242 /* We cannot use file.c if we are going to symlink or if we are dragging
245 if (source_panel
&& source_widget
!= source_panel
->tree
&& action
!= GDK_ACTION_LINK
)
246 perform_action_on_panel (source_panel
, action
, destdir
);
248 names
= gnome_uri_list_extract_uris (selection_data
->data
);
250 if (action
== GDK_ACTION_LINK
)
251 perform_links (names
, destdir
);
253 perform_action (names
, action
, destdir
);
255 gnome_uri_list_free_strings (names
);
259 /* Drop a Netscape URL in a directory */
261 drop_url_on_directory (GdkDragContext
*context
, GtkSelectionData
*selection_data
, char *destdir
)
265 template = g_concat_dir_and_file (destdir
, "urlXXXXXX");
267 if (mktemp (template)) {
270 icon
= g_concat_dir_and_file (ICONDIR
, "gnome-http-url.png");
273 selection_data
->data
,
274 selection_data
->data
,
281 /* Drop stuff on a directory */
283 drop_on_directory (GdkDragContext
*context
, GtkSelectionData
*selection_data
,
284 GdkDragAction action
, char *directory
)
290 if (gdnd_drag_context_has_target (context
, TARGET_URI_LIST
)) {
291 drop_uri_list_on_directory (context
, selection_data
, action
, directory
);
293 } else if (gdnd_drag_context_has_target (context
, TARGET_URL
)) {
294 drop_url_on_directory (context
, selection_data
, directory
);
301 /* Returns whether a file has the drop-action metadata or MIME property defined
305 file_has_drop_action (char *filename
)
309 const char *mime_type
;
311 if (gnome_metadata_get (filename
, "drop-action", &size
, &buf
) == 0) {
315 mime_type
= gnome_mime_type_or_default (filename
, NULL
);
319 if (gnome_mime_get_value (mime_type
, "drop-action") != NULL
)
326 /* Drop stuff on a non-directory file. This uses metadata and MIME as well. */
328 drop_on_file (GdkDragContext
*context
, GtkSelectionData
*selection_data
,
329 char *dest_full_name
, file_entry
*dest_fe
)
333 const char *mime_type
;
339 retval
= FALSE
; /* assume we cannot drop */
341 /* Convert the data list into an array of strings */
343 names
= gnome_uri_list_extract_uris (selection_data
->data
);
344 len
= g_list_length (names
);
345 drops
= g_new (char *, len
+ 1);
347 for (l
= names
, i
= 0; i
< len
; i
++, l
= l
->next
) {
348 char *text
= l
->data
;
350 if (strncmp (text
, "file:", 5) == 0)
357 /* 1. Try to use a metadata-based drop action */
359 if (gnome_metadata_get (dest_full_name
, "drop-action", &size
, &buf
) == 0) {
360 exec_extension (dest_full_name
, buf
, drops
, NULL
, 0, 0);
366 /* 2. Try a drop action from the MIME-type */
368 mime_type
= gnome_mime_type_or_default (dest_full_name
, NULL
);
372 action
= gnome_mime_get_value (mime_type
, "drop-action");
374 exec_extension (dest_full_name
, action
, drops
, NULL
, 0, 0);
380 /* 3. If executable, try metadata keys for "open" */
382 if (is_exe (dest_fe
->buf
.st_mode
) && if_link_is_exe (dest_full_name
, dest_fe
)) {
383 /* FIXME: handle the case for Netscape URLs */
385 if (gnome_metadata_get (dest_full_name
, "open", &size
, &buf
) == 0)
386 exec_extension (dest_full_name
, buf
, drops
, NULL
, 0, 0);
388 exec_extension (dest_full_name
, "%f %q", drops
, NULL
, 0, 0);
398 gnome_uri_list_free_strings (names
);
404 * @context: Drag context for operation.
405 * @selection_data: Selection data from drag_data_received.
406 * @dest_full_name: Complete name of the destination file or directory.
407 * @dest_fe: File entry for the destination file or directory.
409 * Performs a drop operation on a directory or file.
411 * Return value: TRUE if the drop is successful, FALSE otherwise.
414 gdnd_perform_drop (GdkDragContext
*context
, GtkSelectionData
*selection_data
,
415 char *dest_full_name
, file_entry
*dest_fe
)
417 GdkDragAction action
;
419 g_return_val_if_fail (context
!= NULL
, FALSE
);
420 g_return_val_if_fail (selection_data
!= NULL
, FALSE
);
421 g_return_val_if_fail (dest_full_name
!= NULL
, FALSE
);
422 g_return_val_if_fail (dest_fe
!= NULL
, FALSE
);
426 if (context
->action
== GDK_ACTION_ASK
) {
427 action
= get_action (context
);
428 if (action
== GDK_ACTION_ASK
)
431 action
= context
->action
;
433 if (S_ISDIR (dest_fe
->buf
.st_mode
) || dest_fe
->f
.link_to_dir
)
434 return drop_on_directory (context
, selection_data
, action
, dest_full_name
);
436 return drop_on_file (context
, selection_data
, dest_full_name
, dest_fe
);
440 * gdnd_drag_context_has_target:
441 * @context: The context to query for a target type
442 * @type: The sought target type
444 * Tests whether the specified drag context has a target of the specified type.
446 * Return value: TRUE if the context has the specified target type, FALSE
450 gdnd_drag_context_has_target (GdkDragContext
*context
, TargetType type
)
454 g_return_val_if_fail (context
!= NULL
, FALSE
);
456 for (l
= context
->targets
; l
; l
= l
->next
)
457 if (dnd_target_atoms
[type
] == GPOINTER_TO_INT (l
->data
))
464 * gdnd_find_panel_by_drag_context:
465 * @context: The context by which to find a panel.
466 * @source_widget: The source widget is returned here.
468 * Looks in the list of panels for the one that corresponds to the specified
471 * Return value: The sought panel, or NULL if no panel corresponds to the
475 gdnd_find_panel_by_drag_context (GdkDragContext
*context
, GtkWidget
**source_widget
)
482 g_return_val_if_fail (context
!= NULL
, NULL
);
484 source
= gtk_drag_get_source_widget (context
);
487 *source_widget
= source
;
490 return NULL
; /* different process */
492 toplevel
= gtk_widget_get_toplevel (source
);
494 for (l
= containers
; l
; l
= l
->next
) {
495 panel
= ((PanelContainer
*) l
->data
)->panel
;
497 if (panel
->xwindow
== toplevel
)
505 * gdnd_validate_action:
506 * @context: The drag context for this drag operation.
507 * @on_desktop: Whether we are dragging onto the desktop or a desktop icon.
508 * @same_process: Whether the drag comes from the same process or not.
509 * @same_source: If same_process, then whether the source and dest widgets are the same.
510 * @dest_full_name: Complete name of the destination file or directory.
511 * @dest_fe: File entry for the destination file, or NULL if directory.
512 * @dest_selected: If dest is non-NULL, whether it is selected or not.
514 * Computes the final drag action based on the suggested action of the specified
515 * context and conditions.
517 * Return value: The computed action, meant to be passed to gdk_drag_action().
520 gdnd_validate_action (GdkDragContext
*context
,
521 int on_desktop
, int same_process
, int same_source
,
522 char *dest_full_name
, file_entry
*dest_fe
, int dest_selected
)
527 g_return_val_if_fail (context
!= NULL
, 0);
528 g_return_val_if_fail (dest_full_name
!= NULL
, 0);
530 /* If we are dragging a desktop icon onto the desktop or onto a selected
531 * desktop icon, unconditionally specify MOVE.
534 && gdnd_drag_context_has_target (context
, TARGET_MC_DESKTOP_ICON
)
535 && (!dest_fe
|| dest_selected
))
536 return GDK_ACTION_MOVE
;
538 /* See what kind of file the destination is, if any */
541 on_directory
= S_ISDIR (dest_fe
->buf
.st_mode
) || dest_fe
->f
.link_to_dir
;
542 on_exe
= is_exe (dest_fe
->buf
.st_mode
) && if_link_is_exe (dest_full_name
, dest_fe
);
545 if (gdnd_drag_context_has_target (context
, TARGET_URI_LIST
)) {
547 if (same_source
&& dest_selected
)
551 if ((same_source
|| same_process
)
552 && (context
->actions
& GDK_ACTION_MOVE
)
553 && context
->suggested_action
!= GDK_ACTION_ASK
)
554 return GDK_ACTION_MOVE
;
556 return context
->suggested_action
;
558 if (context
->actions
& GDK_ACTION_COPY
)
559 return GDK_ACTION_COPY
;
560 } else if (file_has_drop_action (dest_full_name
)) {
561 if (context
->actions
& GDK_ACTION_COPY
)
562 return GDK_ACTION_COPY
;
563 } else if (same_source
)
565 else if (same_process
566 && (context
->actions
& GDK_ACTION_MOVE
)
567 && context
->suggested_action
!= GDK_ACTION_ASK
)
568 return GDK_ACTION_MOVE
;
570 return context
->suggested_action
;
574 else if (same_process
575 && (context
->actions
& GDK_ACTION_MOVE
)
576 && context
->suggested_action
!= GDK_ACTION_ASK
)
577 return GDK_ACTION_MOVE
;
579 return context
->suggested_action
;
583 if (gdnd_drag_context_has_target (context
, TARGET_URL
)) {
584 /* FIXME: right now we only allow linking to directories. We
585 * should see if we can move or copy stuff instead (for ftp
586 * instead of http sites, for example).
590 if (context
->actions
& GDK_ACTION_LINK
)
591 return GDK_ACTION_LINK
;
592 } else if (context
->actions
& GDK_ACTION_COPY
)
593 return GDK_ACTION_COPY
;
594 } else if (context
->actions
& GDK_ACTION_LINK
)
595 return GDK_ACTION_LINK
;
602 * gdnd_can_drop_on_file:
603 * @full_name: Complete name of the file.
604 * @fe: File entry for the file.
606 * Computes whether a non-directory file can take drops.
608 * Return value: TRUE if the file can take drops, FALSE otherwise.
611 gdnd_can_drop_on_file (char *full_name
, file_entry
*fe
)
613 g_return_val_if_fail (full_name
!= NULL
, FALSE
);
614 g_return_val_if_fail (fe
!= NULL
, FALSE
);
616 if ((is_exe (fe
->buf
.st_mode
) && if_link_is_exe (full_name
, fe
))
617 || file_has_drop_action (full_name
))