summaryrefslogtreecommitdiffstats
path: root/src/nautilus-dnd.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/nautilus-dnd.c')
-rw-r--r--src/nautilus-dnd.c978
1 files changed, 978 insertions, 0 deletions
diff --git a/src/nautilus-dnd.c b/src/nautilus-dnd.c
new file mode 100644
index 0000000..8071d5b
--- /dev/null
+++ b/src/nautilus-dnd.c
@@ -0,0 +1,978 @@
+/* nautilus-dnd.c - Common Drag & drop handling code shared by the icon container
+ * and the list view.
+ *
+ * Copyright (C) 2000, 2001 Eazel, Inc.
+ *
+ * The Gnome 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 Gnome 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 Gnome Library; see the file COPYING.LIB. If not,
+ * see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Pavel Cisler <pavel@eazel.com>,
+ * Ettore Perazzoli <ettore@gnu.org>
+ */
+
+#include <config.h>
+#include "nautilus-dnd.h"
+
+#include "nautilus-program-choosing.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 "nautilus-file-utilities.h"
+#include "nautilus-canvas-dnd.h"
+#include <src/nautilus-list-view-dnd.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
+nautilus_drag_init (NautilusDragInfo *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,
+ NAUTILUS_ICON_DND_TEXT);
+ }
+
+ drag_info->drop_occurred = FALSE;
+ drag_info->need_to_destroy = FALSE;
+}
+
+void
+nautilus_drag_finalize (NautilusDragInfo *drag_info)
+{
+ gtk_target_list_unref (drag_info->target_list);
+ nautilus_drag_destroy_selection_list (drag_info->selection_list);
+ nautilus_drag_destroy_selection_list (drag_info->selection_cache);
+
+ g_free (drag_info);
+}
+
+
+/* Functions to deal with NautilusDragSelectionItems. */
+
+NautilusDragSelectionItem *
+nautilus_drag_selection_item_new (void)
+{
+ return g_new0 (NautilusDragSelectionItem, 1);
+}
+
+static void
+drag_selection_item_destroy (NautilusDragSelectionItem *item)
+{
+ g_clear_object (&item->file);
+ g_free (item->uri);
+ g_free (item);
+}
+
+void
+nautilus_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);
+}
+
+GList *
+nautilus_drag_uri_list_from_selection_list (const GList *selection_list)
+{
+ NautilusDragSelectionItem *selection_item;
+ GList *uri_list;
+ const GList *l;
+
+ uri_list = NULL;
+ for (l = selection_list; l != NULL; l = l->next)
+ {
+ selection_item = (NautilusDragSelectionItem *) 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);
+}
+
+/*
+ * Transfer: Full. Free with g_list_free_full (list, g_object_unref);
+ */
+GList *
+nautilus_drag_file_list_from_selection_list (const GList *selection_list)
+{
+ NautilusDragSelectionItem *selection_item;
+ GList *file_list;
+ const GList *l;
+
+ file_list = NULL;
+ for (l = selection_list; l != NULL; l = l->next)
+ {
+ selection_item = (NautilusDragSelectionItem *) l->data;
+ if (selection_item->file != NULL)
+ {
+ file_list = g_list_prepend (file_list, g_object_ref (selection_item->file));
+ }
+ }
+
+ return g_list_reverse (file_list);
+}
+
+GList *
+nautilus_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 *
+nautilus_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)
+ {
+ NautilusDragSelectionItem *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 = nautilus_drag_selection_item_new ();
+
+ len = p - oldp;
+
+ item->uri = g_malloc (len + 1);
+ memcpy (item->uri, oldp, len);
+ item->uri[len] = 0;
+ item->file = nautilus_file_get_by_uri (item->uri);
+
+ p++;
+ if (*p == '\n' || *p == '\0')
+ {
+ result = g_list_prepend (result, item);
+ if (p == 0)
+ {
+ g_warning ("Invalid x-special/gnome-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 ((const gchar *) 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/gnome-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/gnome-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
+nautilus_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
+nautilus_drag_uris_local (const char *target_uri,
+ const GList *source_uri_list)
+{
+ /* must have at least one item */
+ g_assert (source_uri_list);
+
+ return nautilus_drag_file_local_internal (target_uri, source_uri_list->data);
+}
+
+gboolean
+nautilus_drag_items_local (const char *target_uri_string,
+ const GList *selection_list)
+{
+ /* must have at least one item */
+ g_assert (selection_list);
+
+ return nautilus_drag_file_local_internal (target_uri_string,
+ ((NautilusDragSelectionItem *) selection_list->data)->uri);
+}
+
+GdkDragAction
+nautilus_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 LINK */
+ if (gdk_drag_context_get_suggested_action (context) == GDK_ACTION_COPY &&
+ gdk_drag_context_get_actions (context) != GDK_ACTION_COPY)
+ {
+ return GDK_ACTION_LINK;
+ }
+ 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 (NautilusFile *file1,
+ NautilusFile *file2)
+{
+ char *id1, *id2;
+ gboolean result;
+
+ result = FALSE;
+
+ if (file1 != NULL && file2 != NULL)
+ {
+ id1 = nautilus_file_get_filesystem_id (file1);
+ id2 = nautilus_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)
+{
+ NautilusFile *naut_file;
+ gboolean ret;
+
+ /* if there's no a cached NautilusFile, it returns NULL */
+ naut_file = nautilus_file_get (file);
+ if (naut_file == NULL)
+ {
+ return FALSE;
+ }
+
+ ret = nautilus_file_can_delete (naut_file);
+ nautilus_file_unref (naut_file);
+
+ return ret;
+}
+
+NautilusDragInfo *
+nautilus_drag_get_source_data (GdkDragContext *context)
+{
+ GtkWidget *source_widget;
+ NautilusDragInfo *source_data;
+
+ source_widget = gtk_drag_get_source_widget (context);
+ if (source_widget == NULL)
+ {
+ return NULL;
+ }
+
+ if (NAUTILUS_IS_CANVAS_CONTAINER (source_widget))
+ {
+ source_data = nautilus_canvas_dnd_get_drag_source_data (NAUTILUS_CANVAS_CONTAINER (source_widget),
+ context);
+ }
+ else if (GTK_IS_TREE_VIEW (source_widget))
+ {
+ NautilusWindow *window;
+ NautilusWindowSlot *active_slot;
+ NautilusView *view;
+
+ window = NAUTILUS_WINDOW (gtk_widget_get_toplevel (source_widget));
+ active_slot = nautilus_window_get_active_slot (window);
+ view = nautilus_window_slot_get_current_view (active_slot);
+ if (NAUTILUS_IS_LIST_VIEW (view))
+ {
+ source_data = nautilus_list_view_dnd_get_drag_source_data (NAUTILUS_LIST_VIEW (view),
+ context);
+ }
+ else
+ {
+ g_warning ("Got a drag context with a tree view source widget, but current view is not list view");
+ source_data = NULL;
+ }
+ }
+ else
+ {
+ /* it's a slot or something else */
+ g_warning ("Requested drag source data from a widget that doesn't support it");
+ source_data = NULL;
+ }
+
+ return source_data;
+}
+
+void
+nautilus_drag_default_drop_action_for_icons (GdkDragContext *context,
+ const char *target_uri_string,
+ const GList *items,
+ guint32 source_actions,
+ int *action)
+{
+ gboolean same_fs;
+ gboolean target_is_source_parent;
+ gboolean source_deletable;
+ const char *dropped_uri;
+ GFile *target, *dropped, *dropped_directory;
+ GdkDragAction actions;
+ NautilusFile *dropped_file, *target_file;
+
+ if (target_uri_string == NULL)
+ {
+ *action = 0;
+ return;
+ }
+
+ /* this is needed because of how dnd works. The actions at the time drag-begin
+ * is done are not set, because they are first set on drag-motion. However,
+ * for our use case, which is validation with the sidebar for dnd feedback
+ * when the dnd doesn't have as a destination the sidebar itself, we need
+ * a way to know the actions at drag-begin time. Either canvas view or
+ * list view know them when starting the drag, but asking for them here
+ * would be breaking the current model too much. So instead we rely on the
+ * caller, which will ask if appropiate to those objects about the actions
+ * available, instead of relying solely on the context here. */
+ if (source_actions)
+ {
+ actions = source_actions & (GDK_ACTION_MOVE | GDK_ACTION_COPY);
+ }
+ else
+ {
+ 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 = ((NautilusDragSelectionItem *) items->data)->uri;
+ dropped_file = ((NautilusDragSelectionItem *) items->data)->file;
+ target_file = nautilus_file_get_by_uri (target_uri_string);
+
+ /*
+ * Check for trash URI. We do a find_directory for any Trash directory.
+ * Passing 0 permissions as gnome-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;
+ }
+ nautilus_file_unref (target_file);
+ return;
+ }
+ else if (target_file != NULL && nautilus_file_is_archive (target_file))
+ {
+ *action = GDK_ACTION_COPY;
+
+ nautilus_file_unref (target_file);
+ return;
+ }
+ else
+ {
+ target = g_file_new_for_uri (target_uri_string);
+ }
+
+ same_fs = check_same_fs (target_file, dropped_file);
+
+ nautilus_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
+nautilus_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/gnome-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_gnome_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);
+}
+
+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");
+}
+
+static void
+cache_one_item (const char *uri,
+ int x,
+ int y,
+ int w,
+ int h,
+ gpointer data)
+{
+ GList **cache = data;
+ NautilusDragSelectionItem *item;
+
+ item = nautilus_drag_selection_item_new ();
+ item->uri = nautilus_uri_to_native_uri (uri);
+
+ if (item->uri == NULL)
+ {
+ item->uri = g_strdup (uri);
+ }
+
+ item->file = nautilus_file_get_by_uri (uri);
+ item->icon_x = x;
+ item->icon_y = y;
+ item->icon_width = w;
+ item->icon_height = h;
+ *cache = g_list_prepend (*cache, item);
+}
+
+GList *
+nautilus_drag_create_selection_cache (gpointer container_context,
+ NautilusDragEachSelectedItemIterator each_selected_item_iterator)
+{
+ GList *cache = NULL;
+
+ (*each_selected_item_iterator)(cache_one_item, container_context, &cache);
+ cache = g_list_reverse (cache);
+
+ return cache;
+}
+
+/* Common function for drag_data_get_callback calls.
+ * Returns FALSE if it doesn't handle drag data */
+gboolean
+nautilus_drag_drag_data_get_from_cache (GList *cache,
+ GdkDragContext *context,
+ GtkSelectionData *selection_data,
+ guint info,
+ guint32 time)
+{
+ GList *l;
+ GString *result;
+ NautilusDragEachSelectedItemDataGet func;
+
+ if (cache == NULL)
+ {
+ return FALSE;
+ }
+
+ switch (info)
+ {
+ case NAUTILUS_ICON_DND_GNOME_ICON_LIST:
+ {
+ func = add_one_gnome_icon;
+ }
+ break;
+
+ case NAUTILUS_ICON_DND_URI_LIST:
+ case NAUTILUS_ICON_DND_TEXT:
+ {
+ func = add_one_uri;
+ }
+ break;
+
+ default:
+ return FALSE;
+ }
+
+ result = g_string_new (NULL);
+
+ for (l = cache; l != NULL; l = l->next)
+ {
+ NautilusDragSelectionItem *item = l->data;
+ (*func)(item->uri, item->icon_x, item->icon_y, item->icon_width, item->icon_height, result);
+ }
+
+ gtk_selection_data_set (selection_data,
+ gtk_selection_data_get_target (selection_data),
+ 8, (guchar *) 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
+nautilus_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);
+
+ 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_at_pointer (GTK_MENU (menu),
+ NULL);
+
+ 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
+nautilus_drag_autoscroll_in_scroll_region (GtkWidget *widget)
+{
+ float x_scroll_delta, y_scroll_delta;
+
+ nautilus_drag_autoscroll_calculate_delta (widget, &x_scroll_delta, &y_scroll_delta);
+
+ return x_scroll_delta != 0 || y_scroll_delta != 0;
+}
+
+
+void
+nautilus_drag_autoscroll_calculate_delta (GtkWidget *widget,
+ float *x_scroll_delta,
+ float *y_scroll_delta)
+{
+ GtkAllocation allocation;
+ GdkDisplay *display;
+ GdkSeat *seat;
+ GdkDevice *pointer;
+ int x, y;
+
+ g_assert (GTK_IS_WIDGET (widget));
+
+ display = gtk_widget_get_display (widget);
+ seat = gdk_display_get_default_seat (display);
+ pointer = gdk_seat_get_pointer (seat);
+ gdk_window_get_device_position (gtk_widget_get_window (widget), pointer,
+ &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
+nautilus_drag_autoscroll_start (NautilusDragInfo *drag_info,
+ GtkWidget *widget,
+ GSourceFunc callback,
+ gpointer user_data)
+{
+ if (nautilus_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 = g_get_monotonic_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
+nautilus_drag_autoscroll_stop (NautilusDragInfo *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;
+ }
+}