diff options
Diffstat (limited to 'libcaja-private/caja-dnd.c')
-rw-r--r-- | libcaja-private/caja-dnd.c | 1446 |
1 files changed, 1446 insertions, 0 deletions
diff --git a/libcaja-private/caja-dnd.c b/libcaja-private/caja-dnd.c new file mode 100644 index 00000000..9b794956 --- /dev/null +++ b/libcaja-private/caja-dnd.c @@ -0,0 +1,1446 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* caja-dnd.c - Common Drag & drop handling code shared by the icon container + and the list view. + + Copyright (C) 2000, 2001 Eazel, Inc. + + The Mate Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Mate Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Mate Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + Authors: Pavel Cisler <[email protected]>, + Ettore Perazzoli <[email protected]> +*/ + +/* FIXME: This should really be back in Caja, not here in Eel. */ + +#include <config.h> +#include "caja-dnd.h" + +#include "caja-program-choosing.h" +#include "caja-link.h" +#include "caja-window-slot-info.h" +#include "caja-window-info.h" +#include "caja-view.h" +#include <eel/eel-glib-extensions.h> +#include <eel/eel-gtk-extensions.h> +#include <eel/eel-string.h> +#include <eel/eel-vfs-extensions.h> +#include <gtk/gtk.h> +#include <glib/gi18n.h> +#include <libcaja-private/caja-file-utilities.h> +#include <stdio.h> +#include <string.h> + +/* a set of defines stolen from the eel-icon-dnd.c file. + * These are in microseconds. + */ +#define AUTOSCROLL_TIMEOUT_INTERVAL 100 +#define AUTOSCROLL_INITIAL_DELAY 100000 + +/* drag this close to the view edge to start auto scroll*/ +#define AUTO_SCROLL_MARGIN 30 + +/* the smallest amount of auto scroll used when we just enter the autoscroll + * margin + */ +#define MIN_AUTOSCROLL_DELTA 5 + +/* the largest amount of auto scroll used when we are right over the view + * edge + */ +#define MAX_AUTOSCROLL_DELTA 50 + +void +caja_drag_init (CajaDragInfo *drag_info, + const GtkTargetEntry *drag_types, + int drag_type_count, + gboolean add_text_targets) +{ + drag_info->target_list = gtk_target_list_new (drag_types, + drag_type_count); + + if (add_text_targets) + { + gtk_target_list_add_text_targets (drag_info->target_list, + CAJA_ICON_DND_TEXT); + } + + drag_info->drop_occured = FALSE; + drag_info->need_to_destroy = FALSE; +} + +void +caja_drag_finalize (CajaDragInfo *drag_info) +{ + gtk_target_list_unref (drag_info->target_list); + caja_drag_destroy_selection_list (drag_info->selection_list); + + g_free (drag_info); +} + + +/* Functions to deal with CajaDragSelectionItems. */ + +CajaDragSelectionItem * +caja_drag_selection_item_new (void) +{ + return g_new0 (CajaDragSelectionItem, 1); +} + +static void +drag_selection_item_destroy (CajaDragSelectionItem *item) +{ + g_free (item->uri); + g_free (item); +} + +void +caja_drag_destroy_selection_list (GList *list) +{ + GList *p; + + if (list == NULL) + return; + + for (p = list; p != NULL; p = p->next) + drag_selection_item_destroy (p->data); + + g_list_free (list); +} + +char ** +caja_drag_uri_array_from_selection_list (const GList *selection_list) +{ + GList *uri_list; + char **uris; + + uri_list = caja_drag_uri_list_from_selection_list (selection_list); + uris = caja_drag_uri_array_from_list (uri_list); + eel_g_list_free_deep (uri_list); + + return uris; +} + +GList * +caja_drag_uri_list_from_selection_list (const GList *selection_list) +{ + CajaDragSelectionItem *selection_item; + GList *uri_list; + const GList *l; + + uri_list = NULL; + for (l = selection_list; l != NULL; l = l->next) + { + selection_item = (CajaDragSelectionItem *) l->data; + if (selection_item->uri != NULL) + { + uri_list = g_list_prepend (uri_list, g_strdup (selection_item->uri)); + } + } + + return g_list_reverse (uri_list); +} + +char ** +caja_drag_uri_array_from_list (const GList *uri_list) +{ + const GList *l; + char **uris; + int i; + + if (uri_list == NULL) + { + return NULL; + } + + uris = g_new0 (char *, g_list_length ((GList *) uri_list)); + for (i = 0, l = uri_list; l != NULL; l = l->next) + { + uris[i++] = g_strdup ((char *) l->data); + } + uris[i] = NULL; + + return uris; +} + +GList * +caja_drag_uri_list_from_array (const char **uris) +{ + GList *uri_list; + int i; + + if (uris == NULL) + { + return NULL; + } + + uri_list = NULL; + + for (i = 0; uris[i] != NULL; i++) + { + uri_list = g_list_prepend (uri_list, g_strdup (uris[i])); + } + + return g_list_reverse (uri_list); +} + +GList * +caja_drag_build_selection_list (GtkSelectionData *data) +{ + GList *result; + const guchar *p, *oldp; + int size; + + result = NULL; + oldp = gtk_selection_data_get_data (data); + size = gtk_selection_data_get_length (data); + + while (size > 0) + { + CajaDragSelectionItem *item; + guint len; + + /* The list is in the form: + + name\rx:y:width:height\r\n + + The geometry information after the first \r is optional. */ + + /* 1: Decode name. */ + + p = memchr (oldp, '\r', size); + if (p == NULL) + { + break; + } + + item = caja_drag_selection_item_new (); + + len = p - oldp; + + item->uri = g_malloc (len + 1); + memcpy (item->uri, oldp, len); + item->uri[len] = 0; + + p++; + if (*p == '\n' || *p == '\0') + { + result = g_list_prepend (result, item); + if (p == 0) + { + g_warning ("Invalid x-special/mate-icon-list data received: " + "missing newline character."); + break; + } + else + { + oldp = p + 1; + continue; + } + } + + size -= p - oldp; + oldp = p; + + /* 2: Decode geometry information. */ + + item->got_icon_position = sscanf (p, "%d:%d:%d:%d%*s", + &item->icon_x, &item->icon_y, + &item->icon_width, &item->icon_height) == 4; + if (!item->got_icon_position) + { + g_warning ("Invalid x-special/mate-icon-list data received: " + "invalid icon position specification."); + } + + result = g_list_prepend (result, item); + + p = memchr (p, '\r', size); + if (p == NULL || p[1] != '\n') + { + g_warning ("Invalid x-special/mate-icon-list data received: " + "missing newline character."); + if (p == NULL) + { + break; + } + } + else + { + p += 2; + } + + size -= p - oldp; + oldp = p; + } + + return g_list_reverse (result); +} + +static gboolean +caja_drag_file_local_internal (const char *target_uri_string, + const char *first_source_uri) +{ + /* check if the first item on the list has target_uri_string as a parent + * FIXME: + * we should really test each item but that would be slow for large selections + * and currently dropped items can only be from the same container + */ + GFile *target, *item, *parent; + gboolean result; + + result = FALSE; + + target = g_file_new_for_uri (target_uri_string); + + /* get the parent URI of the first item in the selection */ + item = g_file_new_for_uri (first_source_uri); + parent = g_file_get_parent (item); + g_object_unref (item); + + if (parent != NULL) + { + result = g_file_equal (parent, target); + g_object_unref (parent); + } + + g_object_unref (target); + + return result; +} + +gboolean +caja_drag_uris_local (const char *target_uri, + const GList *source_uri_list) +{ + /* must have at least one item */ + g_assert (source_uri_list); + + return caja_drag_file_local_internal (target_uri, source_uri_list->data); +} + +gboolean +caja_drag_items_local (const char *target_uri_string, + const GList *selection_list) +{ + /* must have at least one item */ + g_assert (selection_list); + + return caja_drag_file_local_internal (target_uri_string, + ((CajaDragSelectionItem *)selection_list->data)->uri); +} + +gboolean +caja_drag_items_in_trash (const GList *selection_list) +{ + /* check if the first item on the list is in trash. + * FIXME: + * we should really test each item but that would be slow for large selections + * and currently dropped items can only be from the same container + */ + return eel_uri_is_trash (((CajaDragSelectionItem *)selection_list->data)->uri); +} + +gboolean +caja_drag_items_on_desktop (const GList *selection_list) +{ + char *uri; + GFile *desktop, *item, *parent; + gboolean result; + + /* check if the first item on the list is in trash. + * FIXME: + * we should really test each item but that would be slow for large selections + * and currently dropped items can only be from the same container + */ + uri = ((CajaDragSelectionItem *)selection_list->data)->uri; + if (eel_uri_is_desktop (uri)) + { + return TRUE; + } + + desktop = caja_get_desktop_location (); + + item = g_file_new_for_uri (uri); + parent = g_file_get_parent (item); + g_object_unref (item); + + result = FALSE; + + if (parent) + { + result = g_file_equal (desktop, parent); + g_object_unref (parent); + } + g_object_unref (desktop); + + return result; + +} + +GdkDragAction +caja_drag_default_drop_action_for_netscape_url (GdkDragContext *context) +{ + /* Mozilla defaults to copy, but unless thats the + only allowed thing (enforced by ctrl) we want to ASK */ + if (gdk_drag_context_get_suggested_action (context) == GDK_ACTION_COPY && + gdk_drag_context_get_actions (context) != GDK_ACTION_COPY) + { + return GDK_ACTION_ASK; + } + else if (gdk_drag_context_get_suggested_action (context) == GDK_ACTION_MOVE) + { + /* Don't support move */ + return GDK_ACTION_COPY; + } + + return gdk_drag_context_get_suggested_action (context); +} + +static gboolean +check_same_fs (CajaFile *file1, + CajaFile *file2) +{ + char *id1, *id2; + gboolean result; + + result = FALSE; + + if (file1 != NULL && file2 != NULL) + { + id1 = caja_file_get_filesystem_id (file1); + id2 = caja_file_get_filesystem_id (file2); + + if (id1 != NULL && id2 != NULL) + { + result = (strcmp (id1, id2) == 0); + } + + g_free (id1); + g_free (id2); + } + + return result; +} + +static gboolean +source_is_deletable (GFile *file) +{ + CajaFile *naut_file; + gboolean ret; + + /* if there's no a cached CajaFile, it returns NULL */ + naut_file = caja_file_get_existing (file); + if (naut_file == NULL) + { + return FALSE; + } + + ret = caja_file_can_delete (naut_file); + caja_file_unref (naut_file); + + return ret; +} + +void +caja_drag_default_drop_action_for_icons (GdkDragContext *context, + const char *target_uri_string, const GList *items, + int *action) +{ + gboolean same_fs; + gboolean target_is_source_parent; + gboolean source_deletable; + const char *dropped_uri; + GFile *target, *dropped, *dropped_directory; + GdkDragAction actions; + CajaFile *dropped_file, *target_file; + + if (target_uri_string == NULL) + { + *action = 0; + return; + } + + actions = gdk_drag_context_get_actions (context) & (GDK_ACTION_MOVE | GDK_ACTION_COPY); + if (actions == 0) + { + /* We can't use copy or move, just go with the suggested action. */ + *action = gdk_drag_context_get_suggested_action (context); + return; + } + + if (gdk_drag_context_get_suggested_action (context) == GDK_ACTION_ASK) + { + /* Don't override ask */ + *action = gdk_drag_context_get_suggested_action (context); + return; + } + + dropped_uri = ((CajaDragSelectionItem *)items->data)->uri; + dropped_file = caja_file_get_existing_by_uri (dropped_uri); + target_file = caja_file_get_existing_by_uri (target_uri_string); + + /* + * Check for trash URI. We do a find_directory for any Trash directory. + * Passing 0 permissions as mate-vfs would override the permissions + * passed with 700 while creating .Trash directory + */ + if (eel_uri_is_trash (target_uri_string)) + { + /* Only move to Trash */ + if (actions & GDK_ACTION_MOVE) + { + *action = GDK_ACTION_MOVE; + } + + caja_file_unref (dropped_file); + caja_file_unref (target_file); + return; + + } + else if (dropped_file != NULL && caja_file_is_launcher (dropped_file)) + { + if (actions & GDK_ACTION_MOVE) + { + *action = GDK_ACTION_MOVE; + } + caja_file_unref (dropped_file); + caja_file_unref (target_file); + return; + } + else if (eel_uri_is_desktop (target_uri_string)) + { + target = caja_get_desktop_location (); + + caja_file_unref (target_file); + target_file = caja_file_get (target); + + if (eel_uri_is_desktop (dropped_uri)) + { + /* Only move to Desktop icons */ + if (actions & GDK_ACTION_MOVE) + { + *action = GDK_ACTION_MOVE; + } + + g_object_unref (target); + caja_file_unref (dropped_file); + caja_file_unref (target_file); + return; + } + } + else if (target_file != NULL && caja_file_is_archive (target_file)) + { + *action = GDK_ACTION_COPY; + + caja_file_unref (dropped_file); + caja_file_unref (target_file); + return; + } + else + { + target = g_file_new_for_uri (target_uri_string); + } + + same_fs = check_same_fs (target_file, dropped_file); + + caja_file_unref (dropped_file); + caja_file_unref (target_file); + + /* Compare the first dropped uri with the target uri for same fs match. */ + dropped = g_file_new_for_uri (dropped_uri); + dropped_directory = g_file_get_parent (dropped); + target_is_source_parent = FALSE; + if (dropped_directory != NULL) + { + /* If the dropped file is already in the same directory but + is in another filesystem we still want to move, not copy + as this is then just a move of a mountpoint to another + position in the dir */ + target_is_source_parent = g_file_equal (dropped_directory, target); + g_object_unref (dropped_directory); + } + source_deletable = source_is_deletable (dropped); + + if ((same_fs && source_deletable) || target_is_source_parent || + g_file_has_uri_scheme (dropped, "trash")) + { + if (actions & GDK_ACTION_MOVE) + { + *action = GDK_ACTION_MOVE; + } + else + { + *action = gdk_drag_context_get_suggested_action (context); + } + } + else + { + if (actions & GDK_ACTION_COPY) + { + *action = GDK_ACTION_COPY; + } + else + { + *action = gdk_drag_context_get_suggested_action (context); + } + } + + g_object_unref (target); + g_object_unref (dropped); + +} + +GdkDragAction +caja_drag_default_drop_action_for_uri_list (GdkDragContext *context, + const char *target_uri_string) +{ + if (eel_uri_is_trash (target_uri_string) && (gdk_drag_context_get_actions (context) & GDK_ACTION_MOVE)) + { + /* Only move to Trash */ + return GDK_ACTION_MOVE; + } + else + { + return gdk_drag_context_get_suggested_action (context); + } +} + +/* Encode a "x-special/mate-icon-list" selection. + Along with the URIs of the dragged files, this encodes + the location and size of each icon relative to the cursor. +*/ +static void +add_one_mate_icon (const char *uri, int x, int y, int w, int h, + gpointer data) +{ + GString *result; + + result = (GString *) data; + + g_string_append_printf (result, "%s\r%d:%d:%hu:%hu\r\n", + uri, x, y, w, h); +} + +/* + * Cf. #48423 + */ +#ifdef THIS_WAS_REALLY_BROKEN +static gboolean +is_path_that_mate_uri_list_extract_filenames_can_parse (const char *path) +{ + if (path == NULL || path [0] == '\0') + { + return FALSE; + } + + /* It strips leading and trailing spaces. So it can't handle + * file names with leading and trailing spaces. + */ + if (g_ascii_isspace (path [0])) + { + return FALSE; + } + if (g_ascii_isspace (path [strlen (path) - 1])) + { + return FALSE; + } + + /* # works as a comment delimiter, and \r and \n are used to + * separate the lines, so it can't handle file names with any + * of these. + */ + if (strchr (path, '#') != NULL + || strchr (path, '\r') != NULL + || strchr (path, '\n') != NULL) + { + return FALSE; + } + + return TRUE; +} + +/* Encode a "text/plain" selection; this is a broken URL -- just + * "file:" with a path after it (no escaping or anything). We are + * trying to make the old mate_uri_list_extract_filenames function + * happy, so this is coded to its idiosyncrasises. + */ +static void +add_one_compatible_uri (const char *uri, int x, int y, int w, int h, gpointer data) +{ + GString *result; + char *local_path; + + result = (GString *) data; + + /* For URLs that do not have a file: scheme, there's no harm + * in passing the real URL. But for URLs that do have a file: + * scheme, we have to send a URL that will work with the old + * mate-libs function or nothing will be able to understand + * it. + */ + if (!eel_istr_has_prefix (uri, "file:")) + { + g_string_append (result, uri); + g_string_append (result, "\r\n"); + } + else + { + local_path = g_filename_from_uri (uri, NULL, NULL); + + /* Check for characters that confuse the old + * mate_uri_list_extract_filenames implementation, and just leave + * out any paths with those in them. + */ + if (is_path_that_mate_uri_list_extract_filenames_can_parse (local_path)) + { + g_string_append (result, "file:"); + g_string_append (result, local_path); + g_string_append (result, "\r\n"); + } + + g_free (local_path); + } +} +#endif + +static void +add_one_uri (const char *uri, int x, int y, int w, int h, gpointer data) +{ + GString *result; + + result = (GString *) data; + + g_string_append (result, uri); + g_string_append (result, "\r\n"); +} + +/* Common function for drag_data_get_callback calls. + * Returns FALSE if it doesn't handle drag data */ +gboolean +caja_drag_drag_data_get (GtkWidget *widget, + GdkDragContext *context, + GtkSelectionData *selection_data, + guint info, + guint32 time, + gpointer container_context, + CajaDragEachSelectedItemIterator each_selected_item_iterator) +{ + GString *result; + + switch (info) + { + case CAJA_ICON_DND_MATE_ICON_LIST: + result = g_string_new (NULL); + (* each_selected_item_iterator) (add_one_mate_icon, container_context, result); + break; + + case CAJA_ICON_DND_URI_LIST: + case CAJA_ICON_DND_TEXT: + result = g_string_new (NULL); + (* each_selected_item_iterator) (add_one_uri, container_context, result); + break; + + default: + return FALSE; + } + + gtk_selection_data_set (selection_data, + gtk_selection_data_get_target (selection_data), + 8, result->str, result->len); + g_string_free (result, TRUE); + + return TRUE; +} + +typedef struct +{ + GMainLoop *loop; + GdkDragAction chosen; +} DropActionMenuData; + +static void +menu_deactivate_callback (GtkWidget *menu, + gpointer data) +{ + DropActionMenuData *damd; + + damd = data; + + if (g_main_loop_is_running (damd->loop)) + g_main_loop_quit (damd->loop); +} + +static void +drop_action_activated_callback (GtkWidget *menu_item, + gpointer data) +{ + DropActionMenuData *damd; + + damd = data; + + damd->chosen = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (menu_item), + "action")); + + if (g_main_loop_is_running (damd->loop)) + g_main_loop_quit (damd->loop); +} + +static void +append_drop_action_menu_item (GtkWidget *menu, + const char *text, + GdkDragAction action, + gboolean sensitive, + DropActionMenuData *damd) +{ + GtkWidget *menu_item; + + menu_item = gtk_menu_item_new_with_mnemonic (text); + gtk_widget_set_sensitive (menu_item, sensitive); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item); + + g_object_set_data (G_OBJECT (menu_item), + "action", + GINT_TO_POINTER (action)); + + g_signal_connect (menu_item, "activate", + G_CALLBACK (drop_action_activated_callback), + damd); + + gtk_widget_show (menu_item); +} + +/* Pops up a menu of actions to perform on dropped files */ +GdkDragAction +caja_drag_drop_action_ask (GtkWidget *widget, + GdkDragAction actions) +{ + GtkWidget *menu; + GtkWidget *menu_item; + DropActionMenuData damd; + + /* Create the menu and set the sensitivity of the items based on the + * allowed actions. + */ + menu = gtk_menu_new (); + gtk_menu_set_screen (GTK_MENU (menu), gtk_widget_get_screen (widget)); + + append_drop_action_menu_item (menu, _("_Move Here"), + GDK_ACTION_MOVE, + (actions & GDK_ACTION_MOVE) != 0, + &damd); + + append_drop_action_menu_item (menu, _("_Copy Here"), + GDK_ACTION_COPY, + (actions & GDK_ACTION_COPY) != 0, + &damd); + + append_drop_action_menu_item (menu, _("_Link Here"), + GDK_ACTION_LINK, + (actions & GDK_ACTION_LINK) != 0, + &damd); + + append_drop_action_menu_item (menu, _("Set as _Background"), + CAJA_DND_ACTION_SET_AS_BACKGROUND, + (actions & CAJA_DND_ACTION_SET_AS_BACKGROUND) != 0, + &damd); + + eel_gtk_menu_append_separator (GTK_MENU (menu)); + + menu_item = gtk_menu_item_new_with_mnemonic (_("Cancel")); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item); + gtk_widget_show (menu_item); + + damd.chosen = 0; + damd.loop = g_main_loop_new (NULL, FALSE); + + g_signal_connect (menu, "deactivate", + G_CALLBACK (menu_deactivate_callback), + &damd); + + gtk_grab_add (menu); + + gtk_menu_popup (GTK_MENU (menu), NULL, NULL, + NULL, NULL, 0, GDK_CURRENT_TIME); + + g_main_loop_run (damd.loop); + + gtk_grab_remove (menu); + + g_main_loop_unref (damd.loop); + + g_object_ref_sink (menu); + g_object_unref (menu); + + return damd.chosen; +} + +GdkDragAction +caja_drag_drop_background_ask (GtkWidget *widget, + GdkDragAction actions) +{ + GtkWidget *menu; + GtkWidget *menu_item; + DropActionMenuData damd; + + /* Create the menu and set the sensitivity of the items based on the + * allowed actions. + */ + menu = gtk_menu_new (); + gtk_menu_set_screen (GTK_MENU (menu), gtk_widget_get_screen (widget)); + + append_drop_action_menu_item (menu, _("Set as background for _all folders"), + CAJA_DND_ACTION_SET_AS_GLOBAL_BACKGROUND, + (actions & CAJA_DND_ACTION_SET_AS_GLOBAL_BACKGROUND) != 0, + &damd); + + append_drop_action_menu_item (menu, _("Set as background for _this folder"), + CAJA_DND_ACTION_SET_AS_FOLDER_BACKGROUND, + (actions & CAJA_DND_ACTION_SET_AS_FOLDER_BACKGROUND) != 0, + &damd); + + eel_gtk_menu_append_separator (GTK_MENU (menu)); + + menu_item = gtk_menu_item_new_with_mnemonic (_("Cancel")); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item); + gtk_widget_show (menu_item); + + damd.chosen = 0; + damd.loop = g_main_loop_new (NULL, FALSE); + + g_signal_connect (menu, "deactivate", + G_CALLBACK (menu_deactivate_callback), + &damd); + + gtk_grab_add (menu); + + gtk_menu_popup (GTK_MENU (menu), NULL, NULL, + NULL, NULL, 0, GDK_CURRENT_TIME); + + g_main_loop_run (damd.loop); + + gtk_grab_remove (menu); + + g_main_loop_unref (damd.loop); + + g_object_ref_sink (menu); + g_object_unref (menu); + + return damd.chosen; +} + +gboolean +caja_drag_autoscroll_in_scroll_region (GtkWidget *widget) +{ + float x_scroll_delta, y_scroll_delta; + + caja_drag_autoscroll_calculate_delta (widget, &x_scroll_delta, &y_scroll_delta); + + return x_scroll_delta != 0 || y_scroll_delta != 0; +} + + +void +caja_drag_autoscroll_calculate_delta (GtkWidget *widget, float *x_scroll_delta, float *y_scroll_delta) +{ + GtkAllocation allocation; + int x, y; + + g_assert (GTK_IS_WIDGET (widget)); + + gdk_window_get_pointer (gtk_widget_get_window (widget), &x, &y, NULL); + + /* Find out if we are anywhere close to the tree view edges + * to see if we need to autoscroll. + */ + *x_scroll_delta = 0; + *y_scroll_delta = 0; + + if (x < AUTO_SCROLL_MARGIN) + { + *x_scroll_delta = (float)(x - AUTO_SCROLL_MARGIN); + } + + gtk_widget_get_allocation (widget, &allocation); + if (x > allocation.width - AUTO_SCROLL_MARGIN) + { + if (*x_scroll_delta != 0) + { + /* Already trying to scroll because of being too close to + * the top edge -- must be the window is really short, + * don't autoscroll. + */ + return; + } + *x_scroll_delta = (float)(x - (allocation.width - AUTO_SCROLL_MARGIN)); + } + + if (y < AUTO_SCROLL_MARGIN) + { + *y_scroll_delta = (float)(y - AUTO_SCROLL_MARGIN); + } + + if (y > allocation.height - AUTO_SCROLL_MARGIN) + { + if (*y_scroll_delta != 0) + { + /* Already trying to scroll because of being too close to + * the top edge -- must be the window is really narrow, + * don't autoscroll. + */ + return; + } + *y_scroll_delta = (float)(y - (allocation.height - AUTO_SCROLL_MARGIN)); + } + + if (*x_scroll_delta == 0 && *y_scroll_delta == 0) + { + /* no work */ + return; + } + + /* Adjust the scroll delta to the proper acceleration values depending on how far + * into the sroll margins we are. + * FIXME bugzilla.eazel.com 2486: + * we could use an exponential acceleration factor here for better feel + */ + if (*x_scroll_delta != 0) + { + *x_scroll_delta /= AUTO_SCROLL_MARGIN; + *x_scroll_delta *= (MAX_AUTOSCROLL_DELTA - MIN_AUTOSCROLL_DELTA); + *x_scroll_delta += MIN_AUTOSCROLL_DELTA; + } + + if (*y_scroll_delta != 0) + { + *y_scroll_delta /= AUTO_SCROLL_MARGIN; + *y_scroll_delta *= (MAX_AUTOSCROLL_DELTA - MIN_AUTOSCROLL_DELTA); + *y_scroll_delta += MIN_AUTOSCROLL_DELTA; + } + +} + + + +void +caja_drag_autoscroll_start (CajaDragInfo *drag_info, + GtkWidget *widget, + GtkFunction callback, + gpointer user_data) +{ + if (caja_drag_autoscroll_in_scroll_region (widget)) + { + if (drag_info->auto_scroll_timeout_id == 0) + { + drag_info->waiting_to_autoscroll = TRUE; + drag_info->start_auto_scroll_in = eel_get_system_time() + + AUTOSCROLL_INITIAL_DELAY; + + drag_info->auto_scroll_timeout_id = g_timeout_add + (AUTOSCROLL_TIMEOUT_INTERVAL, + callback, + user_data); + } + } + else + { + if (drag_info->auto_scroll_timeout_id != 0) + { + g_source_remove (drag_info->auto_scroll_timeout_id); + drag_info->auto_scroll_timeout_id = 0; + } + } +} + +void +caja_drag_autoscroll_stop (CajaDragInfo *drag_info) +{ + if (drag_info->auto_scroll_timeout_id != 0) + { + g_source_remove (drag_info->auto_scroll_timeout_id); + drag_info->auto_scroll_timeout_id = 0; + } +} + +gboolean +caja_drag_selection_includes_special_link (GList *selection_list) +{ + GList *node; + char *uri; + + for (node = selection_list; node != NULL; node = node->next) + { + uri = ((CajaDragSelectionItem *) node->data)->uri; + + if (eel_uri_is_desktop (uri)) + { + return TRUE; + } + } + + return FALSE; +} + +static gboolean +slot_proxy_drag_motion (GtkWidget *widget, + GdkDragContext *context, + int x, + int y, + unsigned int time, + gpointer user_data) +{ + CajaDragSlotProxyInfo *drag_info; + CajaWindowSlotInfo *target_slot; + GtkWidget *window; + GdkAtom target; + int action; + char *target_uri; + + drag_info = user_data; + + action = 0; + + if (gtk_drag_get_source_widget (context) == widget) + { + goto out; + } + + window = gtk_widget_get_toplevel (widget); + g_assert (CAJA_IS_WINDOW_INFO (window)); + + if (!drag_info->have_data) + { + target = gtk_drag_dest_find_target (widget, context, NULL); + + if (target == GDK_NONE) + { + goto out; + } + + gtk_drag_get_data (widget, context, target, time); + } + + target_uri = NULL; + if (drag_info->target_location != NULL) + { + target_uri = g_file_get_uri (drag_info->target_location); + } + else + { + if (drag_info->target_slot != NULL) + { + target_slot = drag_info->target_slot; + } + else + { + target_slot = caja_window_info_get_active_slot (CAJA_WINDOW_INFO (window)); + } + + if (target_slot != NULL) + { + target_uri = caja_window_slot_info_get_current_location (target_slot); + } + } + + if (drag_info->have_data && + drag_info->have_valid_data) + { + if (drag_info->info == CAJA_ICON_DND_MATE_ICON_LIST) + { + caja_drag_default_drop_action_for_icons (context, target_uri, + drag_info->data.selection_list, + &action); + } + else if (drag_info->info == CAJA_ICON_DND_URI_LIST) + { + action = caja_drag_default_drop_action_for_uri_list (context, target_uri); + } + else if (drag_info->info == CAJA_ICON_DND_NETSCAPE_URL) + { + action = caja_drag_default_drop_action_for_netscape_url (context); + } + } + + g_free (target_uri); + +out: + if (action != 0) + { + gtk_drag_highlight (widget); + } + else + { + gtk_drag_unhighlight (widget); + } + + gdk_drag_status (context, action, time); + + return TRUE; +} + +static void +drag_info_clear (CajaDragSlotProxyInfo *drag_info) +{ + if (!drag_info->have_data) + { + goto out; + } + + if (drag_info->info == CAJA_ICON_DND_MATE_ICON_LIST) + { + caja_drag_destroy_selection_list (drag_info->data.selection_list); + } + else if (drag_info->info == CAJA_ICON_DND_URI_LIST) + { + g_list_free (drag_info->data.uri_list); + } + else if (drag_info->info == CAJA_ICON_DND_NETSCAPE_URL) + { + g_free (drag_info->data.netscape_url); + } + +out: + drag_info->have_data = FALSE; + drag_info->have_valid_data = FALSE; + + drag_info->drop_occured = FALSE; +} + +static void +slot_proxy_drag_leave (GtkWidget *widget, + GdkDragContext *context, + unsigned int time, + gpointer user_data) +{ + CajaDragSlotProxyInfo *drag_info; + + drag_info = user_data; + + gtk_drag_unhighlight (widget); + drag_info_clear (drag_info); +} + +static gboolean +slot_proxy_drag_drop (GtkWidget *widget, + GdkDragContext *context, + int x, + int y, + unsigned int time, + gpointer user_data) +{ + GdkAtom target; + CajaDragSlotProxyInfo *drag_info; + + drag_info = user_data; + g_assert (!drag_info->have_data); + + drag_info->drop_occured = TRUE; + + target = gtk_drag_dest_find_target (widget, context, NULL); + gtk_drag_get_data (widget, context, target, time); + + return TRUE; +} + + +static void +slot_proxy_handle_drop (GtkWidget *widget, + GdkDragContext *context, + unsigned int time, + CajaDragSlotProxyInfo *drag_info) +{ + GtkWidget *window; + CajaWindowSlotInfo *target_slot; + CajaView *target_view; + char *target_uri; + GList *uri_list; + + if (!drag_info->have_data || + !drag_info->have_valid_data) + { + gtk_drag_finish (context, FALSE, FALSE, time); + drag_info_clear (drag_info); + return; + } + + window = gtk_widget_get_toplevel (widget); + g_assert (CAJA_IS_WINDOW_INFO (window)); + + if (drag_info->target_slot != NULL) + { + target_slot = drag_info->target_slot; + } + else + { + target_slot = caja_window_info_get_active_slot (CAJA_WINDOW_INFO (window)); + } + + target_uri = NULL; + if (drag_info->target_location != NULL) + { + target_uri = g_file_get_uri (drag_info->target_location); + } + else if (target_slot != NULL) + { + target_uri = caja_window_slot_info_get_current_location (target_slot); + } + + target_view = NULL; + if (target_slot != NULL) + { + target_view = caja_window_slot_info_get_current_view (target_slot); + } + + if (target_slot != NULL && target_view != NULL) + { + if (drag_info->info == CAJA_ICON_DND_MATE_ICON_LIST) + { + uri_list = caja_drag_uri_list_from_selection_list (drag_info->data.selection_list); + g_assert (uri_list != NULL); + + caja_view_drop_proxy_received_uris (target_view, + uri_list, + target_uri, + gdk_drag_context_get_selected_action (context)); + eel_g_list_free_deep (uri_list); + } + else if (drag_info->info == CAJA_ICON_DND_URI_LIST) + { + caja_view_drop_proxy_received_uris (target_view, + drag_info->data.uri_list, + target_uri, + gdk_drag_context_get_selected_action (context)); + } + if (drag_info->info == CAJA_ICON_DND_NETSCAPE_URL) + { + caja_view_drop_proxy_received_netscape_url (target_view, + drag_info->data.netscape_url, + target_uri, + gdk_drag_context_get_selected_action (context)); + } + + + gtk_drag_finish (context, TRUE, FALSE, time); + } + else + { + gtk_drag_finish (context, FALSE, FALSE, time); + } + + if (target_view != NULL) + { + g_object_unref (target_view); + } + + g_free (target_uri); + + drag_info_clear (drag_info); +} + +static void +slot_proxy_drag_data_received (GtkWidget *widget, + GdkDragContext *context, + int x, + int y, + GtkSelectionData *data, + unsigned int info, + unsigned int time, + gpointer user_data) +{ + CajaDragSlotProxyInfo *drag_info; + char **uris; + + drag_info = user_data; + + g_assert (!drag_info->have_data); + + drag_info->have_data = TRUE; + drag_info->info = info; + + if (gtk_selection_data_get_length (data) < 0) + { + drag_info->have_valid_data = FALSE; + return; + } + + if (info == CAJA_ICON_DND_MATE_ICON_LIST) + { + drag_info->data.selection_list = caja_drag_build_selection_list (data); + + drag_info->have_valid_data = drag_info->data.selection_list != NULL; + } + else if (info == CAJA_ICON_DND_URI_LIST) + { + uris = gtk_selection_data_get_uris (data); + drag_info->data.uri_list = caja_drag_uri_list_from_array ((const char **) uris); + g_strfreev (uris); + + drag_info->have_valid_data = drag_info->data.uri_list != NULL; + } + else if (info == CAJA_ICON_DND_NETSCAPE_URL) + { + drag_info->data.netscape_url = g_strdup ((char *) gtk_selection_data_get_data (data)); + + drag_info->have_valid_data = drag_info->data.netscape_url != NULL; + } + + if (drag_info->drop_occured) + { + slot_proxy_handle_drop (widget, context, time, drag_info); + } +} + +void +caja_drag_slot_proxy_init (GtkWidget *widget, + CajaDragSlotProxyInfo *drag_info) +{ + const GtkTargetEntry targets[] = + { + { CAJA_ICON_DND_MATE_ICON_LIST_TYPE, 0, CAJA_ICON_DND_MATE_ICON_LIST }, + { CAJA_ICON_DND_NETSCAPE_URL_TYPE, 0, CAJA_ICON_DND_NETSCAPE_URL } + }; + GtkTargetList *target_list; + + g_assert (GTK_IS_WIDGET (widget)); + g_assert (drag_info != NULL); + + gtk_drag_dest_set (widget, 0, + NULL, 0, + GDK_ACTION_MOVE | + GDK_ACTION_COPY | + GDK_ACTION_LINK | + GDK_ACTION_ASK); + + target_list = gtk_target_list_new (targets, G_N_ELEMENTS (targets)); + gtk_target_list_add_uri_targets (target_list, CAJA_ICON_DND_URI_LIST); + gtk_drag_dest_set_target_list (widget, target_list); + gtk_target_list_unref (target_list); + + g_signal_connect (widget, "drag-motion", + G_CALLBACK (slot_proxy_drag_motion), + drag_info); + g_signal_connect (widget, "drag-drop", + G_CALLBACK (slot_proxy_drag_drop), + drag_info); + g_signal_connect (widget, "drag-data-received", + G_CALLBACK (slot_proxy_drag_data_received), + drag_info); + g_signal_connect (widget, "drag-leave", + G_CALLBACK (slot_proxy_drag_leave), + drag_info); +} + + |