diff options
Diffstat (limited to 'libcaja-private/caja-icon-dnd.c')
-rw-r--r-- | libcaja-private/caja-icon-dnd.c | 2123 |
1 files changed, 2123 insertions, 0 deletions
diff --git a/libcaja-private/caja-icon-dnd.c b/libcaja-private/caja-icon-dnd.c new file mode 100644 index 00000000..b0e89942 --- /dev/null +++ b/libcaja-private/caja-icon-dnd.c @@ -0,0 +1,2123 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* caja-icon-dnd.c - Drag & drop handling for the icon container widget. + + Copyright (C) 1999, 2000 Free Software Foundation + Copyright (C) 2000 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: Ettore Perazzoli <[email protected]>, + Darin Adler <[email protected]>, + Andy Hertzfeld <[email protected]> + Pavel Cisler <[email protected]> + + + XDS support: Benedikt Meurer <[email protected]> (adapted by Amos Brocco <[email protected]>) + +*/ + + +#include <config.h> +#include <math.h> +#include "caja-icon-dnd.h" + +#include "caja-debug-log.h" +#include "caja-file-dnd.h" +#include "caja-icon-private.h" +#include "caja-link.h" +#include "caja-metadata.h" +#include <eel/eel-background.h> +#include <eel/eel-gdk-pixbuf-extensions.h> +#include <eel/eel-glib-extensions.h> +#include <eel/eel-mate-extensions.h> +#include <eel/eel-graphic-effects.h> +#include <eel/eel-gtk-extensions.h> +#include <eel/eel-gtk-macros.h> +#include <eel/eel-stock-dialogs.h> +#include <eel/eel-string.h> +#include <eel/eel-vfs-extensions.h> +#include <gdk/gdkkeysyms.h> +#include <gdk/gdkx.h> +#include <gtk/gtk.h> +#include <glib/gi18n.h> +#include <eel/eel-canvas-rect-ellipse.h> +#include <libcaja-private/caja-file-utilities.h> +#include <libcaja-private/caja-file-changes-queue.h> +#include <stdio.h> +#include <string.h> + +static const GtkTargetEntry drag_types [] = +{ + { CAJA_ICON_DND_MATE_ICON_LIST_TYPE, 0, CAJA_ICON_DND_MATE_ICON_LIST }, + { CAJA_ICON_DND_URI_LIST_TYPE, 0, CAJA_ICON_DND_URI_LIST }, +}; + +static const GtkTargetEntry drop_types [] = +{ + { CAJA_ICON_DND_MATE_ICON_LIST_TYPE, 0, CAJA_ICON_DND_MATE_ICON_LIST }, + /* prefer "_NETSCAPE_URL" over "text/uri-list" to satisfy web browsers. */ + { CAJA_ICON_DND_NETSCAPE_URL_TYPE, 0, CAJA_ICON_DND_NETSCAPE_URL }, + { CAJA_ICON_DND_URI_LIST_TYPE, 0, CAJA_ICON_DND_URI_LIST }, + { CAJA_ICON_DND_COLOR_TYPE, 0, CAJA_ICON_DND_COLOR }, + { CAJA_ICON_DND_BGIMAGE_TYPE, 0, CAJA_ICON_DND_BGIMAGE }, + { CAJA_ICON_DND_KEYWORD_TYPE, 0, CAJA_ICON_DND_KEYWORD }, + { CAJA_ICON_DND_RESET_BACKGROUND_TYPE, 0, CAJA_ICON_DND_RESET_BACKGROUND }, + { CAJA_ICON_DND_XDNDDIRECTSAVE_TYPE, 0, CAJA_ICON_DND_XDNDDIRECTSAVE }, /* XDS Protocol Type */ + { CAJA_ICON_DND_RAW_TYPE, 0, CAJA_ICON_DND_RAW }, + /* Must be last: */ + { CAJA_ICON_DND_ROOTWINDOW_DROP_TYPE, 0, CAJA_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 * caja_icon_container_find_drop_target (CajaIconContainer *container, + GdkDragContext *context, + int x, int y, gboolean *icon_hit, + gboolean rewrite_desktop); + +static EelCanvasItem * +create_selection_shadow (CajaIconContainer *container, + GList *list) +{ + EelCanvasGroup *group; + EelCanvas *canvas; + GdkBitmap *stipple; + 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; + } + + stipple = container->details->dnd_info->stipple; + g_return_val_if_fail (stipple != NULL, 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) + { + CajaDragSelectionItem *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, + eel_canvas_rect_get_type (), + "x1", (double) x1, + "y1", (double) y1, + "x2", (double) x2, + "y2", (double) y2, + "outline_color", "black", + "outline_stipple", stipple, + "width_pixels", 1, + 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; + CajaDragEachSelectedItemDataGet iteratee; + gpointer iteratee_data; +} IconGetDataBinderContext; + +static void +canvas_rect_world_to_widget (EelCanvas *canvas, + EelDRect *world_rect, + EelIRect *widget_rect) +{ + EelDRect window_rect; + + 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 (gtk_layout_get_hadjustment (GTK_LAYOUT (canvas))); + widget_rect->y0 = (int) window_rect.y0 - gtk_adjustment_get_value (gtk_layout_get_vadjustment (GTK_LAYOUT (canvas))); + widget_rect->x1 = (int) window_rect.x1 - gtk_adjustment_get_value (gtk_layout_get_hadjustment (GTK_LAYOUT (canvas))); + widget_rect->y1 = (int) window_rect.y1 - gtk_adjustment_get_value (gtk_layout_get_vadjustment (GTK_LAYOUT (canvas))); +} + +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_layout_get_hadjustment (GTK_LAYOUT (canvas))), + widget_y + gtk_adjustment_get_value (gtk_layout_get_vadjustment (GTK_LAYOUT (canvas))), + world_x, world_y); +} + +static gboolean +icon_get_data_binder (CajaIcon *icon, gpointer data) +{ + IconGetDataBinderContext *context; + EelDRect world_rect; + EelIRect widget_rect; + char *uri; + CajaIconContainer *container; + + context = (IconGetDataBinderContext *)data; + + g_assert (CAJA_IS_ICON_CONTAINER (context->iterator_context)); + + container = CAJA_ICON_CONTAINER (context->iterator_context); + + world_rect = caja_icon_canvas_item_get_icon_rectangle (icon->item); + + canvas_rect_world_to_widget (EEL_CANVAS (container), &world_rect, &widget_rect); + + uri = caja_icon_container_get_icon_uri (container, icon); + if (uri == NULL) + { + g_warning ("no URI for one of the iterated icons"); + 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); + + return TRUE; +} + +/* Iterate over each selected icon in a CajaIconContainer, + * calling each_function on each. + */ +static void +caja_icon_container_each_selected_icon (CajaIconContainer *container, + gboolean (*each_function) (CajaIcon *, gpointer), gpointer data) +{ + GList *p; + CajaIcon *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 caja_icon_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 (CajaDragEachSelectedItemDataGet iteratee, + gpointer iterator_context, gpointer data) +{ + IconGetDataBinderContext context; + CajaIconContainer *container; + + g_assert (CAJA_IS_ICON_CONTAINER (iterator_context)); + container = CAJA_ICON_CONTAINER (iterator_context); + + context.iterator_context = iterator_context; + context.iteratee = iteratee; + context.iteratee_data = data; + caja_icon_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) +{ + g_assert (widget != NULL); + g_assert (CAJA_IS_ICON_CONTAINER (widget)); + g_return_if_fail (context != NULL); + + /* Call common function from caja-drag that set's up + * the selection data in the right format. Pass it means to + * iterate all the selected icons. + */ + caja_drag_drag_data_get (widget, context, selection_data, + info, time, widget, each_icon_get_data_binder); +} + + +/* Target-side handling of the drag. */ + +static void +caja_icon_container_position_shadow (CajaIconContainer *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 +caja_icon_container_dropped_icon_feedback (GtkWidget *widget, + GtkSelectionData *data, + int x, int y) +{ + CajaIconContainer *container; + CajaIconDndInfo *dnd_info; + + container = CAJA_ICON_CONTAINER (widget); + dnd_info = container->details->dnd_info; + + /* Delete old selection list. */ + caja_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? */ + gtk_object_destroy (GTK_OBJECT (dnd_info->shadow)); + } + + /* Build the selection list and the shadow. */ + dnd_info->drag_info.selection_list = caja_drag_build_selection_list (data); + dnd_info->shadow = create_selection_shadow (container, dnd_info->drag_info.selection_list); + caja_icon_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 (CAJA_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) + { + caja_debug_log (FALSE, CAJA_DEBUG_LOG_DOMAIN_USER, + "Invalid filename provided by XDS drag site"); + g_free (prop_text); + return NULL; + } + + return prop_text; +} + +static void +set_direct_save_uri (GtkWidget *widget, GdkDragContext *context, CajaDragInfo *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 = CAJA_ICON_DND_XDNDDIRECTSAVE; + + uri = NULL; + + filename = get_direct_save_filename (context); + drop_target = caja_icon_container_find_drop_target (CAJA_ICON_CONTAINER (widget), + context, x, y, NULL, TRUE); + + 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_DRAWABLE (gdk_drag_context_get_source_window (context)), + gdk_atom_intern (CAJA_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, CAJA_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, CAJA_ICON_DND_TEXT); + } + + if (caja_icon_container_get_is_desktop (CAJA_ICON_CONTAINER (widget))) + { + list = drop_types_list_root; + } + else + { + list = drop_types_list; + } + + target = gtk_drag_dest_find_target (widget, context, list); + if (target != GDK_NONE) + { + guint info; + CajaDragInfo *drag_info; + gboolean found; + + drag_info = &(CAJA_ICON_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 == CAJA_ICON_DND_ROOTWINDOW_DROP || + info == CAJA_ICON_DND_XDNDDIRECTSAVE) && + !drag_info->drop_occured) + { + /* 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 == CAJA_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 +caja_icon_container_ensure_drag_data (CajaIconContainer *container, + GdkDragContext *context, + guint32 time) +{ + CajaIconDndInfo *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) +{ + CajaIconContainer *container; + CajaIconDndInfo *dnd_info; + + container = CAJA_ICON_CONTAINER (widget); + dnd_info = container->details->dnd_info; + + caja_drag_destroy_selection_list (dnd_info->drag_info.selection_list); + dnd_info->drag_info.selection_list = NULL; +} + +static CajaIcon * +caja_icon_container_item_at (CajaIconContainer *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) + { + CajaIcon *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 (caja_icon_canvas_item_hit_test_rectangle (icon->item, canvas_point)) + { + return icon; + } + } + + return NULL; +} + +static char * +get_container_uri (CajaIconContainer *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 +caja_icon_container_selection_items_local (CajaIconContainer *container, + GList *items) +{ + char *container_uri_string; + gboolean result; + + /* must have at least one item */ + g_assert (items); + + result = FALSE; + + /* get the URI associated with the container */ + container_uri_string = get_container_uri (container); + + if (eel_uri_is_desktop (container_uri_string)) + { + result = caja_drag_items_on_desktop (items); + } + else + { + result = caja_drag_items_local (container_uri_string, items); + } + g_free (container_uri_string); + + return result; +} + +static GdkDragAction +get_background_drag_action (CajaIconContainer *container, + GdkDragAction action) +{ + /* FIXME: This function is very FMDirectoryView specific, and + * should be moved out of caja-icon-dnd.c */ + GdkDragAction valid_actions; + + if (action == GDK_ACTION_ASK) + { + valid_actions = CAJA_DND_ACTION_SET_AS_FOLDER_BACKGROUND; + if (!eel_background_is_desktop (eel_get_widget_background (GTK_WIDGET (container)))) + { + valid_actions |= CAJA_DND_ACTION_SET_AS_GLOBAL_BACKGROUND; + } + + action = caja_drag_drop_background_ask + (GTK_WIDGET (container), valid_actions); + } + + return action; +} + +static void +receive_dropped_color (CajaIconContainer *container, + int x, int y, + GdkDragAction action, + GtkSelectionData *data) +{ + action = get_background_drag_action (container, action); + + if (action > 0) + { + char *uri; + + uri = get_container_uri (container); + caja_debug_log (FALSE, CAJA_DEBUG_LOG_DOMAIN_USER, + "dropped color on icon container displaying %s", uri); + g_free (uri); + + eel_background_receive_dropped_color + (eel_get_widget_background (GTK_WIDGET (container)), + GTK_WIDGET (container), + action, x, y, data); + } +} + +/* handle dropped tile images */ +static void +receive_dropped_tile_image (CajaIconContainer *container, GdkDragAction action, GtkSelectionData *data) +{ + g_assert (data != NULL); + + action = get_background_drag_action (container, action); + + if (action > 0) + { + char *uri; + + uri = get_container_uri (container); + caja_debug_log (FALSE, CAJA_DEBUG_LOG_DOMAIN_USER, + "dropped tile image on icon container displaying %s", uri); + g_free (uri); + + eel_background_receive_dropped_background_image + (eel_get_widget_background (GTK_WIDGET (container)), + action, + gtk_selection_data_get_data (data)); + } +} + +/* handle dropped keywords */ +static void +receive_dropped_keyword (CajaIconContainer *container, const char *keyword, int x, int y) +{ + char *uri; + double world_x, world_y; + + CajaIcon *drop_target_icon; + CajaFile *file; + + g_assert (keyword != NULL); + + /* find the item we hit with our drop, if any */ + canvas_widget_to_world (EEL_CANVAS (container), x, y, &world_x, &world_y); + drop_target_icon = caja_icon_container_item_at (container, world_x, world_y); + if (drop_target_icon == NULL) + { + return; + } + + /* FIXME bugzilla.gnome.org 42485: + * This does not belong in the icon code. + * It has to be in the file manager. + * The icon code has no right to deal with the file directly. + * But luckily there's no issue of not getting a file object, + * so we don't have to worry about async. issues here. + */ + uri = caja_icon_container_get_icon_uri (container, drop_target_icon); + + caja_debug_log (FALSE, CAJA_DEBUG_LOG_DOMAIN_USER, + "dropped emblem '%s' on icon container URI: %s", + keyword, uri); + + file = caja_file_get_by_uri (uri); + g_free (uri); + + caja_drag_file_receive_dropped_keyword (file, keyword); + + caja_file_unref (file); + caja_icon_container_update_icon (container, drop_target_icon); +} + +/* handle dropped url */ +static void +receive_dropped_netscape_url (CajaIconContainer *container, const char *encoded_url, GdkDragContext *context, int x, int y) +{ + char *drop_target; + + if (encoded_url == NULL) + { + return; + } + + drop_target = caja_icon_container_find_drop_target (container, context, x, y, NULL, TRUE); + + g_signal_emit_by_name (container, "handle_netscape_url", + encoded_url, + drop_target, + gdk_drag_context_get_selected_action (context), + x, y); + + g_free (drop_target); +} + +/* handle dropped uri list */ +static void +receive_dropped_uri_list (CajaIconContainer *container, const char *uri_list, GdkDragContext *context, int x, int y) +{ + char *drop_target; + + if (uri_list == NULL) + { + return; + } + + drop_target = caja_icon_container_find_drop_target (container, context, x, y, NULL, TRUE); + + g_signal_emit_by_name (container, "handle_uri_list", + uri_list, + drop_target, + gdk_drag_context_get_selected_action (context), + x, y); + + g_free (drop_target); +} + +/* handle dropped text */ +static void +receive_dropped_text (CajaIconContainer *container, const char *text, GdkDragContext *context, int x, int y) +{ + char *drop_target; + + if (text == NULL) + { + return; + } + + drop_target = caja_icon_container_find_drop_target (container, context, x, y, NULL, TRUE); + + g_signal_emit_by_name (container, "handle_text", + text, + drop_target, + gdk_drag_context_get_selected_action (context), + x, y); + + g_free (drop_target); +} + +/* handle dropped raw data */ +static void +receive_dropped_raw (CajaIconContainer *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 = caja_icon_container_find_drop_target (container, context, x, y, NULL, TRUE); + + g_signal_emit_by_name (container, "handle_raw", + raw_data, + length, + drop_target, + direct_save_uri, + gdk_drag_context_get_selected_action (context), + x, y); + + g_free (drop_target); +} + +static int +auto_scroll_timeout_callback (gpointer data) +{ + CajaIconContainer *container; + GtkWidget *widget; + float x_scroll_delta, y_scroll_delta; + GdkRectangle exposed_area; + GtkAllocation allocation; + + g_assert (CAJA_IS_ICON_CONTAINER (data)); + widget = GTK_WIDGET (data); + container = CAJA_ICON_CONTAINER (widget); + + if (container->details->dnd_info->drag_info.waiting_to_autoscroll + && container->details->dnd_info->drag_info.start_auto_scroll_in > eel_get_system_time()) + { + /* not yet */ + return TRUE; + } + + container->details->dnd_info->drag_info.waiting_to_autoscroll = FALSE; + + caja_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 (!caja_icon_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 (CajaIconContainer *container) +{ + caja_drag_autoscroll_start (&container->details->dnd_info->drag_info, + GTK_WIDGET (container), + auto_scroll_timeout_callback, + container); +} + +static void +stop_auto_scroll (CajaIconContainer *container) +{ + caja_drag_autoscroll_stop (&container->details->dnd_info->drag_info); +} + +static gboolean +confirm_switch_to_manual_layout (CajaIconContainer *container) +{ +#if 0 + const char *message; + const char *detail; + GtkDialog *dialog; + int response; + + /* FIXME bugzilla.gnome.org 40915: Use of the word "directory" + * makes this FMIconView specific. Move these messages into + * FMIconView so CajaIconContainer can be used for things + * that are not directories? + */ + if (caja_icon_container_has_stored_icon_positions (container)) + { + if (eel_g_list_exactly_one_item (container->details->dnd_info->drag_info.selection_list)) + { + message = no_translate("Do you want to switch to manual layout and leave this item where you dropped it? " + "This will clobber the stored manual layout."); + detail = no_translate("This folder uses automatic layout."); + } + else + { + message = no_translate("Do you want to switch to manual layout and leave these items where you dropped them? " + "This will clobber the stored manual layout."); + detail = no_translate("This folder uses automatic layout."); + } + } + else + { + if (eel_g_list_exactly_one_item (container->details->dnd_info->drag_info.selection_list)) + { + message = no_translate("Do you want to switch to manual layout and leave this item where you dropped it?"); + detail = no_translate("This folder uses automatic layout."); + } + else + { + message = no_translate("Do you want to switch to manual layout and leave these items where you dropped them?"); + detail = no_translate("This folder uses automatic layout."); + + } + } + + dialog = eel_show_yes_no_dialog (message, detail, _("Switch to Manual Layout?"), + GTK_STOCK_CANCEL, + GTK_WINDOW (gtk_widget_get_toplevel(GTK_WIDGET(container)))); + + response = gtk_dialog_run (dialog); + gtk_object_destroy (GTK_OBJECT (dialog)); + + return response == GTK_RESPONSE_YES; +#else + return FALSE; +#endif +} + +static void +handle_local_move (CajaIconContainer *container, + double world_x, double world_y) +{ + GList *moved_icons, *p; + CajaDragSelectionItem *item; + CajaIcon *icon; + CajaFile *file; + char screen_string[32]; + GdkScreen *screen; + time_t now; + + if (container->details->auto_layout) + { + if (!confirm_switch_to_manual_layout (container)) + { + return; + } + caja_icon_container_freeze_icon_positions (container); + } + + time (&now); + + /* Move and select the icons. */ + moved_icons = NULL; + for (p = container->details->dnd_info->drag_info.selection_list; p != NULL; p = p->next) + { + item = p->data; + + icon = caja_icon_container_get_icon_by_uri + (container, item->uri); + + if (icon == NULL) + { + /* probably dragged from another screen. Add it to + * this screen + */ + + file = caja_file_get_by_uri (item->uri); + + screen = gtk_widget_get_screen (GTK_WIDGET (container)); + g_snprintf (screen_string, sizeof (screen_string), "%d", + gdk_screen_get_number (screen)); + caja_file_set_metadata (file, + CAJA_METADATA_KEY_SCREEN, + NULL, screen_string); + caja_file_set_time_metadata (file, + CAJA_METADATA_KEY_ICON_POSITION_TIMESTAMP, now); + + caja_icon_container_add (container, CAJA_ICON_CONTAINER_ICON_DATA (file)); + + icon = caja_icon_container_get_icon_by_uri + (container, item->uri); + } + + if (item->got_icon_position) + { + caja_icon_container_move_icon + (container, icon, + world_x + item->icon_x, world_y + item->icon_y, + icon->scale, + TRUE, TRUE, TRUE); + } + moved_icons = g_list_prepend (moved_icons, icon); + } + caja_icon_container_select_list_unselect_others + (container, moved_icons); + /* Might have been moved in a way that requires adjusting scroll region. */ + caja_icon_container_update_scroll_region (container); + g_list_free (moved_icons); +} + +static void +handle_nonlocal_move (CajaIconContainer *container, + GdkDragAction action, + int x, int y, + const char *target_uri, + gboolean icon_hit) +{ + GList *source_uris, *p; + GArray *source_item_locations; + gboolean free_target_uri, is_rtl; + int index, item_x; + GtkAllocation allocation; + + 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, ((CajaDragSelectionItem *)p->data)->uri); + } + source_uris = g_list_reverse (source_uris); + + is_rtl = caja_icon_container_is_layout_rtl (container); + + source_item_locations = g_array_new (FALSE, TRUE, sizeof (GdkPoint)); + if (!icon_hit) + { + /* Drop onto a container. Pass along the item points to allow placing + * the items in their same relative positions in the new container. + */ + source_item_locations = g_array_set_size (source_item_locations, + g_list_length (container->details->dnd_info->drag_info.selection_list)); + + for (index = 0, p = container->details->dnd_info->drag_info.selection_list; + p != NULL; index++, p = p->next) + { + item_x = ((CajaDragSelectionItem *)p->data)->icon_x; + if (is_rtl) + item_x = -item_x - ((CajaDragSelectionItem *)p->data)->icon_width; + g_array_index (source_item_locations, GdkPoint, index).x = item_x; + g_array_index (source_item_locations, GdkPoint, index).y = + ((CajaDragSelectionItem *)p->data)->icon_y; + } + } + + free_target_uri = FALSE; + /* Rewrite internal desktop URIs to the normal target uri */ + if (eel_uri_is_desktop (target_uri)) + { + target_uri = caja_get_desktop_directory_uri (); + free_target_uri = TRUE; + } + + if (is_rtl) + { + gtk_widget_get_allocation (GTK_WIDGET (container), &allocation); + x = CANVAS_WIDTH (container, allocation) - x; + } + + /* start the copy */ + g_signal_emit_by_name (container, "move_copy_items", + source_uris, + source_item_locations, + target_uri, + action, + x, y); + + if (free_target_uri) + { + g_free ((char *)target_uri); + } + + g_list_free (source_uris); + g_array_free (source_item_locations, TRUE); +} + +static char * +caja_icon_container_find_drop_target (CajaIconContainer *container, + GdkDragContext *context, + int x, int y, + gboolean *icon_hit, + gboolean rewrite_desktop) +{ + CajaIcon *drop_target_icon; + double world_x, world_y; + CajaFile *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 icon 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 = caja_icon_container_item_at (container, world_x, world_y); + if (drop_target_icon != NULL) + { + icon_uri = caja_icon_container_get_icon_uri (container, drop_target_icon); + if (icon_uri != NULL) + { + file = caja_file_get_by_uri (icon_uri); + + if (!caja_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); + caja_file_unref (file); + } + } + + if (drop_target_icon == NULL) + { + if (icon_hit) + { + *icon_hit = FALSE; + } + + container_uri = get_container_uri (container); + + if (rewrite_desktop && + container_uri != NULL && + eel_uri_is_desktop (container_uri)) + { + g_free (container_uri); + container_uri = caja_get_desktop_directory_uri (); + } + + return container_uri; + } + + if (icon_hit) + { + *icon_hit = TRUE; + } + return caja_icon_container_get_icon_drop_target_uri (container, drop_target_icon); +} + +static gboolean +selection_is_image_file (GList *selection_list) +{ + const char *mime_type; + CajaDragSelectionItem *selected_item; + gboolean result; + GFile *location; + GFileInfo *info; + + /* Make sure only one item is selected */ + if (selection_list == NULL || + selection_list->next != NULL) + { + return FALSE; + } + + selected_item = selection_list->data; + + mime_type = NULL; + + location = g_file_new_for_uri (selected_item->uri); + info = g_file_query_info (location, + G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, + 0, NULL, NULL); + if (info) + { + mime_type = g_file_info_get_content_type (info); + } + + result = eel_istr_has_prefix (mime_type, "image/"); + + if (info) + { + g_object_unref (info); + } + g_object_unref (location); + + return result; +} + + +static void +caja_icon_container_receive_dropped_icons (CajaIconContainer *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; + CajaDragSelectionItem *selected_item; + + 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) + { + /* FIXME bugzilla.gnome.org 42485: This belongs in FMDirectoryView, not here. */ + /* Check for special case items in selection list */ + if (caja_drag_selection_includes_special_link (container->details->dnd_info->drag_info.selection_list)) + { + /* We only want to move the trash */ + action = GDK_ACTION_MOVE; + } + else + { + action = GDK_ACTION_MOVE | GDK_ACTION_COPY | GDK_ACTION_LINK; + + if (selection_is_image_file (container->details->dnd_info->drag_info.selection_list)) + { + action |= CAJA_DND_ACTION_SET_AS_BACKGROUND; + } + } + real_action = caja_drag_drop_action_ask + (GTK_WIDGET (container), action); + } + + if (real_action == (GdkDragAction) CAJA_DND_ACTION_SET_AS_BACKGROUND) + { + selected_item = container->details->dnd_info->drag_info.selection_list->data; + eel_background_receive_dropped_background_image + (eel_get_widget_background (GTK_WIDGET (container)), + real_action, + selected_item->uri); + return; + } + + if (real_action > 0) + { + eel_canvas_window_to_world (EEL_CANVAS (container), + x + gtk_adjustment_get_value (gtk_layout_get_hadjustment (GTK_LAYOUT (container))), + y + gtk_adjustment_get_value (gtk_layout_get_vadjustment (GTK_LAYOUT (container))), + &world_x, &world_y); + + drop_target = caja_icon_container_find_drop_target (container, + context, x, y, &icon_hit, FALSE); + + local_move_only = FALSE; + if (!icon_hit && real_action == GDK_ACTION_MOVE) + { + /* we can just move the icon positions if the move ended up in + * the item's parent container + */ + local_move_only = caja_icon_container_selection_items_local + (container, container->details->dnd_info->drag_info.selection_list); + } + + if (local_move_only) + { + handle_local_move (container, world_x, world_y); + } + else + { + handle_nonlocal_move (container, real_action, world_x, world_y, drop_target, icon_hit); + } + } + + g_free (drop_target); + caja_drag_destroy_selection_list (container->details->dnd_info->drag_info.selection_list); + container->details->dnd_info->drag_info.selection_list = NULL; +} + +static void +caja_icon_container_get_drop_action (CajaIconContainer *container, + GdkDragContext *context, + int x, int y, + int *action) +{ + char *drop_target; + gboolean icon_hit; + CajaIcon *icon; + 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 icon */ + canvas_widget_to_world (EEL_CANVAS (container), x, y, &world_x, &world_y); + + icon = caja_icon_container_item_at (container, world_x, world_y); + + *action = 0; + + /* case out on the type of object being dragged */ + switch (container->details->dnd_info->drag_info.data_type) + { + case CAJA_ICON_DND_MATE_ICON_LIST: + if (container->details->dnd_info->drag_info.selection_list == NULL) + { + return; + } + drop_target = caja_icon_container_find_drop_target (container, + context, x, y, &icon_hit, FALSE); + if (!drop_target) + { + return; + } + caja_drag_default_drop_action_for_icons (context, drop_target, + container->details->dnd_info->drag_info.selection_list, + action); + g_free (drop_target); + break; + case CAJA_ICON_DND_URI_LIST: + drop_target = caja_icon_container_find_drop_target (container, + context, x, y, &icon_hit, FALSE); + *action = caja_drag_default_drop_action_for_uri_list (context, drop_target); + + g_free (drop_target); + break; + + /* handle emblems by setting the action if we're over an object */ + case CAJA_ICON_DND_KEYWORD: + if (icon != NULL) + { + *action = gdk_drag_context_get_suggested_action (context); + } + break; + + case CAJA_ICON_DND_NETSCAPE_URL: + *action = caja_drag_default_drop_action_for_netscape_url (context); + break; + + case CAJA_ICON_DND_COLOR: + case CAJA_ICON_DND_BGIMAGE: + case CAJA_ICON_DND_RESET_BACKGROUND: + case CAJA_ICON_DND_ROOTWINDOW_DROP: + *action = gdk_drag_context_get_suggested_action (context); + break; + + case CAJA_ICON_DND_TEXT: + case CAJA_ICON_DND_XDNDDIRECTSAVE: + case CAJA_ICON_DND_RAW: + *action = GDK_ACTION_COPY; + break; + } +} + +static void +set_drop_target (CajaIconContainer *container, + CajaIcon *icon) +{ + CajaIcon *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; + caja_icon_container_update_icon (container, old_icon); + caja_icon_container_update_icon (container, icon); +} + +static void +caja_icon_dnd_update_drop_target (CajaIconContainer *container, + GdkDragContext *context, + int x, int y) +{ + CajaIcon *icon; + CajaFile *file; + double world_x, world_y; + char *uri; + + g_assert (CAJA_IS_ICON_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 = caja_icon_container_item_at (container, world_x, world_y); + + /* FIXME bugzilla.gnome.org 42485: + * These "can_accept_items" tests need to be done by + * the icon view, not here. This file is not supposed to know + * that the target is a file. + */ + + /* Find if target icon accepts our drop. */ + if (icon != NULL && (container->details->dnd_info->drag_info.data_type != CAJA_ICON_DND_KEYWORD)) + { + uri = caja_icon_container_get_icon_uri (container, icon); + file = caja_file_get_by_uri (uri); + g_free (uri); + + if (!caja_drag_can_accept_info (file, + container->details->dnd_info->drag_info.data_type, + container->details->dnd_info->drag_info.selection_list)) + { + icon = NULL; + } + + caja_file_unref (file); + } + + set_drop_target (container, icon); +} + +static void +caja_icon_container_free_drag_data (CajaIconContainer *container) +{ + CajaIconDndInfo *dnd_info; + + dnd_info = container->details->dnd_info; + + dnd_info->drag_info.got_drop_data_type = FALSE; + + if (dnd_info->shadow != NULL) + { + gtk_object_destroy (GTK_OBJECT (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; + } +} + +static void +drag_leave_callback (GtkWidget *widget, + GdkDragContext *context, + guint32 time, + gpointer data) +{ + CajaIconDndInfo *dnd_info; + + dnd_info = CAJA_ICON_CONTAINER (widget)->details->dnd_info; + + if (dnd_info->shadow != NULL) + eel_canvas_item_hide (dnd_info->shadow); + + stop_dnd_highlight (widget); + + set_drop_target (CAJA_ICON_CONTAINER (widget), NULL); + stop_auto_scroll (CAJA_ICON_CONTAINER (widget)); + caja_icon_container_free_drag_data(CAJA_ICON_CONTAINER (widget)); +} + +static void +drag_begin_callback (GtkWidget *widget, + GdkDragContext *context, + gpointer data) +{ + CajaIconContainer *container; + GdkScreen *screen; + GdkColormap *colormap; + GdkPixmap *pixmap; + GdkBitmap *mask; + double x1, y1, x2, y2, winx, winy; + int x_offset, y_offset; + int start_x, start_y; + gboolean use_mask; + + container = CAJA_ICON_CONTAINER (widget); + + screen = gtk_widget_get_screen (widget); + colormap = NULL; + if (gdk_screen_is_composited (screen)) + { + colormap = gdk_screen_get_rgba_colormap (screen); + if (colormap != NULL) + { + use_mask = FALSE; + } + } + + /* Fall back on using the same colormap as the widget */ + if (colormap == NULL) + { + colormap = gtk_widget_get_colormap (widget); + use_mask = TRUE; + } + + start_x = container->details->dnd_info->drag_info.start_x + gtk_adjustment_get_value (gtk_layout_get_hadjustment (GTK_LAYOUT (container))); + start_y = container->details->dnd_info->drag_info.start_y + gtk_adjustment_get_value (gtk_layout_get_vadjustment (GTK_LAYOUT (container))); + + /* create a pixmap and mask to drag with */ + pixmap = caja_icon_canvas_item_get_image (container->details->drag_icon->item, &mask, colormap); + + /* we want to drag semi-transparent pixbufs, but X is too slow dealing with + stippled masks, so we had to remove the code; this comment is left as a memorial + to it, with the hope that we get it back someday as X Windows improves */ + + /* 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; + + if (!use_mask && pixmap != NULL) + { + cairo_t *cr; + + /* If composite works, make the icons partially transparent */ + cr = gdk_cairo_create (pixmap); + cairo_set_operator (cr, CAIRO_OPERATOR_DEST_OUT); + cairo_set_source_rgba(cr, 1,0,0,0.35); + cairo_paint (cr); + cairo_destroy (cr); + } + + gtk_drag_set_icon_pixmap (context, + colormap, + pixmap, (use_mask ? mask : NULL), + x_offset, y_offset); +} + +void +caja_icon_dnd_begin_drag (CajaIconContainer *container, + GdkDragAction actions, + int button, + GdkEventMotion *event, + int start_x, + int start_y) +{ + CajaIconDndInfo *dnd_info; + GdkDragContext *context; + + g_return_if_fail (CAJA_IS_ICON_CONTAINER (container)); + g_return_if_fail (event != NULL); + + dnd_info = container->details->dnd_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_layout_get_hadjustment (GTK_LAYOUT (container))); + dnd_info->drag_info.start_y = start_y - gtk_adjustment_get_value (gtk_layout_get_vadjustment (GTK_LAYOUT (container))); + + /* start the drag */ + context = gtk_drag_begin (GTK_WIDGET (container), + dnd_info->drag_info.target_list, + actions, + button, + (GdkEvent *) event); +} + +static gboolean +drag_highlight_expose (GtkWidget *widget, + GdkEventExpose *event, + gpointer data) +{ + gint x, y, width, height; + GdkWindow *window; + + x = gtk_adjustment_get_value (gtk_layout_get_hadjustment (GTK_LAYOUT (widget))); + y = gtk_adjustment_get_value (gtk_layout_get_vadjustment (GTK_LAYOUT (widget))); + +#if GTK_CHECK_VERSION(3, 0, 0) + width = gdk_window_get_width(GDK_WINDOW(gtk_widget_get_window(widget))); + height = gdk_window_get_height(GDK_WINDOW(gtk_widget_get_window(widget))); +#else + gdk_drawable_get_size(gtk_widget_get_window(widget), &width, &height); +#endif + + window = gtk_layout_get_bin_window (GTK_LAYOUT (widget)); + + gtk_paint_shadow (gtk_widget_get_style (widget), window, + GTK_STATE_NORMAL, GTK_SHADOW_OUT, + NULL, widget, "dnd", + x, y, width, height); + + gdk_draw_rectangle (window, + (gtk_widget_get_style(widget))->black_gc, + FALSE, + x, y, width - 1, height - 1); + + return FALSE; +} + +/* Queue a redraw of the dnd highlight rect */ +static void +dnd_highlight_queue_redraw (GtkWidget *widget) +{ + CajaIconDndInfo *dnd_info; + int width, height; + GtkAllocation allocation; + + dnd_info = CAJA_ICON_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) +{ + CajaIconDndInfo *dnd_info; + GtkWidget *toplevel; + + dnd_info = CAJA_ICON_CONTAINER (widget)->details->dnd_info; + + toplevel = gtk_widget_get_toplevel (widget); + if (toplevel != NULL && + g_object_get_data (G_OBJECT (toplevel), "is_desktop_window")) + { + return; + } + + if (!dnd_info->highlighted) + { + dnd_info->highlighted = TRUE; + g_signal_connect_after (widget, "expose_event", + G_CALLBACK (drag_highlight_expose), + NULL); + dnd_highlight_queue_redraw (widget); + } +} + +static void +stop_dnd_highlight (GtkWidget *widget) +{ + CajaIconDndInfo *dnd_info; + + dnd_info = CAJA_ICON_CONTAINER (widget)->details->dnd_info; + + if (dnd_info->highlighted) + { + g_signal_handlers_disconnect_by_func (widget, + drag_highlight_expose, + NULL); + dnd_highlight_queue_redraw (widget); + dnd_info->highlighted = FALSE; + } +} + +static gboolean +drag_motion_callback (GtkWidget *widget, + GdkDragContext *context, + int x, int y, + guint32 time) +{ + int action; + + caja_icon_container_ensure_drag_data (CAJA_ICON_CONTAINER (widget), context, time); + caja_icon_container_position_shadow (CAJA_ICON_CONTAINER (widget), x, y); + caja_icon_dnd_update_drop_target (CAJA_ICON_CONTAINER (widget), context, x, y); + set_up_auto_scroll_if_needed (CAJA_ICON_CONTAINER (widget)); + /* Find out what the drop actions are based on our drag selection and + * the drop target. + */ + action = 0; + caja_icon_container_get_drop_action (CAJA_ICON_CONTAINER (widget), context, x, y, + &action); + if (action != 0) + { + start_dnd_highlight (widget); + } + + 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) +{ + CajaIconDndInfo *dnd_info; + + dnd_info = CAJA_ICON_CONTAINER (widget)->details->dnd_info; + + /* tell the drag_data_received callback that + the drop occured and that it can actually + process the actions. + make sure it is going to be called at least once. + */ + dnd_info->drag_info.drop_occured = TRUE; + + get_data_on_first_target_we_support (widget, context, time, x, y); + + return TRUE; +} + +void +caja_icon_dnd_end_drag (CajaIconContainer *container) +{ + CajaIconDndInfo *dnd_info; + + g_return_if_fail (CAJA_IS_ICON_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) +{ + CajaDragInfo *drag_info; + EelBackground *background; + char *tmp; + const char *tmp_raw; + int length; + gboolean success; + + drag_info = &(CAJA_ICON_CONTAINER (widget)->details->dnd_info->drag_info); + + drag_info->got_drop_data_type = TRUE; + drag_info->data_type = info; + + switch (info) + { + case CAJA_ICON_DND_MATE_ICON_LIST: + caja_icon_container_dropped_icon_feedback (widget, data, x, y); + break; + case CAJA_ICON_DND_COLOR: + case CAJA_ICON_DND_BGIMAGE: + case CAJA_ICON_DND_KEYWORD: + case CAJA_ICON_DND_URI_LIST: + case CAJA_ICON_DND_TEXT: + case CAJA_ICON_DND_RESET_BACKGROUND: + case CAJA_ICON_DND_XDNDDIRECTSAVE: + case CAJA_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 CAJA_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 CAJA_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_occured) + { + + success = FALSE; + switch (info) + { + case CAJA_ICON_DND_MATE_ICON_LIST: + caja_icon_container_receive_dropped_icons + (CAJA_ICON_CONTAINER (widget), + context, x, y); + break; + case CAJA_ICON_DND_COLOR: + receive_dropped_color (CAJA_ICON_CONTAINER (widget), + x, y, + gdk_drag_context_get_selected_action (context), + data); + success = TRUE; + break; + case CAJA_ICON_DND_BGIMAGE: + receive_dropped_tile_image + (CAJA_ICON_CONTAINER (widget), + gdk_drag_context_get_selected_action (context), + data); + break; + case CAJA_ICON_DND_KEYWORD: + receive_dropped_keyword + (CAJA_ICON_CONTAINER (widget), + (char *) gtk_selection_data_get_data (data), x, y); + break; + case CAJA_ICON_DND_NETSCAPE_URL: + receive_dropped_netscape_url + (CAJA_ICON_CONTAINER (widget), + (char *) gtk_selection_data_get_data (data), context, x, y); + success = TRUE; + break; + case CAJA_ICON_DND_URI_LIST: + receive_dropped_uri_list + (CAJA_ICON_CONTAINER (widget), + (char *) gtk_selection_data_get_data (data), context, x, y); + success = TRUE; + break; + case CAJA_ICON_DND_TEXT: + tmp = gtk_selection_data_get_text (data); + receive_dropped_text + (CAJA_ICON_CONTAINER (widget), + (char *) tmp, context, x, y); + success = TRUE; + g_free (tmp); + break; + case CAJA_ICON_DND_RAW: + length = gtk_selection_data_get_length (data); + tmp_raw = gtk_selection_data_get_data (data); + receive_dropped_raw + (CAJA_ICON_CONTAINER (widget), + tmp_raw, length, drag_info->direct_save_uri, + context, x, y); + success = TRUE; + break; + case CAJA_ICON_DND_RESET_BACKGROUND: + background = eel_get_widget_background (widget); + if (background != NULL) + { + eel_background_reset (background); + } + gtk_drag_finish (context, FALSE, FALSE, time); + break; + case CAJA_ICON_DND_ROOTWINDOW_DROP: + /* Do nothing, everything is done by the sender */ + break; + case CAJA_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 (CAJA_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) + { + GdkPoint p; + GFile *location; + + location = g_file_new_for_uri (drag_info->direct_save_uri); + + caja_file_changes_queue_file_added (location); + p.x = x; + p.y = y; + caja_file_changes_queue_schedule_position_set ( + location, + p, + gdk_screen_get_number ( + gtk_widget_get_screen (widget))); + g_object_unref (location); + caja_file_changes_consume_changes (TRUE); + success = TRUE; + } + break; + } /* CAJA_ICON_DND_XDNDDIRECTSAVE */ + } + gtk_drag_finish (context, success, FALSE, time); + + caja_icon_container_free_drag_data (CAJA_ICON_CONTAINER (widget)); + + set_drop_target (CAJA_ICON_CONTAINER (widget), NULL); + + /* reinitialise it for the next dnd */ + drag_info->drop_occured = FALSE; + } + +} + +void +caja_icon_dnd_set_stipple (CajaIconContainer *container, + GdkBitmap *stipple) +{ + if (stipple != NULL) + { + g_object_ref (stipple); + } + + if (container->details->dnd_info->stipple != NULL) + { + g_object_unref (container->details->dnd_info->stipple); + } + + container->details->dnd_info->stipple = stipple; +} + +void +caja_icon_dnd_init (CajaIconContainer *container, + GdkBitmap *stipple) +{ + GtkTargetList *targets; + int n_elements; + + g_return_if_fail (container != NULL); + g_return_if_fail (CAJA_IS_ICON_CONTAINER (container)); + + + container->details->dnd_info = g_new0 (CajaIconDndInfo, 1); + caja_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); + if (!caja_icon_container_get_is_desktop (container)) + { + /* Don't set up rootwindow drop */ + n_elements -= 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, CAJA_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); + + if (stipple != NULL) + { + container->details->dnd_info->stipple = g_object_ref (stipple); + } +} + +void +caja_icon_dnd_fini (CajaIconContainer *container) +{ + g_return_if_fail (CAJA_IS_ICON_CONTAINER (container)); + + if (container->details->dnd_info != NULL) + { + stop_auto_scroll (container); + + if (container->details->dnd_info->stipple != NULL) + { + g_object_unref (container->details->dnd_info->stipple); + } + + caja_drag_finalize (&container->details->dnd_info->drag_info); + container->details->dnd_info = NULL; + } +} |