diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 15:59:36 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 15:59:36 +0000 |
commit | ec52555862913a23417735f9f7f5402f5230da13 (patch) | |
tree | 5e43a30d289a3daa69dddfbb060216ff6332f197 /src/nautilus-canvas-dnd.c | |
parent | Initial commit. (diff) | |
download | nautilus-upstream/3.38.2.tar.xz nautilus-upstream/3.38.2.zip |
Adding upstream version 3.38.2.upstream/3.38.2upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/nautilus-canvas-dnd.c')
-rw-r--r-- | src/nautilus-canvas-dnd.c | 1839 |
1 files changed, 1839 insertions, 0 deletions
diff --git a/src/nautilus-canvas-dnd.c b/src/nautilus-canvas-dnd.c new file mode 100644 index 0000000..9d5a5c6 --- /dev/null +++ b/src/nautilus-canvas-dnd.c @@ -0,0 +1,1839 @@ +/* nautilus-canvas-dnd.c - Drag & drop handling for the canvas container widget. + * + * Copyright (C) 1999, 2000 Free Software Foundation + * Copyright (C) 2000 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: Ettore Perazzoli <ettore@gnu.org>, + * Darin Adler <darin@bentspoon.com>, + * Andy Hertzfeld <andy@eazel.com> + * Pavel Cisler <pavel@eazel.com> + * + * + * XDS support: Benedikt Meurer <benny@xfce.org> (adapted by Amos Brocco <amos.brocco@unifr.ch>) + * + */ + + +#include <config.h> +#include <math.h> +#include <src/nautilus-window.h> + +#include "nautilus-canvas-dnd.h" + +#include "nautilus-canvas-private.h" +#include "nautilus-global-preferences.h" +#include "nautilus-metadata.h" +#include "nautilus-selection-canvas-item.h" +#include <eel/eel-glib-extensions.h> +#include <eel/eel-graphic-effects.h> +#include <eel/eel-gtk-extensions.h> +#include <eel/eel-stock-dialogs.h> +#include <eel/eel-string.h> +#include <eel/eel-vfs-extensions.h> +#include <gdk/gdkkeysyms.h> +#include <gdk/gdk.h> +#include <gtk/gtk.h> +#include <glib/gi18n.h> + +#include "nautilus-file-utilities.h" +#include "nautilus-file-changes-queue.h" +#include <stdio.h> +#include <string.h> + +#define DEBUG_FLAG NAUTILUS_DEBUG_CANVAS_CONTAINER +#include "nautilus-debug.h" + +static const GtkTargetEntry drag_types [] = +{ + { NAUTILUS_ICON_DND_GNOME_ICON_LIST_TYPE, 0, NAUTILUS_ICON_DND_GNOME_ICON_LIST }, + { NAUTILUS_ICON_DND_URI_LIST_TYPE, 0, NAUTILUS_ICON_DND_URI_LIST }, +}; + +static const GtkTargetEntry drop_types [] = +{ + { NAUTILUS_ICON_DND_GNOME_ICON_LIST_TYPE, 0, NAUTILUS_ICON_DND_GNOME_ICON_LIST }, + /* prefer "_NETSCAPE_URL" over "text/uri-list" to satisfy web browsers. */ + { NAUTILUS_ICON_DND_NETSCAPE_URL_TYPE, 0, NAUTILUS_ICON_DND_NETSCAPE_URL }, + /* prefer XDS over "text/uri-list" */ + { NAUTILUS_ICON_DND_XDNDDIRECTSAVE_TYPE, 0, NAUTILUS_ICON_DND_XDNDDIRECTSAVE }, /* XDS Protocol Type */ + { NAUTILUS_ICON_DND_URI_LIST_TYPE, 0, NAUTILUS_ICON_DND_URI_LIST }, + { NAUTILUS_ICON_DND_RAW_TYPE, 0, NAUTILUS_ICON_DND_RAW }, + /* Must be last: */ + { NAUTILUS_ICON_DND_ROOTWINDOW_DROP_TYPE, 0, NAUTILUS_ICON_DND_ROOTWINDOW_DROP } +}; +static void stop_dnd_highlight (GtkWidget *widget); +static void dnd_highlight_queue_redraw (GtkWidget *widget); + +static GtkTargetList *drop_types_list = NULL; +static GtkTargetList *drop_types_list_root = NULL; + +static char *nautilus_canvas_container_find_drop_target (NautilusCanvasContainer *container, + GdkDragContext *context, + int x, + int y, + gboolean *icon_hit); + +static EelCanvasItem * +create_selection_shadow (NautilusCanvasContainer *container, + GList *list) +{ + EelCanvasGroup *group; + EelCanvas *canvas; + int max_x, max_y; + int min_x, min_y; + GList *p; + GtkAllocation allocation; + + if (list == NULL) + { + return NULL; + } + + /* if we're only dragging a single item, don't worry about the shadow */ + if (list->next == NULL) + { + return NULL; + } + + canvas = EEL_CANVAS (container); + gtk_widget_get_allocation (GTK_WIDGET (container), &allocation); + + /* Creating a big set of rectangles in the canvas can be expensive, so + * we try to be smart and only create the maximum number of rectangles + * that we will need, in the vertical/horizontal directions. */ + + max_x = allocation.width; + min_x = -max_x; + + max_y = allocation.height; + min_y = -max_y; + + /* Create a group, so that it's easier to move all the items around at + * once. */ + group = EEL_CANVAS_GROUP + (eel_canvas_item_new (EEL_CANVAS_GROUP (canvas->root), + eel_canvas_group_get_type (), + NULL)); + + for (p = list; p != NULL; p = p->next) + { + NautilusDragSelectionItem *item; + int x1, y1, x2, y2; + + item = p->data; + + if (!item->got_icon_position) + { + continue; + } + + x1 = item->icon_x; + y1 = item->icon_y; + x2 = x1 + item->icon_width; + y2 = y1 + item->icon_height; + + if (x2 >= min_x && x1 <= max_x && y2 >= min_y && y1 <= max_y) + { + eel_canvas_item_new + (group, + NAUTILUS_TYPE_SELECTION_CANVAS_ITEM, + "x1", (double) x1, + "y1", (double) y1, + "x2", (double) x2, + "y2", (double) y2, + NULL); + } + } + + return EEL_CANVAS_ITEM (group); +} + +/* Set the affine instead of the x and y position. + * Simple, and setting x and y was broken at one point. + */ +static void +set_shadow_position (EelCanvasItem *shadow, + double x, + double y) +{ + eel_canvas_item_set (shadow, + "x", x, "y", y, + NULL); +} + + +/* Source-side handling of the drag. */ + +/* iteration glue struct */ +typedef struct +{ + gpointer iterator_context; + NautilusDragEachSelectedItemDataGet iteratee; + gpointer iteratee_data; +} CanvasGetDataBinderContext; + +static void +canvas_rect_world_to_widget (EelCanvas *canvas, + EelDRect *world_rect, + EelIRect *widget_rect) +{ + EelDRect window_rect; + GtkAdjustment *hadj, *vadj; + + hadj = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (canvas)); + vadj = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (canvas)); + + eel_canvas_world_to_window (canvas, + world_rect->x0, world_rect->y0, + &window_rect.x0, &window_rect.y0); + eel_canvas_world_to_window (canvas, + world_rect->x1, world_rect->y1, + &window_rect.x1, &window_rect.y1); + widget_rect->x0 = (int) window_rect.x0 - gtk_adjustment_get_value (hadj); + widget_rect->y0 = (int) window_rect.y0 - gtk_adjustment_get_value (vadj); + widget_rect->x1 = (int) window_rect.x1 - gtk_adjustment_get_value (hadj); + widget_rect->y1 = (int) window_rect.y1 - gtk_adjustment_get_value (vadj); +} + +static void +canvas_widget_to_world (EelCanvas *canvas, + double widget_x, + double widget_y, + double *world_x, + double *world_y) +{ + eel_canvas_window_to_world (canvas, + widget_x + gtk_adjustment_get_value (gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (canvas))), + widget_y + gtk_adjustment_get_value (gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (canvas))), + world_x, world_y); +} + +static gboolean +icon_get_data_binder (NautilusCanvasIcon *icon, + gpointer data) +{ + CanvasGetDataBinderContext *context; + EelDRect world_rect; + EelIRect widget_rect; + char *uri; + NautilusCanvasContainer *container; + NautilusFile *file; + + context = (CanvasGetDataBinderContext *) data; + + g_assert (NAUTILUS_IS_CANVAS_CONTAINER (context->iterator_context)); + + container = NAUTILUS_CANVAS_CONTAINER (context->iterator_context); + + world_rect = nautilus_canvas_item_get_icon_rectangle (icon->item); + + canvas_rect_world_to_widget (EEL_CANVAS (container), &world_rect, &widget_rect); + + uri = nautilus_canvas_container_get_icon_uri (container, icon); + file = nautilus_file_get_by_uri (uri); + g_free (uri); + uri = nautilus_canvas_container_get_icon_activation_uri (container, icon); + + if (uri == NULL) + { + g_warning ("no URI for one of the iterated icons"); + nautilus_file_unref (file); + return TRUE; + } + + widget_rect = eel_irect_offset_by (widget_rect, + -container->details->dnd_info->drag_info.start_x, + -container->details->dnd_info->drag_info.start_y); + + widget_rect = eel_irect_scale_by (widget_rect, + 1 / EEL_CANVAS (container)->pixels_per_unit); + + /* pass the uri, mouse-relative x/y and icon width/height */ + context->iteratee (uri, + (int) widget_rect.x0, + (int) widget_rect.y0, + widget_rect.x1 - widget_rect.x0, + widget_rect.y1 - widget_rect.y0, + context->iteratee_data); + + g_free (uri); + nautilus_file_unref (file); + + return TRUE; +} + +/* Iterate over each selected icon in a NautilusCanvasContainer, + * calling each_function on each. + */ +static void +nautilus_canvas_container_each_selected_icon (NautilusCanvasContainer *container, + gboolean (*each_function)(NautilusCanvasIcon *, gpointer), + gpointer data) +{ + GList *p; + NautilusCanvasIcon *icon; + + for (p = container->details->icons; p != NULL; p = p->next) + { + icon = p->data; + if (!icon->is_selected) + { + continue; + } + if (!each_function (icon, data)) + { + return; + } + } +} + +/* Adaptor function used with nautilus_canvas_container_each_selected_icon + * to help iterate over all selected items, passing uris, x, y, w and h + * values to the iteratee + */ +static void +each_icon_get_data_binder (NautilusDragEachSelectedItemDataGet iteratee, + gpointer iterator_context, + gpointer data) +{ + CanvasGetDataBinderContext context; + NautilusCanvasContainer *container; + + g_assert (NAUTILUS_IS_CANVAS_CONTAINER (iterator_context)); + container = NAUTILUS_CANVAS_CONTAINER (iterator_context); + + context.iterator_context = iterator_context; + context.iteratee = iteratee; + context.iteratee_data = data; + nautilus_canvas_container_each_selected_icon (container, icon_get_data_binder, &context); +} + +/* Called when the data for drag&drop is needed */ +static void +drag_data_get_callback (GtkWidget *widget, + GdkDragContext *context, + GtkSelectionData *selection_data, + guint info, + guint32 time, + gpointer data) +{ + NautilusDragInfo *drag_info; + + g_assert (widget != NULL); + g_assert (NAUTILUS_IS_CANVAS_CONTAINER (widget)); + g_return_if_fail (context != NULL); + + /* Call common function from nautilus-drag that set's up + * the selection data in the right format. Pass it means to + * iterate all the selected icons. + */ + drag_info = &(NAUTILUS_CANVAS_CONTAINER (widget)->details->dnd_info->drag_info); + nautilus_drag_drag_data_get_from_cache (drag_info->selection_cache, context, selection_data, info, time); +} + + +/* Target-side handling of the drag. */ + +static void +nautilus_canvas_container_position_shadow (NautilusCanvasContainer *container, + int x, + int y) +{ + EelCanvasItem *shadow; + double world_x, world_y; + + shadow = container->details->dnd_info->shadow; + if (shadow == NULL) + { + return; + } + + canvas_widget_to_world (EEL_CANVAS (container), x, y, + &world_x, &world_y); + + set_shadow_position (shadow, world_x, world_y); + eel_canvas_item_show (shadow); +} + +static void +stop_cache_selection_list (NautilusDragInfo *drag_info) +{ + if (drag_info->file_list_info_handler) + { + nautilus_file_list_cancel_call_when_ready (drag_info->file_list_info_handler); + drag_info->file_list_info_handler = NULL; + } +} + +static void +cache_selection_list (NautilusDragInfo *drag_info) +{ + GList *files; + + files = nautilus_drag_file_list_from_selection_list (drag_info->selection_list); + nautilus_file_list_call_when_ready (files, + NAUTILUS_FILE_ATTRIBUTE_INFO, + drag_info->file_list_info_handler, + NULL, NULL); + + g_list_free_full (files, g_object_unref); +} + +static void +nautilus_canvas_container_dropped_canvas_feedback (GtkWidget *widget, + GtkSelectionData *data, + int x, + int y) +{ + NautilusCanvasContainer *container; + NautilusCanvasDndInfo *dnd_info; + + container = NAUTILUS_CANVAS_CONTAINER (widget); + dnd_info = container->details->dnd_info; + + /* Delete old selection list. */ + stop_cache_selection_list (&dnd_info->drag_info); + nautilus_drag_destroy_selection_list (dnd_info->drag_info.selection_list); + dnd_info->drag_info.selection_list = NULL; + + /* Delete old shadow if any. */ + if (dnd_info->shadow != NULL) + { + /* FIXME bugzilla.gnome.org 42484: + * Is a destroy really sufficient here? Who does the unref? */ + eel_canvas_item_destroy (dnd_info->shadow); + } + + /* Build the selection list and the shadow. */ + dnd_info->drag_info.selection_list = nautilus_drag_build_selection_list (data); + cache_selection_list (&dnd_info->drag_info); + dnd_info->shadow = create_selection_shadow (container, dnd_info->drag_info.selection_list); + nautilus_canvas_container_position_shadow (container, x, y); +} + +static char * +get_direct_save_filename (GdkDragContext *context) +{ + guchar *prop_text; + gint prop_len; + + if (!gdk_property_get (gdk_drag_context_get_source_window (context), gdk_atom_intern (NAUTILUS_ICON_DND_XDNDDIRECTSAVE_TYPE, FALSE), + gdk_atom_intern ("text/plain", FALSE), 0, 1024, FALSE, NULL, NULL, + &prop_len, &prop_text)) + { + return NULL; + } + + /* Zero-terminate the string */ + prop_text = g_realloc (prop_text, prop_len + 1); + prop_text[prop_len] = '\0'; + + /* Verify that the file name provided by the source is valid */ + if (*prop_text == '\0' || + strchr ((const gchar *) prop_text, G_DIR_SEPARATOR) != NULL) + { + DEBUG ("Invalid filename provided by XDS drag site"); + g_free (prop_text); + return NULL; + } + + return (gchar *) prop_text; +} + +static void +set_direct_save_uri (GtkWidget *widget, + GdkDragContext *context, + NautilusDragInfo *drag_info, + int x, + int y) +{ + GFile *base, *child; + char *filename, *drop_target; + gchar *uri; + + drag_info->got_drop_data_type = TRUE; + drag_info->data_type = NAUTILUS_ICON_DND_XDNDDIRECTSAVE; + + uri = NULL; + + filename = get_direct_save_filename (context); + drop_target = nautilus_canvas_container_find_drop_target (NAUTILUS_CANVAS_CONTAINER (widget), + context, x, y, NULL); + + if (drop_target && eel_uri_is_trash (drop_target)) + { + g_free (drop_target); + drop_target = NULL; /* Cannot save to trash ...*/ + } + + if (filename != NULL && drop_target != NULL) + { + /* Resolve relative path */ + base = g_file_new_for_uri (drop_target); + child = g_file_get_child (base, filename); + uri = g_file_get_uri (child); + g_object_unref (base); + g_object_unref (child); + + /* Change the uri property */ + gdk_property_change (gdk_drag_context_get_source_window (context), + gdk_atom_intern (NAUTILUS_ICON_DND_XDNDDIRECTSAVE_TYPE, FALSE), + gdk_atom_intern ("text/plain", FALSE), 8, + GDK_PROP_MODE_REPLACE, (const guchar *) uri, + strlen (uri)); + + drag_info->direct_save_uri = uri; + } + + g_free (filename); + g_free (drop_target); +} + +/* FIXME bugzilla.gnome.org 47445: Needs to become a shared function */ +static void +get_data_on_first_target_we_support (GtkWidget *widget, + GdkDragContext *context, + guint32 time, + int x, + int y) +{ + GtkTargetList *list; + GdkAtom target; + + if (drop_types_list == NULL) + { + drop_types_list = gtk_target_list_new (drop_types, + G_N_ELEMENTS (drop_types) - 1); + gtk_target_list_add_text_targets (drop_types_list, NAUTILUS_ICON_DND_TEXT); + } + if (drop_types_list_root == NULL) + { + drop_types_list_root = gtk_target_list_new (drop_types, + G_N_ELEMENTS (drop_types)); + gtk_target_list_add_text_targets (drop_types_list_root, NAUTILUS_ICON_DND_TEXT); + } + + list = drop_types_list; + + target = gtk_drag_dest_find_target (widget, context, list); + if (target != GDK_NONE) + { + guint info; + NautilusDragInfo *drag_info; + gboolean found; + + drag_info = &(NAUTILUS_CANVAS_CONTAINER (widget)->details->dnd_info->drag_info); + + found = gtk_target_list_find (list, target, &info); + g_assert (found); + + /* Don't get_data for destructive ops */ + if ((info == NAUTILUS_ICON_DND_ROOTWINDOW_DROP || + info == NAUTILUS_ICON_DND_XDNDDIRECTSAVE) && + !drag_info->drop_occurred) + { + /* We can't call get_data here, because that would + * make the source execute the rootwin action or the direct save */ + drag_info->got_drop_data_type = TRUE; + drag_info->data_type = info; + } + else + { + if (info == NAUTILUS_ICON_DND_XDNDDIRECTSAVE) + { + set_direct_save_uri (widget, context, drag_info, x, y); + } + gtk_drag_get_data (GTK_WIDGET (widget), context, + target, time); + } + } +} + +static void +nautilus_canvas_container_ensure_drag_data (NautilusCanvasContainer *container, + GdkDragContext *context, + guint32 time) +{ + NautilusCanvasDndInfo *dnd_info; + + dnd_info = container->details->dnd_info; + + if (!dnd_info->drag_info.got_drop_data_type) + { + get_data_on_first_target_we_support (GTK_WIDGET (container), context, time, 0, 0); + } +} + +static void +drag_end_callback (GtkWidget *widget, + GdkDragContext *context, + gpointer data) +{ + NautilusCanvasContainer *container; + NautilusCanvasDndInfo *dnd_info; + NautilusWindow *window; + + container = NAUTILUS_CANVAS_CONTAINER (widget); + window = NAUTILUS_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (container))); + dnd_info = container->details->dnd_info; + + stop_cache_selection_list (&dnd_info->drag_info); + nautilus_drag_destroy_selection_list (dnd_info->drag_info.selection_list); + nautilus_drag_destroy_selection_list (dnd_info->drag_info.selection_cache); + nautilus_drag_destroy_selection_list (container->details->dnd_source_info->selection_cache); + dnd_info->drag_info.selection_list = NULL; + dnd_info->drag_info.selection_cache = NULL; + container->details->dnd_source_info->selection_cache = NULL; + + nautilus_window_end_dnd (window, context); +} + +static NautilusCanvasIcon * +nautilus_canvas_container_item_at (NautilusCanvasContainer *container, + int x, + int y) +{ + GList *p; + int size; + EelDRect point; + EelIRect canvas_point; + + /* build the hit-test rectangle. Base the size on the scale factor to ensure that it is + * non-empty even at the smallest scale factor + */ + + size = MAX (1, 1 + (1 / EEL_CANVAS (container)->pixels_per_unit)); + point.x0 = x; + point.y0 = y; + point.x1 = x + size; + point.y1 = y + size; + + for (p = container->details->icons; p != NULL; p = p->next) + { + NautilusCanvasIcon *icon; + icon = p->data; + + eel_canvas_w2c (EEL_CANVAS (container), + point.x0, + point.y0, + &canvas_point.x0, + &canvas_point.y0); + eel_canvas_w2c (EEL_CANVAS (container), + point.x1, + point.y1, + &canvas_point.x1, + &canvas_point.y1); + if (nautilus_canvas_item_hit_test_rectangle (icon->item, canvas_point)) + { + return icon; + } + } + + return NULL; +} + +static char * +get_container_uri (NautilusCanvasContainer *container) +{ + char *uri; + + /* get the URI associated with the container */ + uri = NULL; + g_signal_emit_by_name (container, "get-container-uri", &uri); + return uri; +} + +static gboolean +nautilus_canvas_container_selection_items_local (NautilusCanvasContainer *container, + GList *items) +{ + char *container_uri_string; + gboolean result; + + /* must have at least one item */ + g_assert (items); + + /* get the URI associated with the container */ + container_uri_string = get_container_uri (container); + + result = nautilus_drag_items_local (container_uri_string, items); + + g_free (container_uri_string); + + return result; +} + +/* handle dropped url */ +static void +receive_dropped_netscape_url (NautilusCanvasContainer *container, + const char *encoded_url, + GdkDragContext *context, + int x, + int y) +{ + char *drop_target; + + if (encoded_url == NULL) + { + return; + } + + drop_target = nautilus_canvas_container_find_drop_target (container, context, x, y, NULL); + + g_signal_emit_by_name (container, "handle-netscape-url", + encoded_url, + drop_target, + gdk_drag_context_get_selected_action (context)); + + g_free (drop_target); +} + +/* handle dropped uri list */ +static void +receive_dropped_uri_list (NautilusCanvasContainer *container, + const char *uri_list, + GdkDragContext *context, + int x, + int y) +{ + char *drop_target; + + if (uri_list == NULL) + { + return; + } + + drop_target = nautilus_canvas_container_find_drop_target (container, context, x, y, NULL); + + g_signal_emit_by_name (container, "handle-uri-list", + uri_list, + drop_target, + gdk_drag_context_get_selected_action (context)); + + g_free (drop_target); +} + +/* handle dropped text */ +static void +receive_dropped_text (NautilusCanvasContainer *container, + const char *text, + GdkDragContext *context, + int x, + int y) +{ + char *drop_target; + + if (text == NULL) + { + return; + } + + drop_target = nautilus_canvas_container_find_drop_target (container, context, x, y, NULL); + + g_signal_emit_by_name (container, "handle-text", + text, + drop_target, + gdk_drag_context_get_selected_action (context)); + + g_free (drop_target); +} + +/* handle dropped raw data */ +static void +receive_dropped_raw (NautilusCanvasContainer *container, + const char *raw_data, + int length, + const char *direct_save_uri, + GdkDragContext *context, + int x, + int y) +{ + char *drop_target; + + if (raw_data == NULL) + { + return; + } + + drop_target = nautilus_canvas_container_find_drop_target (container, context, x, y, NULL); + + g_signal_emit_by_name (container, "handle-raw", + raw_data, + length, + drop_target, + direct_save_uri, + gdk_drag_context_get_selected_action (context)); + + g_free (drop_target); +} + +static int +auto_scroll_timeout_callback (gpointer data) +{ + NautilusCanvasContainer *container; + GtkWidget *widget; + float x_scroll_delta, y_scroll_delta; + GdkRectangle exposed_area; + GtkAllocation allocation; + + g_assert (NAUTILUS_IS_CANVAS_CONTAINER (data)); + widget = GTK_WIDGET (data); + container = NAUTILUS_CANVAS_CONTAINER (widget); + + if (container->details->dnd_info->drag_info.waiting_to_autoscroll + && container->details->dnd_info->drag_info.start_auto_scroll_in > g_get_monotonic_time ()) + { + /* not yet */ + return TRUE; + } + + container->details->dnd_info->drag_info.waiting_to_autoscroll = FALSE; + + nautilus_drag_autoscroll_calculate_delta (widget, &x_scroll_delta, &y_scroll_delta); + if (x_scroll_delta == 0 && y_scroll_delta == 0) + { + /* no work */ + return TRUE; + } + + /* Clear the old dnd highlight frame */ + dnd_highlight_queue_redraw (widget); + + if (!nautilus_canvas_container_scroll (container, (int) x_scroll_delta, (int) y_scroll_delta)) + { + /* the scroll value got pinned to a min or max adjustment value, + * we ended up not scrolling + */ + return TRUE; + } + + /* Make sure the dnd highlight frame is redrawn */ + dnd_highlight_queue_redraw (widget); + + /* update cached drag start offsets */ + container->details->dnd_info->drag_info.start_x -= x_scroll_delta; + container->details->dnd_info->drag_info.start_y -= y_scroll_delta; + + /* Due to a glitch in GtkLayout, whe need to do an explicit draw of the exposed + * area. + * Calculate the size of the area we need to draw + */ + gtk_widget_get_allocation (widget, &allocation); + exposed_area.x = allocation.x; + exposed_area.y = allocation.y; + exposed_area.width = allocation.width; + exposed_area.height = allocation.height; + + if (x_scroll_delta > 0) + { + exposed_area.x = exposed_area.width - x_scroll_delta; + } + else if (x_scroll_delta < 0) + { + exposed_area.width = -x_scroll_delta; + } + + if (y_scroll_delta > 0) + { + exposed_area.y = exposed_area.height - y_scroll_delta; + } + else if (y_scroll_delta < 0) + { + exposed_area.height = -y_scroll_delta; + } + + /* offset it to 0, 0 */ + exposed_area.x -= allocation.x; + exposed_area.y -= allocation.y; + + gtk_widget_queue_draw_area (widget, + exposed_area.x, + exposed_area.y, + exposed_area.width, + exposed_area.height); + + return TRUE; +} + +static void +set_up_auto_scroll_if_needed (NautilusCanvasContainer *container) +{ + nautilus_drag_autoscroll_start (&container->details->dnd_info->drag_info, + GTK_WIDGET (container), + auto_scroll_timeout_callback, + container); +} + +static void +stop_auto_scroll (NautilusCanvasContainer *container) +{ + nautilus_drag_autoscroll_stop (&container->details->dnd_info->drag_info); +} + +static void +handle_nonlocal_move (NautilusCanvasContainer *container, + GdkDragAction action, + const char *target_uri, + gboolean icon_hit) +{ + GList *source_uris, *p; + gboolean free_target_uri; + + if (container->details->dnd_info->drag_info.selection_list == NULL) + { + return; + } + + source_uris = NULL; + for (p = container->details->dnd_info->drag_info.selection_list; p != NULL; p = p->next) + { + /* do a shallow copy of all the uri strings of the copied files */ + source_uris = g_list_prepend (source_uris, ((NautilusDragSelectionItem *) p->data)->uri); + } + source_uris = g_list_reverse (source_uris); + + free_target_uri = FALSE; + + /* start the copy */ + g_signal_emit_by_name (container, "move-copy-items", + source_uris, + target_uri, + action); + + if (free_target_uri) + { + g_free ((char *) target_uri); + } + + g_list_free (source_uris); +} + +static char * +nautilus_canvas_container_find_drop_target (NautilusCanvasContainer *container, + GdkDragContext *context, + int x, + int y, + gboolean *icon_hit) +{ + NautilusCanvasIcon *drop_target_icon; + double world_x, world_y; + NautilusFile *file; + char *icon_uri; + char *container_uri; + + if (icon_hit) + { + *icon_hit = FALSE; + } + + if (!container->details->dnd_info->drag_info.got_drop_data_type) + { + return NULL; + } + + canvas_widget_to_world (EEL_CANVAS (container), x, y, &world_x, &world_y); + + /* FIXME bugzilla.gnome.org 42485: + * These "can_accept_items" tests need to be done by + * the canvas view, not here. This file is not supposed to know + * that the target is a file. + */ + + /* Find the item we hit with our drop, if any */ + drop_target_icon = nautilus_canvas_container_item_at (container, world_x, world_y); + if (drop_target_icon != NULL) + { + icon_uri = nautilus_canvas_container_get_icon_uri (container, drop_target_icon); + if (icon_uri != NULL) + { + file = nautilus_file_get_by_uri (icon_uri); + + if (!nautilus_drag_can_accept_info (file, + container->details->dnd_info->drag_info.data_type, + container->details->dnd_info->drag_info.selection_list)) + { + /* the item we dropped our selection on cannot accept the items, + * do the same thing as if we just dropped the items on the canvas + */ + drop_target_icon = NULL; + } + + g_free (icon_uri); + nautilus_file_unref (file); + } + } + + if (drop_target_icon == NULL) + { + if (icon_hit) + { + *icon_hit = FALSE; + } + + container_uri = get_container_uri (container); + + if (container_uri != NULL) + { + gboolean can; + file = nautilus_file_get_by_uri (container_uri); + can = nautilus_drag_can_accept_info (file, + container->details->dnd_info->drag_info.data_type, + container->details->dnd_info->drag_info.selection_list); + g_object_unref (file); + if (!can) + { + g_free (container_uri); + container_uri = NULL; + } + } + + return container_uri; + } + + if (icon_hit) + { + *icon_hit = TRUE; + } + return nautilus_canvas_container_get_icon_drop_target_uri (container, drop_target_icon); +} + +static void +nautilus_canvas_container_receive_dropped_icons (NautilusCanvasContainer *container, + GdkDragContext *context, + int x, + int y) +{ + char *drop_target; + gboolean local_move_only; + double world_x, world_y; + gboolean icon_hit; + GdkDragAction action, real_action; + + drop_target = NULL; + + if (container->details->dnd_info->drag_info.selection_list == NULL) + { + return; + } + + real_action = gdk_drag_context_get_selected_action (context); + + if (real_action == GDK_ACTION_ASK) + { + action = GDK_ACTION_MOVE | GDK_ACTION_COPY | GDK_ACTION_LINK; + real_action = nautilus_drag_drop_action_ask (GTK_WIDGET (container), action); + } + + if (real_action > 0) + { + eel_canvas_window_to_world (EEL_CANVAS (container), + x + gtk_adjustment_get_value (gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (container))), + y + gtk_adjustment_get_value (gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (container))), + &world_x, &world_y); + + drop_target = nautilus_canvas_container_find_drop_target (container, + context, x, y, &icon_hit); + + local_move_only = FALSE; + if (!icon_hit && real_action == GDK_ACTION_MOVE) + { + local_move_only = nautilus_canvas_container_selection_items_local + (container, container->details->dnd_info->drag_info.selection_list); + } + + /* If the move is local, there is nothing to do. */ + if (!local_move_only) + { + handle_nonlocal_move (container, real_action, drop_target, icon_hit); + } + } + + g_free (drop_target); + stop_cache_selection_list (&container->details->dnd_info->drag_info); + nautilus_drag_destroy_selection_list (container->details->dnd_info->drag_info.selection_list); + container->details->dnd_info->drag_info.selection_list = NULL; +} + +NautilusDragInfo * +nautilus_canvas_dnd_get_drag_source_data (NautilusCanvasContainer *container, + GdkDragContext *context) +{ + return container->details->dnd_source_info; +} + +static void +nautilus_canvas_container_get_drop_action (NautilusCanvasContainer *container, + GdkDragContext *context, + int x, + int y, + int *action) +{ + char *drop_target; + gboolean icon_hit; + double world_x, world_y; + + icon_hit = FALSE; + if (!container->details->dnd_info->drag_info.got_drop_data_type) + { + /* drag_data_received_callback didn't get called yet */ + return; + } + + /* find out if we're over an canvas */ + canvas_widget_to_world (EEL_CANVAS (container), x, y, &world_x, &world_y); + *action = 0; + + drop_target = nautilus_canvas_container_find_drop_target (container, + context, x, y, &icon_hit); + if (drop_target == NULL) + { + return; + } + + /* case out on the type of object being dragged */ + switch (container->details->dnd_info->drag_info.data_type) + { + case NAUTILUS_ICON_DND_GNOME_ICON_LIST: + { + if (container->details->dnd_info->drag_info.selection_list != NULL) + { + nautilus_drag_default_drop_action_for_icons (context, drop_target, + container->details->dnd_info->drag_info.selection_list, + 0, + action); + } + } + break; + + case NAUTILUS_ICON_DND_URI_LIST: + { + *action = nautilus_drag_default_drop_action_for_uri_list (context, drop_target); + } + break; + + case NAUTILUS_ICON_DND_NETSCAPE_URL: + { + *action = nautilus_drag_default_drop_action_for_netscape_url (context); + } + break; + + case NAUTILUS_ICON_DND_ROOTWINDOW_DROP: + { + *action = gdk_drag_context_get_suggested_action (context); + } + break; + + case NAUTILUS_ICON_DND_TEXT: + case NAUTILUS_ICON_DND_XDNDDIRECTSAVE: + case NAUTILUS_ICON_DND_RAW: + { + *action = GDK_ACTION_COPY; + } + break; + } + + g_free (drop_target); +} + +static void +set_drop_target (NautilusCanvasContainer *container, + NautilusCanvasIcon *icon) +{ + NautilusCanvasIcon *old_icon; + + /* Check if current drop target changed, update icon drop + * higlight if needed. + */ + old_icon = container->details->drop_target; + if (icon == old_icon) + { + return; + } + + /* Remember the new drop target for the next round. */ + container->details->drop_target = icon; + nautilus_canvas_container_update_icon (container, old_icon); + nautilus_canvas_container_update_icon (container, icon); +} + +static void +nautilus_canvas_dnd_update_drop_target (NautilusCanvasContainer *container, + GdkDragContext *context, + int x, + int y) +{ + NautilusCanvasIcon *icon; + NautilusFile *file; + double world_x, world_y; + char *uri; + + g_assert (NAUTILUS_IS_CANVAS_CONTAINER (container)); + + canvas_widget_to_world (EEL_CANVAS (container), x, y, &world_x, &world_y); + + /* Find the item we hit with our drop, if any. */ + icon = nautilus_canvas_container_item_at (container, world_x, world_y); + + /* FIXME bugzilla.gnome.org 42485: + * These "can_accept_items" tests need to be done by + * the canvas view, not here. This file is not supposed to know + * that the target is a file. + */ + + /* Find if target canvas accepts our drop. */ + if (icon != NULL) + { + uri = nautilus_canvas_container_get_icon_uri (container, icon); + file = nautilus_file_get_by_uri (uri); + g_free (uri); + + if (!nautilus_drag_can_accept_info (file, + container->details->dnd_info->drag_info.data_type, + container->details->dnd_info->drag_info.selection_list)) + { + icon = NULL; + } + + nautilus_file_unref (file); + } + + set_drop_target (container, icon); +} + +static void +remove_hover_timer (NautilusCanvasDndInfo *dnd_info) +{ + if (dnd_info->hover_id != 0) + { + g_source_remove (dnd_info->hover_id); + dnd_info->hover_id = 0; + } +} + +static void +nautilus_canvas_container_free_drag_data (NautilusCanvasContainer *container) +{ + NautilusCanvasDndInfo *dnd_info; + + dnd_info = container->details->dnd_info; + + dnd_info->drag_info.got_drop_data_type = FALSE; + + if (dnd_info->shadow != NULL) + { + eel_canvas_item_destroy (dnd_info->shadow); + dnd_info->shadow = NULL; + } + + if (dnd_info->drag_info.selection_data != NULL) + { + gtk_selection_data_free (dnd_info->drag_info.selection_data); + dnd_info->drag_info.selection_data = NULL; + } + + if (dnd_info->drag_info.direct_save_uri != NULL) + { + g_free (dnd_info->drag_info.direct_save_uri); + dnd_info->drag_info.direct_save_uri = NULL; + } + + g_free (dnd_info->target_uri); + dnd_info->target_uri = NULL; + + remove_hover_timer (dnd_info); +} + +static void +drag_leave_callback (GtkWidget *widget, + GdkDragContext *context, + guint32 time, + gpointer data) +{ + NautilusCanvasDndInfo *dnd_info; + + dnd_info = NAUTILUS_CANVAS_CONTAINER (widget)->details->dnd_info; + + if (dnd_info->shadow != NULL) + { + eel_canvas_item_hide (dnd_info->shadow); + } + + stop_dnd_highlight (widget); + + set_drop_target (NAUTILUS_CANVAS_CONTAINER (widget), NULL); + stop_auto_scroll (NAUTILUS_CANVAS_CONTAINER (widget)); + nautilus_canvas_container_free_drag_data (NAUTILUS_CANVAS_CONTAINER (widget)); +} + +static void +drag_begin_callback (GtkWidget *widget, + GdkDragContext *context, + gpointer data) +{ + NautilusCanvasContainer *container; + NautilusDragInfo *drag_info; + NautilusWindow *window; + cairo_surface_t *surface; + double x1, y1, x2, y2, winx, winy; + int x_offset, y_offset; + int start_x, start_y; + GList *dragged_files; + double sx, sy; + + container = NAUTILUS_CANVAS_CONTAINER (widget); + window = NAUTILUS_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (container))); + + start_x = container->details->dnd_info->drag_info.start_x + + gtk_adjustment_get_value (gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (container))); + start_y = container->details->dnd_info->drag_info.start_y + + gtk_adjustment_get_value (gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (container))); + + /* create a pixmap and mask to drag with */ + surface = nautilus_canvas_item_get_drag_surface (container->details->drag_icon->item); + + /* compute the image's offset */ + eel_canvas_item_get_bounds (EEL_CANVAS_ITEM (container->details->drag_icon->item), + &x1, &y1, &x2, &y2); + eel_canvas_world_to_window (EEL_CANVAS (container), + x1, y1, &winx, &winy); + x_offset = start_x - winx; + y_offset = start_y - winy; + + cairo_surface_get_device_scale (surface, &sx, &sy); + cairo_surface_set_device_offset (surface, + -x_offset * sx, + -y_offset * sy); + gtk_drag_set_icon_surface (context, surface); + cairo_surface_destroy (surface); + + /* cache the data at the beginning since the view may change */ + drag_info = &(container->details->dnd_info->drag_info); + drag_info->selection_cache = nautilus_drag_create_selection_cache (widget, + each_icon_get_data_binder); + + container->details->dnd_source_info->selection_cache = nautilus_drag_create_selection_cache (widget, + each_icon_get_data_binder); + + dragged_files = nautilus_drag_file_list_from_selection_list (drag_info->selection_cache); + if (nautilus_file_list_are_all_folders (dragged_files)) + { + nautilus_window_start_dnd (window, context); + } + g_list_free_full (dragged_files, g_object_unref); +} + +void +nautilus_canvas_dnd_begin_drag (NautilusCanvasContainer *container, + GdkDragAction actions, + int button, + GdkEventMotion *event, + int start_x, + int start_y) +{ + NautilusCanvasDndInfo *dnd_info; + NautilusDragInfo *dnd_source_info; + + g_return_if_fail (NAUTILUS_IS_CANVAS_CONTAINER (container)); + g_return_if_fail (event != NULL); + + dnd_info = container->details->dnd_info; + container->details->dnd_source_info = g_new0 (NautilusDragInfo, 1); + dnd_source_info = container->details->dnd_source_info; + g_return_if_fail (dnd_info != NULL); + + /* Notice that the event is in bin_window coordinates, because of + * the way the canvas handles events. + */ + dnd_info->drag_info.start_x = start_x - + gtk_adjustment_get_value (gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (container))); + dnd_info->drag_info.start_y = start_y - + gtk_adjustment_get_value (gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (container))); + + dnd_source_info->source_actions = actions; + /* start the drag */ + gtk_drag_begin_with_coordinates (GTK_WIDGET (container), + dnd_info->drag_info.target_list, + actions, + button, + (GdkEvent *) event, + dnd_info->drag_info.start_x, + dnd_info->drag_info.start_y); +} + +static gboolean +drag_highlight_draw (GtkWidget *widget, + cairo_t *cr, + gpointer user_data) +{ + gint width, height; + GdkWindow *window; + GtkStyleContext *style; + + window = gtk_widget_get_window (widget); + width = gdk_window_get_width (window); + height = gdk_window_get_height (window); + + style = gtk_widget_get_style_context (widget); + + gtk_style_context_save (style); + gtk_style_context_add_class (style, GTK_STYLE_CLASS_DND); + gtk_style_context_set_state (style, GTK_STATE_FLAG_FOCUSED); + + gtk_render_frame (style, + cr, + 0, 0, width, height); + + gtk_style_context_restore (style); + + return FALSE; +} + +/* Queue a redraw of the dnd highlight rect */ +static void +dnd_highlight_queue_redraw (GtkWidget *widget) +{ + NautilusCanvasDndInfo *dnd_info; + int width, height; + GtkAllocation allocation; + + dnd_info = NAUTILUS_CANVAS_CONTAINER (widget)->details->dnd_info; + + if (!dnd_info->highlighted) + { + return; + } + + gtk_widget_get_allocation (widget, &allocation); + width = allocation.width; + height = allocation.height; + + /* we don't know how wide the shadow is exactly, + * so we expose a 10-pixel wide border + */ + gtk_widget_queue_draw_area (widget, + 0, 0, + width, 10); + gtk_widget_queue_draw_area (widget, + 0, 0, + 10, height); + gtk_widget_queue_draw_area (widget, + 0, height - 10, + width, 10); + gtk_widget_queue_draw_area (widget, + width - 10, 0, + 10, height); +} + +static void +start_dnd_highlight (GtkWidget *widget) +{ + NautilusCanvasDndInfo *dnd_info; + + dnd_info = NAUTILUS_CANVAS_CONTAINER (widget)->details->dnd_info; + + if (!dnd_info->highlighted) + { + dnd_info->highlighted = TRUE; + g_signal_connect_after (widget, "draw", + G_CALLBACK (drag_highlight_draw), + NULL); + dnd_highlight_queue_redraw (widget); + } +} + +static void +stop_dnd_highlight (GtkWidget *widget) +{ + NautilusCanvasDndInfo *dnd_info; + + dnd_info = NAUTILUS_CANVAS_CONTAINER (widget)->details->dnd_info; + + if (dnd_info->highlighted) + { + g_signal_handlers_disconnect_by_func (widget, + drag_highlight_draw, + NULL); + dnd_highlight_queue_redraw (widget); + dnd_info->highlighted = FALSE; + } +} + +static gboolean +hover_timer (gpointer user_data) +{ + NautilusCanvasContainer *container = user_data; + NautilusCanvasDndInfo *dnd_info; + + dnd_info = container->details->dnd_info; + + dnd_info->hover_id = 0; + + g_signal_emit_by_name (container, "handle-hover", dnd_info->target_uri); + + return FALSE; +} + +static void +check_hover_timer (NautilusCanvasContainer *container, + const char *uri) +{ + NautilusCanvasDndInfo *dnd_info; + GtkSettings *settings; + guint timeout; + + dnd_info = container->details->dnd_info; + + if (g_strcmp0 (uri, dnd_info->target_uri) == 0) + { + return; + } + + remove_hover_timer (dnd_info); + + settings = gtk_widget_get_settings (GTK_WIDGET (container)); + g_object_get (settings, "gtk-timeout-expand", &timeout, NULL); + + g_free (dnd_info->target_uri); + dnd_info->target_uri = NULL; + + if (uri != NULL) + { + dnd_info->target_uri = g_strdup (uri); + dnd_info->hover_id = + gdk_threads_add_timeout (timeout, + hover_timer, + container); + } +} + +static gboolean +drag_motion_callback (GtkWidget *widget, + GdkDragContext *context, + int x, + int y, + guint32 time) +{ + int action; + + nautilus_canvas_container_ensure_drag_data (NAUTILUS_CANVAS_CONTAINER (widget), context, time); + nautilus_canvas_container_position_shadow (NAUTILUS_CANVAS_CONTAINER (widget), x, y); + nautilus_canvas_dnd_update_drop_target (NAUTILUS_CANVAS_CONTAINER (widget), context, x, y); + set_up_auto_scroll_if_needed (NAUTILUS_CANVAS_CONTAINER (widget)); + /* Find out what the drop actions are based on our drag selection and + * the drop target. + */ + action = 0; + nautilus_canvas_container_get_drop_action (NAUTILUS_CANVAS_CONTAINER (widget), context, x, y, + &action); + if (action != 0) + { + char *uri; + uri = nautilus_canvas_container_find_drop_target (NAUTILUS_CANVAS_CONTAINER (widget), + context, x, y, NULL); + check_hover_timer (NAUTILUS_CANVAS_CONTAINER (widget), uri); + g_free (uri); + start_dnd_highlight (widget); + } + else + { + remove_hover_timer (NAUTILUS_CANVAS_CONTAINER (widget)->details->dnd_info); + } + + gdk_drag_status (context, action, time); + + return TRUE; +} + +static gboolean +drag_drop_callback (GtkWidget *widget, + GdkDragContext *context, + int x, + int y, + guint32 time, + gpointer data) +{ + NautilusCanvasDndInfo *dnd_info; + + dnd_info = NAUTILUS_CANVAS_CONTAINER (widget)->details->dnd_info; + + /* tell the drag_data_received callback that + * the drop occurred and that it can actually + * process the actions. + * make sure it is going to be called at least once. + */ + dnd_info->drag_info.drop_occurred = TRUE; + + get_data_on_first_target_we_support (widget, context, time, x, y); + + return TRUE; +} + +void +nautilus_canvas_dnd_end_drag (NautilusCanvasContainer *container) +{ + NautilusCanvasDndInfo *dnd_info; + + g_return_if_fail (NAUTILUS_IS_CANVAS_CONTAINER (container)); + + dnd_info = container->details->dnd_info; + g_return_if_fail (dnd_info != NULL); + stop_auto_scroll (container); + /* Do nothing. + * Can that possibly be right? + */ +} + +/** this callback is called in 2 cases. + * It is called upon drag_motion events to get the actual data + * In that case, it just makes sure it gets the data. + * It is called upon drop_drop events to execute the actual + * actions on the received action. In that case, it actually first makes sure + * that we have got the data then processes it. + */ + +static void +drag_data_received_callback (GtkWidget *widget, + GdkDragContext *context, + int x, + int y, + GtkSelectionData *data, + guint info, + guint32 time, + gpointer user_data) +{ + NautilusDragInfo *drag_info; + guchar *tmp; + const guchar *tmp_raw; + int length; + gboolean success; + + drag_info = &(NAUTILUS_CANVAS_CONTAINER (widget)->details->dnd_info->drag_info); + + drag_info->got_drop_data_type = TRUE; + drag_info->data_type = info; + + switch (info) + { + case NAUTILUS_ICON_DND_GNOME_ICON_LIST: + { + nautilus_canvas_container_dropped_canvas_feedback (widget, data, x, y); + } + break; + + case NAUTILUS_ICON_DND_URI_LIST: + case NAUTILUS_ICON_DND_TEXT: + case NAUTILUS_ICON_DND_XDNDDIRECTSAVE: + case NAUTILUS_ICON_DND_RAW: + { + /* Save the data so we can do the actual work on drop. */ + if (drag_info->selection_data != NULL) + { + gtk_selection_data_free (drag_info->selection_data); + } + drag_info->selection_data = gtk_selection_data_copy (data); + } + break; + + /* Netscape keeps sending us the data, even though we accept the first drag */ + case NAUTILUS_ICON_DND_NETSCAPE_URL: + { + if (drag_info->selection_data != NULL) + { + gtk_selection_data_free (drag_info->selection_data); + drag_info->selection_data = gtk_selection_data_copy (data); + } + } + break; + + case NAUTILUS_ICON_DND_ROOTWINDOW_DROP: + { + /* Do nothing, this won't even happen, since we don't want to call get_data twice */ + } + break; + } + + /* this is the second use case of this callback. + * we have to do the actual work for the drop. + */ + if (drag_info->drop_occurred) + { + success = FALSE; + switch (info) + { + case NAUTILUS_ICON_DND_GNOME_ICON_LIST: + { + nautilus_canvas_container_receive_dropped_icons + (NAUTILUS_CANVAS_CONTAINER (widget), + context, x, y); + } + break; + + case NAUTILUS_ICON_DND_NETSCAPE_URL: + { + receive_dropped_netscape_url + (NAUTILUS_CANVAS_CONTAINER (widget), + (char *) gtk_selection_data_get_data (data), context, x, y); + success = TRUE; + } + break; + + case NAUTILUS_ICON_DND_URI_LIST: + { + receive_dropped_uri_list + (NAUTILUS_CANVAS_CONTAINER (widget), + (char *) gtk_selection_data_get_data (data), context, x, y); + success = TRUE; + } + break; + + case NAUTILUS_ICON_DND_TEXT: + { + tmp = gtk_selection_data_get_text (data); + receive_dropped_text + (NAUTILUS_CANVAS_CONTAINER (widget), + (char *) tmp, context, x, y); + success = TRUE; + g_free (tmp); + } + break; + + case NAUTILUS_ICON_DND_RAW: + { + length = gtk_selection_data_get_length (data); + tmp_raw = gtk_selection_data_get_data (data); + receive_dropped_raw + (NAUTILUS_CANVAS_CONTAINER (widget), + (const gchar *) tmp_raw, length, drag_info->direct_save_uri, + context, x, y); + success = TRUE; + } + break; + + case NAUTILUS_ICON_DND_ROOTWINDOW_DROP: + { + /* Do nothing, everything is done by the sender */ + } + break; + + case NAUTILUS_ICON_DND_XDNDDIRECTSAVE: + { + const guchar *selection_data; + gint selection_length; + gint selection_format; + + selection_data = gtk_selection_data_get_data (drag_info->selection_data); + selection_length = gtk_selection_data_get_length (drag_info->selection_data); + selection_format = gtk_selection_data_get_format (drag_info->selection_data); + + if (selection_format == 8 && + selection_length == 1 && + selection_data[0] == 'F') + { + gtk_drag_get_data (widget, context, + gdk_atom_intern (NAUTILUS_ICON_DND_RAW_TYPE, + FALSE), + time); + return; + } + else if (selection_format == 8 && + selection_length == 1 && + selection_data[0] == 'F' && + drag_info->direct_save_uri != NULL) + { + GFile *location; + + location = g_file_new_for_uri (drag_info->direct_save_uri); + + nautilus_file_changes_queue_file_added (location); + g_object_unref (location); + nautilus_file_changes_consume_changes (TRUE); + success = TRUE; + } + } /* NAUTILUS_ICON_DND_XDNDDIRECTSAVE */ + break; + } + gtk_drag_finish (context, success, FALSE, time); + + nautilus_canvas_container_free_drag_data (NAUTILUS_CANVAS_CONTAINER (widget)); + + set_drop_target (NAUTILUS_CANVAS_CONTAINER (widget), NULL); + + /* reinitialise it for the next dnd */ + drag_info->drop_occurred = FALSE; + } +} + +void +nautilus_canvas_dnd_init (NautilusCanvasContainer *container) +{ + GtkTargetList *targets; + int n_elements; + + g_return_if_fail (container != NULL); + g_return_if_fail (NAUTILUS_IS_CANVAS_CONTAINER (container)); + + + container->details->dnd_info = g_new0 (NautilusCanvasDndInfo, 1); + nautilus_drag_init (&container->details->dnd_info->drag_info, + drag_types, G_N_ELEMENTS (drag_types), TRUE); + + /* Set up the widget as a drag destination. + * (But not a source, as drags starting from this widget will be + * implemented by dealing with events manually.) + */ + n_elements = G_N_ELEMENTS (drop_types) - 1; + gtk_drag_dest_set (GTK_WIDGET (container), + 0, + drop_types, n_elements, + GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK | GDK_ACTION_ASK); + + targets = gtk_drag_dest_get_target_list (GTK_WIDGET (container)); + gtk_target_list_add_text_targets (targets, NAUTILUS_ICON_DND_TEXT); + + + /* Messages for outgoing drag. */ + g_signal_connect (container, "drag-begin", + G_CALLBACK (drag_begin_callback), NULL); + g_signal_connect (container, "drag-data-get", + G_CALLBACK (drag_data_get_callback), NULL); + g_signal_connect (container, "drag-end", + G_CALLBACK (drag_end_callback), NULL); + + /* Messages for incoming drag. */ + g_signal_connect (container, "drag-data-received", + G_CALLBACK (drag_data_received_callback), NULL); + g_signal_connect (container, "drag-motion", + G_CALLBACK (drag_motion_callback), NULL); + g_signal_connect (container, "drag-drop", + G_CALLBACK (drag_drop_callback), NULL); + g_signal_connect (container, "drag-leave", + G_CALLBACK (drag_leave_callback), NULL); +} + +void +nautilus_canvas_dnd_fini (NautilusCanvasContainer *container) +{ + g_return_if_fail (NAUTILUS_IS_CANVAS_CONTAINER (container)); + + if (container->details->dnd_info != NULL) + { + stop_auto_scroll (container); + + nautilus_drag_finalize (&container->details->dnd_info->drag_info); + container->details->dnd_info = NULL; + } +} |