/* Drag and Drop functionality for the Midnight Commander * * Copyright (C) 1998 The Free Software Foundation * * Authors: Federico Mena * Miguel de Icaza */ #include #include #include #include "file.h" #include "fileopctx.h" #include "main.h" #include "panel.h" #include "gscreen.h" #include "../vfs/vfs.h" #include #include "gdnd.h" /* Atoms for the DnD target types */ GdkAtom dnd_target_atoms[TARGET_NTARGETS]; /** * gdnd_init: * * Initializes the dnd_target_atoms array by interning the DnD target atom names. **/ void gdnd_init (void) { dnd_target_atoms[TARGET_MC_DESKTOP_ICON] = gdk_atom_intern (TARGET_MC_DESKTOP_ICON_TYPE, FALSE); dnd_target_atoms[TARGET_URI_LIST] = gdk_atom_intern (TARGET_URI_LIST_TYPE, FALSE); dnd_target_atoms[TARGET_TEXT_PLAIN] = gdk_atom_intern (TARGET_TEXT_PLAIN_TYPE, FALSE); dnd_target_atoms[TARGET_URL] = gdk_atom_intern (TARGET_URL_TYPE, FALSE); } /* The menu of DnD actions */ static GnomeUIInfo actions[] = { GNOMEUIINFO_ITEM_NONE (N_("_Move here"), NULL, NULL), GNOMEUIINFO_ITEM_NONE (N_("_Copy here"), NULL, NULL), GNOMEUIINFO_ITEM_NONE (N_("_Link here"), NULL, NULL), GNOMEUIINFO_SEPARATOR, GNOMEUIINFO_ITEM_NONE (N_("Cancel drag"), NULL, NULL), GNOMEUIINFO_END }; /* Pops up a menu of actions to perform on dropped files */ static GdkDragAction get_action (GdkDragContext *context) { GtkWidget *menu; int a; GdkDragAction action; /* Create the menu and set the sensitivity of the items based on the * allowed actions. */ menu = gnome_popup_menu_new (actions); gtk_widget_set_sensitive (actions[0].widget, (context->actions & GDK_ACTION_MOVE) != 0); gtk_widget_set_sensitive (actions[1].widget, (context->actions & GDK_ACTION_COPY) != 0); gtk_widget_set_sensitive (actions[2].widget, (context->actions & GDK_ACTION_LINK) != 0); a = gnome_popup_menu_do_popup_modal (menu, NULL, NULL, NULL, NULL); switch (a) { case 0: action = GDK_ACTION_MOVE; break; case 1: action = GDK_ACTION_COPY; break; case 2: action = GDK_ACTION_LINK; break; default: action = GDK_ACTION_ASK; /* Magic value to indicate cancellation */ } gtk_widget_destroy (menu); return action; } /* * Looks for a panel that has the specified window for its list * display. It is used to figure out if we are receiving a drop from * a panel on this MC process. If no panel is found, it returns NULL. */ static WPanel * find_panel_owning_window (GdkDragContext *context) { GList *list; WPanel *panel; GtkWidget *source_widget, *toplevel_widget; source_widget = gtk_drag_get_source_widget (context); if (!source_widget) return NULL; /* * We will scan the list of existing WPanels. We * uniformize the thing by pulling the toplevel * widget for each WPanel and compare this to the * toplevel source_widget */ toplevel_widget = gtk_widget_get_toplevel (source_widget); for (list = containers; list; list = list->next) { GtkWidget *panel_toplevel_widget; panel = ((PanelContainer *) list->data)->panel; panel_toplevel_widget = panel->xwindow; if (panel->xwindow == toplevel_widget){ /* * Now a WPanel actually contains a number of * drag sources. If the drag source is the * Tree, we must report that it was not the * contents of the WPanel */ if (source_widget == panel->tree) return NULL; return panel; } } return NULL; } /* * Performs a drop action on the specified panel. Only supports copy * and move operations. The files are moved or copied to the * specified destination directory. */ static void perform_action_on_panel (WPanel *source_panel, GdkDragAction action, char *destdir, int ask) { switch (action) { case GDK_ACTION_COPY: panel_operate (source_panel, OP_COPY, destdir, ask); break; case GDK_ACTION_MOVE: panel_operate (source_panel, OP_MOVE, destdir, ask); break; default: g_assert_not_reached (); } /* Finish up */ update_one_panel_widget (source_panel, FALSE, UP_KEEPSEL); if (action == GDK_ACTION_MOVE) panel_update_contents (source_panel); } /* * Performs handling of symlinks via drag and drop. This should go * away when operation windows support links. */ static void perform_links (GList *names, char *destdir) { char *name; char *dest_name; for (; names; names = names->next) { name = names->data; if (strncmp (name, "file:", 5) == 0) name += 5; dest_name = g_concat_dir_and_file (destdir, x_basename (name)); mc_symlink (name, dest_name); g_free (dest_name); } } /* * Performs a drop action manually, by going through the list of files * to operate on. The files are copied or moved to the specified * directory. This should also encompass symlinking when the file * operations window supports links. */ static void perform_action (GList *names, GdkDragAction action, char *destdir) { struct stat s; char *name; char *dest_name; int result; FileOpContext *ctx; ctx = file_op_context_new (); switch (action) { case GDK_ACTION_COPY: file_op_context_create_ui (ctx, OP_COPY, FALSE); break; case GDK_ACTION_MOVE: file_op_context_create_ui (ctx, OP_MOVE, FALSE); break; default: g_assert_not_reached (); } for (; names; names = names->next) { name = names->data; if (strncmp (name, "file:", 5) == 0) name += 5; dest_name = g_concat_dir_and_file (destdir, x_basename (name)); do { result = mc_stat (name, &s); if (result != 0) { /* FIXME: this error message sucks */ if (file_error (_("Could not stat %s\n%s"), dest_name) != FILE_RETRY) result = 0; } else { long count = 0; double bytes = 0; if (S_ISDIR (s.st_mode)) { if (action == GDK_ACTION_COPY) copy_dir_dir (ctx, name, dest_name, TRUE, FALSE, FALSE, FALSE, &count, &bytes); else move_dir_dir (ctx, name, dest_name, &count, &bytes); } else { if (action == GDK_ACTION_COPY) copy_file_file (ctx, name, dest_name, TRUE, &count, &bytes, 1); else move_file_file (ctx, name, dest_name, &count, &bytes); } } } while (result != 0); g_free (dest_name); break; } file_op_context_destroy (ctx); } /** * gdnd_drop_on_directory: * @context: The drag context received from the drag_data_received callback * @selection_data: The selection data from the drag_data_received callback * @dirname: The name of the directory to drop onto * * Extracts an URI list from the selection data and drops all the files in the * specified directory. * * Return Value: TRUE if the drop was sucessful, FALSE if it was not. **/ int gdnd_drop_on_directory (GdkDragContext *context, GtkSelectionData *selection_data, char *destdir) { GdkDragAction action; WPanel *source_panel; GList *names; if (context->action == GDK_ACTION_ASK) { action = get_action (context); if (action == GDK_ACTION_ASK) return FALSE; } else action = context->action; /* If we are dragging from a file panel, we can display a nicer status display */ source_panel = find_panel_owning_window (context); /* Check if the user did not drag the information to the same directory */ if (source_panel) { if (strcmp (source_panel->cwd, destdir) == 0) return FALSE; } /* Symlinks do not use file.c */ if (source_panel && action != GDK_ACTION_LINK) perform_action_on_panel (source_panel, action, destdir, context->action == GDK_ACTION_ASK); else { names = gnome_uri_list_extract_uris (selection_data->data); if (action == GDK_ACTION_LINK) perform_links (names, destdir); else perform_action (names, action, destdir); gnome_uri_list_free_strings (names); } return TRUE; } /** * gdnd_drag_context_has_target: * @context: The context to query for a target type * @type: The sought target type * * Tests whether the specified drag context has a target of the specified type. * * Return value: TRUE if the context has the specified target type, FALSE * otherwise. **/ int gdnd_drag_context_has_target (GdkDragContext *context, TargetType type) { GList *l; g_return_val_if_fail (context != NULL, FALSE); for (l = context->targets; l; l = l->next) if (dnd_target_atoms[type] == GPOINTER_TO_INT (l->data)) return TRUE; return FALSE; }