ru.po: Corrections from Evgeny Bulgakov <bgav@netvision.net.il>
[midnight-commander.git] / gnome / gdnd.c
blobdab81c49e85e642cdd31c66d134707d6b1352ced
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>
7 */
9 #include <config.h>
10 #include <sys/stat.h>
11 #include <sys/types.h>
12 #include "global.h"
13 #include "file.h"
14 #include "fileopctx.h"
15 #include "main.h"
16 #include "panel.h"
17 #include "ext.h"
18 #include "gscreen.h"
19 #include "../vfs/vfs.h"
20 #include <gdk/gdkprivate.h>
21 #include "gdesktop.h"
22 #include "gdnd.h"
25 /* Atoms for the DnD target types */
26 GdkAtom dnd_target_atoms[TARGET_NTARGETS];
29 /**
30 * gdnd_init:
32 * Initializes the dnd_target_atoms array by interning the DnD target atom names.
33 **/
34 void
35 gdnd_init (void)
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),
57 GNOMEUIINFO_END
60 /* Pops up a menu of actions to perform on dropped files */
61 static GdkDragAction
62 get_action (GdkDragContext *context)
64 GtkWidget *menu;
65 int a;
66 GdkDragAction action;
68 /* Create the menu and set the sensitivity of the items based on the
69 * allowed actions.
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);
80 switch (a) {
81 case 0:
82 action = GDK_ACTION_MOVE;
83 break;
85 case 1:
86 action = GDK_ACTION_COPY;
87 break;
89 case 2:
90 action = GDK_ACTION_LINK;
91 break;
93 default:
94 action = GDK_ACTION_ASK; /* Magic value to indicate cancellation */
97 gtk_widget_destroy (menu);
99 return action;
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
104 * directory.
106 static void
107 perform_action_on_panel (WPanel *source_panel, GdkDragAction action, char *destdir)
109 switch (action) {
110 case GDK_ACTION_COPY:
111 panel_operate (source_panel, OP_COPY, destdir, FALSE);
112 break;
114 case GDK_ACTION_MOVE:
115 panel_operate (source_panel, OP_MOVE, destdir, FALSE);
116 break;
118 default:
119 g_assert_not_reached ();
122 /* Finish up */
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.
134 static void
135 perform_links (GList *names, char *destdir)
137 char *name;
138 char *dest_name;
140 for (; names; names = names->next) {
141 name = names->data;
142 if (strncmp (name, "file:", 5) == 0)
143 name += 5;
145 dest_name = g_concat_dir_and_file (destdir, x_basename (name));
146 mc_symlink (name, dest_name);
147 g_free (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
154 * links.
156 static void
157 perform_action (GList *names, GdkDragAction action, char *destdir)
159 struct stat s;
160 char *name;
161 char *dest_name;
162 int result;
163 FileOpContext *ctx;
165 ctx = file_op_context_new ();
167 switch (action) {
168 case GDK_ACTION_COPY:
169 file_op_context_create_ui (ctx, OP_COPY, FALSE);
170 break;
172 case GDK_ACTION_MOVE:
173 file_op_context_create_ui (ctx, OP_MOVE, FALSE);
174 break;
176 default:
177 g_assert_not_reached ();
180 for (; names; names = names->next) {
181 name = names->data;
182 if (strncmp (name, "file:", 5) == 0)
183 name += 5;
185 dest_name = g_concat_dir_and_file (destdir, x_basename (name));
187 do {
188 result = mc_lstat (name, &s);
190 if (result != 0) {
191 /* FIXME: this error message sucks */
192 if (file_error (_("Could not stat %s\n%s"), dest_name) != FILE_RETRY)
193 result = 0;
194 } else {
195 long count = 0;
196 double bytes = 0;
198 if (S_ISDIR (s.st_mode)) {
199 if (action == GDK_ACTION_COPY)
200 copy_dir_dir (ctx,
201 name, dest_name,
202 TRUE, FALSE,
203 FALSE, FALSE,
204 &count, &bytes);
205 else
206 move_dir_dir (ctx,
207 name, dest_name,
208 &count, &bytes);
209 } else {
210 if (action == GDK_ACTION_COPY)
211 copy_file_file (ctx,
212 name, dest_name,
213 TRUE,
214 &count, &bytes, 1);
215 else
216 move_file_file (ctx,
217 name, dest_name,
218 &count, &bytes);
221 } while (result != 0);
223 g_free (dest_name);
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'.
232 static void
233 drop_uri_list_on_directory (GdkDragContext *context, GtkSelectionData *selection_data,
234 GdkDragAction action, char *destdir)
236 WPanel *source_panel;
237 GtkWidget *source_widget;
238 GList *names;
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
243 * from a tree.
245 if (source_panel && source_widget != source_panel->tree && action != GDK_ACTION_LINK)
246 perform_action_on_panel (source_panel, action, destdir);
247 else {
248 names = gnome_uri_list_extract_uris (selection_data->data);
250 if (action == GDK_ACTION_LINK)
251 perform_links (names, destdir);
252 else
253 perform_action (names, action, destdir);
255 gnome_uri_list_free_strings (names);
259 /* Drop a Netscape URL in a directory */
260 static void
261 drop_url_on_directory (GdkDragContext *context, GtkSelectionData *selection_data, char *destdir)
263 char *template;
265 template = g_concat_dir_and_file (destdir, "urlXXXXXX");
267 if (mktemp (template)) {
268 char *icon;
270 icon = g_concat_dir_and_file (ICONDIR, "gnome-http-url.png");
271 desktop_create_url (
272 template,
273 selection_data->data,
274 selection_data->data,
275 icon);
276 g_free (icon);
278 g_free (template);
281 /* Drop stuff on a directory */
282 static int
283 drop_on_directory (GdkDragContext *context, GtkSelectionData *selection_data,
284 GdkDragAction action, char *directory)
286 int retval;
288 retval = FALSE;
290 if (gdnd_drag_context_has_target (context, TARGET_URI_LIST)) {
291 drop_uri_list_on_directory (context, selection_data, action, directory);
292 retval = TRUE;
293 } else if (gdnd_drag_context_has_target (context, TARGET_URL)) {
294 drop_url_on_directory (context, selection_data, directory);
295 retval = TRUE;
298 return retval;
301 /* Returns whether a file has the drop-action metadata or MIME property defined
302 * for it.
304 static int
305 file_has_drop_action (char *filename)
307 char *buf;
308 int size;
309 const char *mime_type;
311 if (gnome_metadata_get (filename, "drop-action", &size, &buf) == 0) {
312 g_free (buf);
313 return TRUE;
314 } else {
315 mime_type = gnome_mime_type_or_default (filename, NULL);
316 if (!mime_type)
317 return FALSE;
319 if (gnome_mime_get_value (mime_type, "drop-action") != NULL)
320 return TRUE;
321 else
322 return FALSE;
326 /* Drop stuff on a non-directory file. This uses metadata and MIME as well. */
327 static int
328 drop_on_file (GdkDragContext *context, GtkSelectionData *selection_data,
329 char *dest_full_name, file_entry *dest_fe)
331 int size;
332 char *buf;
333 const char *mime_type;
334 int retval;
335 GList *names, *l;
336 int len, i;
337 char **drops;
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)
351 text += 5;
353 drops[i] = text;
355 drops[i] = NULL;
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);
361 g_free (buf);
362 retval = TRUE;
363 goto out;
366 /* 2. Try a drop action from the MIME-type */
368 mime_type = gnome_mime_type_or_default (dest_full_name, NULL);
369 if (mime_type) {
370 const char *action;
372 action = gnome_mime_get_value (mime_type, "drop-action");
373 if (action) {
374 exec_extension (dest_full_name, action, drops, NULL, 0, 0);
375 retval = TRUE;
376 goto out;
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);
387 else
388 exec_extension (dest_full_name, "%f %q", drops, NULL, 0, 0);
390 g_free (buf);
392 retval = TRUE;
393 goto out;
396 out:
397 g_free (drops);
398 gnome_uri_list_free_strings (names);
399 return retval;
403 * gdnd_perform_drop:
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);
424 /* Get action */
426 if (context->action == GDK_ACTION_ASK) {
427 action = get_action (context);
428 if (action == GDK_ACTION_ASK)
429 return FALSE;
430 } else
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);
435 else
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
447 * otherwise.
450 gdnd_drag_context_has_target (GdkDragContext *context, TargetType type)
452 GList *l;
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))
458 return TRUE;
460 return FALSE;
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
469 * drag context.
471 * Return value: The sought panel, or NULL if no panel corresponds to the
472 * context.
474 WPanel *
475 gdnd_find_panel_by_drag_context (GdkDragContext *context, GtkWidget **source_widget)
477 GtkWidget *source;
478 GtkWidget *toplevel;
479 GList *l;
480 WPanel *panel;
482 g_return_val_if_fail (context != NULL, NULL);
484 source = gtk_drag_get_source_widget (context);
486 if (source_widget)
487 *source_widget = source;
489 if (!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)
498 return panel;
501 return NULL;
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().
519 GdkDragAction
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)
524 int on_directory;
525 int on_exe;
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.
533 if (on_desktop
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 */
540 if (dest_fe) {
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)) {
546 if (dest_fe) {
547 if (same_source && dest_selected)
548 return 0;
550 if (on_directory) {
551 if ((same_source || same_process)
552 && (context->actions & GDK_ACTION_MOVE)
553 && context->suggested_action != GDK_ACTION_ASK)
554 return GDK_ACTION_MOVE;
555 else
556 return context->suggested_action;
557 } else if (on_exe) {
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)
564 return 0;
565 else if (same_process
566 && (context->actions & GDK_ACTION_MOVE)
567 && context->suggested_action != GDK_ACTION_ASK)
568 return GDK_ACTION_MOVE;
569 else
570 return context->suggested_action;
571 } else {
572 if (same_source)
573 return 0;
574 else if (same_process
575 && (context->actions & GDK_ACTION_MOVE)
576 && context->suggested_action != GDK_ACTION_ASK)
577 return GDK_ACTION_MOVE;
578 else
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).
588 if (dest_fe) {
589 if (on_directory) {
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;
598 return 0;
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))
618 return TRUE;
619 else
620 return FALSE;