mirror of
https://gitlab.gnome.org/GNOME/nautilus
synced 2024-11-05 16:04:31 +00:00
e26bd56e88
Fixed bug 2068 (Dragging icons adjusts scroll area in a way that causes immediate scrolling) Fixed bug 3402 (Dragging singleton icon to edge of window in manual layout mode puts it back at top-left) Fixed bug 3405 (Dropping an icon into empty manual-layout window always puts it at top-left) Fixed bug 3570 (Relayout glitch after removing an item from a view) I had earlier started to fix these by trying to save and restore the scroll position per directory. But this path turned evil, and I gave up on it. There is still some partly-implemented work from my start in the evil direction, which I will remove in my next checkin. The new fix was to always include the visible white space when recomputing the icon container's scroll region, unless a caller has specifically requested not to. * libnautilus-extensions/nautilus-icon-container.h: * libnautilus-extensions/nautilus-icon-container.c: (nautilus_icon_container_reset_scroll_region): New function, sets a flag in the details struct that's respected in the next scroll region update. (reset_scroll_region_if_not_empty): New helper function, calls _reset_scroll_region unless container is empty. (nautilus_icon_container_update_scroll_region_include_visible_area): Removed this function; its equivalent is to call reset_scroll_region before calling update_scroll_region. (nautilus_icon_container_update_scroll_region): Now that the _include_visible_area variant is gone, merged the local function update_scroll_region with this public one. It decides whether to include the visible area or not based on the flag set by _reset_scroll_region (also, it never includes the visible area if the container is empty). (nautilus_icon_container_set_auto_layout): Reset the scroll region when changing the auto_layout state. (nautilus_icon_container_sort): reset the scroll region before sorting. * libnautilus-extensions/nautilus-icon-dnd.c: (handle_local_move): Call nautilus_icon_container_update_scroll_region instead of _update_scroll_region_include_visible_area, which no longer exists. * libnautilus-extensions/nautilus-icon-private.h: Store flag for resetting scroll region in details; eliminate prototype for nautilus_icon_container_update_scroll_region_include_visible_area. * src/file-manager/fm-directory-view.h: * src/file-manager/fm-directory-view.c: (fm_directory_view_initialize_class), (done_loading), (fm_directory_view_begin_loading), (fm_directory_view_end_loading): Added end_loading signal that's sent when a directory is finished loading, parallel to existing begin_loading signal. * src/file-manager/fm-icon-view.c: (fm_icon_view_add_file): Reset the scroll region for the first icon added when loading a directory. (fm_icon_view_begin_loading), (fm_icon_view_end_loading): Remember that we're loading, so fm_icon_view_add_file can test correctly. (set_sort_criterion_by_id): Bail out early if state hasn't changed. (switch_to_manual_layout): Bail out early if state hasn't changed. Also, reset scroll region before switching to manual layout. (fm_icon_view_initialize_class): Attach fm_icon_view_end_loading to virtual function slot.
1342 lines
39 KiB
C
1342 lines
39 KiB
C
/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
|
|
|
|
/* nautilus-icon-dnd.c - Drag & drop handling for the icon 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,
|
|
write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
|
Boston, MA 02111-1307, USA.
|
|
|
|
Authors: Ettore Perazzoli <ettore@gnu.org>,
|
|
Darin Adler <darin@eazel.com>,
|
|
Andy Hertzfeld <andy@eazel.com>
|
|
Pavel Cisler <pavel@eazel.com>
|
|
*/
|
|
|
|
|
|
#include <config.h>
|
|
#include "nautilus-icon-dnd.h"
|
|
|
|
#include "nautilus-background.h"
|
|
#include "nautilus-file-utilities.h"
|
|
#include "nautilus-gdk-pixbuf-extensions.h"
|
|
#include "nautilus-glib-extensions.h"
|
|
#include "nautilus-gnome-extensions.h"
|
|
#include "nautilus-graphic-effects.h"
|
|
#include "nautilus-gtk-extensions.h"
|
|
#include "nautilus-gtk-macros.h"
|
|
#include "nautilus-icon-private.h"
|
|
#include "nautilus-link.h"
|
|
#include "nautilus-stock-dialogs.h"
|
|
#include "nautilus-string.h"
|
|
#include <gdk/gdkkeysyms.h>
|
|
#include <gdk/gdkx.h>
|
|
#include <gtk/gtkmain.h>
|
|
#include <gtk/gtksignal.h>
|
|
#include <libgnome/gnome-i18n.h>
|
|
#include <libgnome/gnome-mime.h>
|
|
#include <libgnomeui/gnome-canvas-rect-ellipse.h>
|
|
#include <libgnomeui/gnome-stock.h>
|
|
#include <libgnomeui/gnome-uidefs.h>
|
|
#include <libgnomevfs/gnome-vfs-uri.h>
|
|
#include <libgnomevfs/gnome-vfs-utils.h>
|
|
#include <math.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
static gboolean drag_drop_callback (GtkWidget *widget,
|
|
GdkDragContext *context,
|
|
int x,
|
|
int y,
|
|
guint32 time,
|
|
gpointer data);
|
|
static void nautilus_icon_dnd_update_drop_target (NautilusIconContainer *container,
|
|
GdkDragContext *context,
|
|
int x,
|
|
int y);
|
|
static gboolean drag_motion_callback (GtkWidget *widget,
|
|
GdkDragContext *context,
|
|
int x,
|
|
int y,
|
|
guint32 time);
|
|
|
|
static void nautilus_icon_container_receive_dropped_icons (NautilusIconContainer *container,
|
|
GdkDragContext *context,
|
|
int x, int y);
|
|
static void receive_dropped_tile_image (NautilusIconContainer *container,
|
|
GtkSelectionData *data);
|
|
static void receive_dropped_keyword (NautilusIconContainer *container,
|
|
char* keyword,
|
|
int x,
|
|
int y);
|
|
static void receive_dropped_uri_list (NautilusIconContainer *container,
|
|
char* keyword,
|
|
int x,
|
|
int y);
|
|
static void nautilus_icon_container_free_drag_data (NautilusIconContainer *container);
|
|
static void set_drop_target (NautilusIconContainer *container,
|
|
NautilusIcon *icon);
|
|
|
|
|
|
static 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 },
|
|
{ NAUTILUS_ICON_DND_URL_TYPE, 0, NAUTILUS_ICON_DND_URL },
|
|
{ NAUTILUS_ICON_DND_TEXT_TYPE, 0, NAUTILUS_ICON_DND_TEXT }
|
|
};
|
|
|
|
static GtkTargetEntry drop_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 },
|
|
{ NAUTILUS_ICON_DND_URL_TYPE, 0, NAUTILUS_ICON_DND_URL },
|
|
{ NAUTILUS_ICON_DND_COLOR_TYPE, 0, NAUTILUS_ICON_DND_COLOR },
|
|
{ NAUTILUS_ICON_DND_BGIMAGE_TYPE, 0, NAUTILUS_ICON_DND_BGIMAGE },
|
|
{ NAUTILUS_ICON_DND_KEYWORD_TYPE, 0, NAUTILUS_ICON_DND_KEYWORD }
|
|
};
|
|
|
|
|
|
static GnomeCanvasItem *
|
|
create_selection_shadow (NautilusIconContainer *container,
|
|
GList *list)
|
|
{
|
|
GnomeCanvasGroup *group;
|
|
GnomeCanvas *canvas;
|
|
GdkBitmap *stipple;
|
|
int max_x, max_y;
|
|
int min_x, min_y;
|
|
GList *p;
|
|
double pixels_per_unit;
|
|
|
|
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->drag_info.stipple;
|
|
g_return_val_if_fail (stipple != NULL, NULL);
|
|
|
|
canvas = GNOME_CANVAS (container);
|
|
|
|
/* 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 = GTK_WIDGET (container)->allocation.width;
|
|
min_x = -max_x;
|
|
|
|
max_y = GTK_WIDGET (container)->allocation.height;
|
|
min_y = -max_y;
|
|
|
|
/* Create a group, so that it's easier to move all the items around at
|
|
once. */
|
|
group = GNOME_CANVAS_GROUP
|
|
(gnome_canvas_item_new (GNOME_CANVAS_GROUP (canvas->root),
|
|
gnome_canvas_group_get_type (),
|
|
NULL));
|
|
|
|
pixels_per_unit = canvas->pixels_per_unit;
|
|
for (p = list; p != NULL; p = p->next) {
|
|
DragSelectionItem *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)
|
|
gnome_canvas_item_new
|
|
(group,
|
|
gnome_canvas_rect_get_type (),
|
|
"x1", (double) x1 / pixels_per_unit,
|
|
"y1", (double) y1 / pixels_per_unit,
|
|
"x2", (double) x2 / pixels_per_unit,
|
|
"y2", (double) y2 / pixels_per_unit,
|
|
"outline_color", "black",
|
|
"outline_stipple", stipple,
|
|
"width_pixels", 1,
|
|
NULL);
|
|
}
|
|
|
|
return GNOME_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 (GnomeCanvasItem *shadow,
|
|
double x, double y)
|
|
{
|
|
double affine[6];
|
|
|
|
affine[0] = 1.0;
|
|
affine[1] = 0.0;
|
|
affine[2] = 0.0;
|
|
affine[3] = 1.0;
|
|
affine[4] = x;
|
|
affine[5] = y;
|
|
|
|
gnome_canvas_item_affine_absolute (shadow, affine);
|
|
}
|
|
|
|
|
|
/* Source-side handling of the drag. */
|
|
|
|
/* iteration glue struct */
|
|
typedef struct {
|
|
gpointer iterator_context;
|
|
NautilusDragEachSelectedItemDataGet iteratee;
|
|
gpointer iteratee_data;
|
|
} IconGetDataBinderContext;
|
|
|
|
static gboolean
|
|
icon_get_data_binder (NautilusIcon *icon, gpointer data)
|
|
{
|
|
IconGetDataBinderContext *context;
|
|
ArtDRect world_rect;
|
|
ArtIRect window_rect;
|
|
char *uri;
|
|
NautilusIconContainer *container;
|
|
|
|
context = (IconGetDataBinderContext *)data;
|
|
|
|
g_assert (NAUTILUS_IS_ICON_CONTAINER (context->iterator_context));
|
|
|
|
container = NAUTILUS_ICON_CONTAINER (context->iterator_context);
|
|
|
|
nautilus_icon_canvas_item_get_icon_rectangle
|
|
(icon->item, &world_rect);
|
|
nautilus_gnome_canvas_world_to_window_rectangle
|
|
(GNOME_CANVAS (container), &world_rect, &window_rect);
|
|
|
|
uri = nautilus_icon_container_get_icon_uri (container, icon);
|
|
if (uri == NULL) {
|
|
g_warning ("no URI for one of the iterated icons");
|
|
return TRUE;
|
|
}
|
|
|
|
/* pass the uri, mouse-relative x/y and icon width/height */
|
|
context->iteratee (uri,
|
|
(int) (window_rect.x0 - container->details->dnd_info->drag_info.start_x),
|
|
(int) (window_rect.y0 - container->details->dnd_info->drag_info.start_y),
|
|
window_rect.x1 - window_rect.x0,
|
|
window_rect.y1 - window_rect.y0,
|
|
context->iteratee_data);
|
|
|
|
g_free (uri);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/* Iterate over each selected icon in a NautilusIconContainer,
|
|
* calling each_function on each.
|
|
*/
|
|
static void
|
|
nautilus_icon_container_each_selected_icon (NautilusIconContainer *container,
|
|
gboolean (*each_function) (NautilusIcon *, gpointer), gpointer data)
|
|
{
|
|
GList *p;
|
|
NautilusIcon *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_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 (NautilusDragEachSelectedItemDataGet iteratee,
|
|
gpointer iterator_context, gpointer data)
|
|
{
|
|
IconGetDataBinderContext context;
|
|
NautilusIconContainer *container;
|
|
|
|
g_assert (NAUTILUS_IS_ICON_CONTAINER (iterator_context));
|
|
container = NAUTILUS_ICON_CONTAINER (iterator_context);
|
|
|
|
context.iterator_context = iterator_context;
|
|
context.iteratee = iteratee;
|
|
context.iteratee_data = data;
|
|
nautilus_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 (NAUTILUS_IS_ICON_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.
|
|
*/
|
|
nautilus_drag_drag_data_get (widget, context, selection_data,
|
|
info, time, widget, each_icon_get_data_binder);
|
|
}
|
|
|
|
|
|
/* Target-side handling of the drag. */
|
|
|
|
static void
|
|
nautilus_icon_container_position_shadow (NautilusIconContainer *container,
|
|
int x, int y)
|
|
{
|
|
GnomeCanvasItem *shadow;
|
|
double world_x, world_y;
|
|
|
|
shadow = container->details->dnd_info->shadow;
|
|
if (shadow == NULL) {
|
|
return;
|
|
}
|
|
gnome_canvas_window_to_world (GNOME_CANVAS (container),
|
|
x, y, &world_x, &world_y);
|
|
|
|
set_shadow_position (shadow, world_x, world_y);
|
|
gnome_canvas_item_show (shadow);
|
|
}
|
|
|
|
static void
|
|
nautilus_icon_container_dropped_icon_feedback (GtkWidget *widget,
|
|
GtkSelectionData *data,
|
|
int x, int y)
|
|
{
|
|
NautilusIconContainer *container;
|
|
NautilusIconDndInfo *dnd_info;
|
|
|
|
container = NAUTILUS_ICON_CONTAINER (widget);
|
|
dnd_info = container->details->dnd_info;
|
|
|
|
/* Delete old selection list. */
|
|
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.eazel.com 2484:
|
|
* 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 = nautilus_drag_build_selection_list (data);
|
|
dnd_info->shadow = create_selection_shadow (container, dnd_info->drag_info.selection_list);
|
|
nautilus_icon_container_position_shadow (container, x, y);
|
|
}
|
|
|
|
/** 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 fist 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;
|
|
|
|
drag_info = &(NAUTILUS_ICON_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_icon_container_dropped_icon_feedback (widget, data, x, y);
|
|
break;
|
|
case NAUTILUS_ICON_DND_COLOR:
|
|
case NAUTILUS_ICON_DND_BGIMAGE:
|
|
case NAUTILUS_ICON_DND_KEYWORD:
|
|
case NAUTILUS_ICON_DND_URI_LIST:
|
|
/* Save the data so we can do the actual work on drop. */
|
|
g_assert (drag_info->selection_data == NULL);
|
|
drag_info->selection_data = nautilus_gtk_selection_data_copy_deep (data);
|
|
break;
|
|
default:
|
|
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) {
|
|
|
|
switch (info) {
|
|
case NAUTILUS_ICON_DND_GNOME_ICON_LIST:
|
|
nautilus_icon_container_receive_dropped_icons
|
|
(NAUTILUS_ICON_CONTAINER (widget),
|
|
context, x, y);
|
|
gtk_drag_finish (context, TRUE, FALSE, time);
|
|
break;
|
|
case NAUTILUS_ICON_DND_COLOR:
|
|
nautilus_background_receive_dropped_color
|
|
(nautilus_get_widget_background (widget),
|
|
widget, x, y, data);
|
|
gtk_drag_finish (context, TRUE, FALSE, time);
|
|
break;
|
|
case NAUTILUS_ICON_DND_BGIMAGE:
|
|
receive_dropped_tile_image
|
|
(NAUTILUS_ICON_CONTAINER (widget),
|
|
data);
|
|
gtk_drag_finish (context, FALSE, FALSE, time);
|
|
break;
|
|
case NAUTILUS_ICON_DND_KEYWORD:
|
|
receive_dropped_keyword
|
|
(NAUTILUS_ICON_CONTAINER (widget),
|
|
(char*) data->data, x, y);
|
|
gtk_drag_finish (context, FALSE, FALSE, time);
|
|
break;
|
|
case NAUTILUS_ICON_DND_URI_LIST:
|
|
receive_dropped_uri_list
|
|
(NAUTILUS_ICON_CONTAINER (widget),
|
|
(char*) data->data, x, y);
|
|
gtk_drag_finish (context, FALSE, FALSE, time);
|
|
break;
|
|
|
|
default:
|
|
gtk_drag_finish (context, FALSE, FALSE, time);
|
|
}
|
|
|
|
nautilus_icon_container_free_drag_data (NAUTILUS_ICON_CONTAINER (widget));
|
|
|
|
set_drop_target (NAUTILUS_ICON_CONTAINER (widget), NULL);
|
|
|
|
/* reinitialise it for the next dnd */
|
|
drag_info->drop_occured = FALSE;
|
|
}
|
|
|
|
}
|
|
|
|
static void
|
|
nautilus_icon_container_ensure_drag_data (NautilusIconContainer *container,
|
|
GdkDragContext *context,
|
|
guint32 time)
|
|
{
|
|
NautilusIconDndInfo *dnd_info;
|
|
|
|
dnd_info = container->details->dnd_info;
|
|
|
|
if (!dnd_info->drag_info.got_drop_data_type) {
|
|
gtk_drag_get_data (GTK_WIDGET (container), context,
|
|
GPOINTER_TO_INT (context->targets->data),
|
|
time);
|
|
}
|
|
}
|
|
|
|
static void
|
|
drag_end_callback (GtkWidget *widget,
|
|
GdkDragContext *context,
|
|
gpointer data)
|
|
{
|
|
NautilusIconContainer *container;
|
|
NautilusIconDndInfo *dnd_info;
|
|
|
|
container = NAUTILUS_ICON_CONTAINER (widget);
|
|
dnd_info = container->details->dnd_info;
|
|
|
|
nautilus_drag_destroy_selection_list (dnd_info->drag_info.selection_list);
|
|
dnd_info->drag_info.selection_list = NULL;
|
|
}
|
|
|
|
static NautilusIcon *
|
|
nautilus_icon_container_item_at (NautilusIconContainer *container,
|
|
int x, int y)
|
|
{
|
|
GList *p;
|
|
ArtDRect point;
|
|
|
|
/* hit test a single pixel rectangle */
|
|
point.x0 = x;
|
|
point.y0 = y;
|
|
point.x1 = x + 1;
|
|
point.y1 = y + 1;
|
|
|
|
for (p = container->details->icons; p != NULL; p = p->next) {
|
|
NautilusIcon *icon;
|
|
icon = p->data;
|
|
if (nautilus_icon_canvas_item_hit_test_rectangle
|
|
(icon->item, &point)) {
|
|
return icon;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static char *
|
|
get_container_uri (const NautilusIconContainer *container)
|
|
{
|
|
char *uri;
|
|
|
|
/* get the URI associated with the container */
|
|
uri = NULL;
|
|
gtk_signal_emit_by_name (GTK_OBJECT (container), "get_container_uri", &uri);
|
|
return uri;
|
|
}
|
|
|
|
static gboolean
|
|
nautilus_icon_container_selection_items_local (const NautilusIconContainer *container,
|
|
const 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);
|
|
result = nautilus_drag_items_local (container_uri_string, items);
|
|
g_free (container_uri_string);
|
|
|
|
return result;
|
|
}
|
|
|
|
/* handle dropped tile images */
|
|
static void
|
|
receive_dropped_tile_image (NautilusIconContainer *container, GtkSelectionData *data)
|
|
{
|
|
g_assert (data != NULL);
|
|
nautilus_background_receive_dropped_background_image
|
|
(nautilus_get_widget_background (GTK_WIDGET (container)), data->data);
|
|
}
|
|
|
|
/* handle dropped keywords */
|
|
static void
|
|
receive_dropped_keyword (NautilusIconContainer *container, char* keyword, int x, int y)
|
|
{
|
|
char *uri;
|
|
double world_x, world_y;
|
|
|
|
NautilusIcon *drop_target_icon;
|
|
NautilusFile *file;
|
|
|
|
g_assert (keyword != NULL);
|
|
|
|
/* find the item we hit with our drop, if any */
|
|
gnome_canvas_window_to_world (GNOME_CANVAS (container), x, y, &world_x, &world_y);
|
|
drop_target_icon = nautilus_icon_container_item_at (container, world_x, world_y);
|
|
if (drop_target_icon == NULL) {
|
|
return;
|
|
}
|
|
|
|
/* FIXME bugzilla.eazel.com 2485:
|
|
* 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 = nautilus_icon_container_get_icon_uri (container, drop_target_icon);
|
|
file = nautilus_file_get (uri);
|
|
g_free (uri);
|
|
|
|
nautilus_drag_file_receive_dropped_keyword (file, keyword);
|
|
|
|
nautilus_file_unref (file);
|
|
nautilus_icon_container_update_icon (container, drop_target_icon);
|
|
}
|
|
|
|
/* handle dropped uri list */
|
|
static void
|
|
receive_dropped_uri_list (NautilusIconContainer *container, char *uri_list, int x, int y)
|
|
{
|
|
/* FIXME bugzilla.eazel.com 5080:
|
|
* this needs a better name - it's link/desktop specific
|
|
*/
|
|
GList *li, *files;
|
|
int argc;
|
|
char **argv;
|
|
int i;
|
|
|
|
if (uri_list == NULL) {
|
|
return;
|
|
}
|
|
|
|
files = gnome_uri_list_extract_filenames (uri_list);
|
|
argc = g_list_length (files);
|
|
argv = g_new (char *, argc + 1);
|
|
argv[argc] = NULL;
|
|
|
|
for (i=0, li = files; li; i++, li = g_list_next (li)) {
|
|
argv[i] = li->data;
|
|
}
|
|
|
|
/* Extract .desktop info and create link/links */
|
|
gtk_signal_emit_by_name (GTK_OBJECT (container), "create_nautilus_links",
|
|
files,
|
|
x, y);
|
|
|
|
gnome_uri_list_free_strings (files);
|
|
g_free(argv);
|
|
}
|
|
|
|
static int
|
|
auto_scroll_timeout_callback (gpointer data)
|
|
{
|
|
NautilusIconContainer *container;
|
|
GtkWidget *widget;
|
|
float x_scroll_delta, y_scroll_delta;
|
|
GdkRectangle exposed_area;
|
|
|
|
g_assert (NAUTILUS_IS_ICON_CONTAINER (data));
|
|
widget = GTK_WIDGET (data);
|
|
container = NAUTILUS_ICON_CONTAINER (widget);
|
|
|
|
if (container->details->dnd_info->drag_info.waiting_to_autoscroll
|
|
&& container->details->dnd_info->drag_info.start_auto_scroll_in > nautilus_get_system_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;
|
|
}
|
|
|
|
if (!nautilus_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;
|
|
}
|
|
|
|
/* 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
|
|
*/
|
|
exposed_area.x = widget->allocation.x;
|
|
exposed_area.y = widget->allocation.y;
|
|
exposed_area.width = widget->allocation.width;
|
|
exposed_area.height = widget->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 -= widget->allocation.x;
|
|
exposed_area.y -= widget->allocation.y;
|
|
|
|
gtk_widget_draw (widget, &exposed_area);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
set_up_auto_scroll_if_needed (NautilusIconContainer *container)
|
|
{
|
|
nautilus_drag_autoscroll_start (&container->details->dnd_info->drag_info,
|
|
GTK_WIDGET (container),
|
|
auto_scroll_timeout_callback,
|
|
container);
|
|
}
|
|
|
|
static void
|
|
stop_auto_scroll (NautilusIconContainer *container)
|
|
{
|
|
nautilus_drag_autoscroll_stop (&container->details->dnd_info->drag_info);
|
|
}
|
|
|
|
static gboolean
|
|
confirm_switch_to_manual_layout (NautilusIconContainer *container)
|
|
{
|
|
const char *message;
|
|
GnomeDialog *dialog;
|
|
|
|
/* FIXME bugzilla.eazel.com 915: Use of the word "directory"
|
|
* makes this FMIconView specific. Move these messages into
|
|
* FMIconView so NautilusIconContainer can be used for things
|
|
* that are not directories?
|
|
*/
|
|
if (nautilus_icon_container_has_stored_icon_positions (container)) {
|
|
if (nautilus_g_list_exactly_one_item (container->details->dnd_info->drag_info.selection_list)) {
|
|
message = _("This folder uses automatic layout. "
|
|
"Do you want to switch to manual layout and leave this item where you dropped it? "
|
|
"This will clobber the stored manual layout.");
|
|
} else {
|
|
message = _("This folder uses automatic layout. "
|
|
"Do you want to switch to manual layout and leave these items where you dropped them? "
|
|
"This will clobber the stored manual layout.");
|
|
}
|
|
} else {
|
|
if (nautilus_g_list_exactly_one_item (container->details->dnd_info->drag_info.selection_list)) {
|
|
message = _("This folder uses automatic layout. "
|
|
"Do you want to switch to manual layout and leave this item where you dropped it?");
|
|
} else {
|
|
message = _("This folder uses automatic layout. "
|
|
"Do you want to switch to manual layout and leave these items where you dropped them?");
|
|
}
|
|
}
|
|
|
|
dialog = nautilus_yes_no_dialog (message, _("Switch to Manual Layout?"),
|
|
_("Switch"), GNOME_STOCK_BUTTON_CANCEL,
|
|
GTK_WINDOW (gtk_widget_get_ancestor
|
|
(GTK_WIDGET (container), GTK_TYPE_WINDOW)));
|
|
|
|
return gnome_dialog_run (dialog) == GNOME_OK;
|
|
}
|
|
|
|
static void
|
|
handle_local_move (NautilusIconContainer *container,
|
|
double world_x, double world_y)
|
|
{
|
|
GList *moved_icons, *p;
|
|
DragSelectionItem *item;
|
|
NautilusIcon *icon;
|
|
|
|
if (container->details->auto_layout) {
|
|
if (!confirm_switch_to_manual_layout (container)) {
|
|
return;
|
|
}
|
|
nautilus_icon_container_freeze_icon_positions (container);
|
|
}
|
|
|
|
/* 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 = nautilus_icon_container_get_icon_by_uri
|
|
(container, item->uri);
|
|
|
|
if (item->got_icon_position) {
|
|
nautilus_icon_container_move_icon
|
|
(container, icon,
|
|
world_x + item->icon_x, world_y + item->icon_y,
|
|
icon->scale_x, icon->scale_y,
|
|
TRUE, TRUE);
|
|
}
|
|
moved_icons = g_list_prepend (moved_icons, icon);
|
|
}
|
|
nautilus_icon_container_select_list_unselect_others
|
|
(container, moved_icons);
|
|
/* Might have been moved in a way that requires adjusting scroll region. */
|
|
nautilus_icon_container_update_scroll_region (container);
|
|
g_list_free (moved_icons);
|
|
}
|
|
|
|
static void
|
|
handle_nonlocal_move (NautilusIconContainer *container,
|
|
GdkDragContext *context,
|
|
int x, int y,
|
|
const char *target_uri,
|
|
gboolean icon_hit)
|
|
{
|
|
GList *source_uris, *p;
|
|
GArray *source_item_locations;
|
|
int index;
|
|
|
|
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, ((DragSelectionItem *)p->data)->uri);
|
|
}
|
|
source_uris = g_list_reverse (source_uris);
|
|
|
|
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) {
|
|
g_array_index (source_item_locations, GdkPoint, index).x =
|
|
((DragSelectionItem *)p->data)->icon_x;
|
|
g_array_index (source_item_locations, GdkPoint, index).y =
|
|
((DragSelectionItem *)p->data)->icon_y;
|
|
}
|
|
}
|
|
|
|
/* start the copy */
|
|
gtk_signal_emit_by_name (GTK_OBJECT (container), "move_copy_items",
|
|
source_uris,
|
|
source_item_locations,
|
|
target_uri,
|
|
context->action,
|
|
x, y);
|
|
|
|
g_list_free (source_uris);
|
|
g_array_free (source_item_locations, TRUE);
|
|
}
|
|
|
|
static char *
|
|
nautilus_icon_container_find_drop_target (NautilusIconContainer *container,
|
|
GdkDragContext *context,
|
|
int x, int y,
|
|
gboolean *icon_hit)
|
|
{
|
|
NautilusIcon *drop_target_icon;
|
|
double world_x, world_y;
|
|
NautilusFile *file;
|
|
char *icon_uri;
|
|
|
|
*icon_hit = FALSE;
|
|
if (container->details->dnd_info->drag_info.selection_list == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
gnome_canvas_window_to_world (GNOME_CANVAS (container),
|
|
x, y, &world_x, &world_y);
|
|
|
|
/* FIXME bugzilla.eazel.com 2485:
|
|
* 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 = nautilus_icon_container_item_at (container, world_x, world_y);
|
|
if (drop_target_icon != NULL) {
|
|
icon_uri = nautilus_icon_container_get_icon_uri (container, drop_target_icon);
|
|
if (icon_uri != NULL) {
|
|
file = nautilus_file_get (icon_uri);
|
|
|
|
if (!nautilus_drag_can_accept_items (file,
|
|
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) {
|
|
*icon_hit = FALSE;
|
|
return get_container_uri (container);
|
|
}
|
|
|
|
*icon_hit = TRUE;
|
|
return nautilus_icon_container_get_icon_drop_target_uri (container, drop_target_icon);
|
|
}
|
|
|
|
/* FIXME bugzilla.eazel.com 2485: This belongs in FMDirectoryView, not here. */
|
|
static gboolean
|
|
selection_includes_special_link (GList *selection_list)
|
|
{
|
|
GList *node;
|
|
char *uri, *local_path;
|
|
gboolean link_in_selection;
|
|
|
|
link_in_selection = FALSE;
|
|
|
|
for (node = selection_list; node != NULL; node = node->next) {
|
|
uri = ((DragSelectionItem *) node->data)->uri;
|
|
|
|
/* FIXME bugzilla.eazel.com 3020: This does sync. I/O and works only locally. */
|
|
local_path = gnome_vfs_get_local_path_from_uri (uri);
|
|
link_in_selection = local_path != NULL
|
|
&& (nautilus_link_local_is_trash_link (local_path) || nautilus_link_local_is_home_link (local_path) ||
|
|
nautilus_link_local_is_volume_link (local_path));
|
|
g_free (local_path);
|
|
|
|
if (link_in_selection) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
return link_in_selection;
|
|
}
|
|
|
|
static void
|
|
nautilus_icon_container_receive_dropped_icons (NautilusIconContainer *container,
|
|
GdkDragContext *context,
|
|
int x, int y)
|
|
{
|
|
char *drop_target;
|
|
gboolean local_move_only;
|
|
double world_x, world_y;
|
|
gboolean icon_hit;
|
|
GdkDragAction action;
|
|
|
|
drop_target = NULL;
|
|
|
|
if (container->details->dnd_info->drag_info.selection_list == NULL) {
|
|
return;
|
|
}
|
|
|
|
if (context->action == GDK_ACTION_ASK) {
|
|
/* FIXME bugzilla.eazel.com 2485: This belongs in FMDirectoryView, not here. */
|
|
/* Check for special case items in selection list */
|
|
if (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;
|
|
}
|
|
context->action = nautilus_drag_drop_action_ask (action);
|
|
}
|
|
|
|
if (context->action > 0) {
|
|
gnome_canvas_window_to_world (GNOME_CANVAS (container),
|
|
x, y, &world_x, &world_y);
|
|
|
|
drop_target = nautilus_icon_container_find_drop_target (container,
|
|
context, x, y, &icon_hit);
|
|
|
|
local_move_only = FALSE;
|
|
if (!icon_hit && context->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 = nautilus_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, context, world_x, world_y, drop_target, icon_hit);
|
|
}
|
|
}
|
|
|
|
g_free (drop_target);
|
|
nautilus_drag_destroy_selection_list (container->details->dnd_info->drag_info.selection_list);
|
|
container->details->dnd_info->drag_info.selection_list = NULL;
|
|
}
|
|
|
|
static void
|
|
nautilus_icon_container_get_drop_action (NautilusIconContainer *container,
|
|
GdkDragContext *context,
|
|
int x, int y,
|
|
int *default_action,
|
|
int *non_default_action)
|
|
{
|
|
char *drop_target;
|
|
gboolean icon_hit;
|
|
NautilusIcon *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 */
|
|
gnome_canvas_window_to_world (GNOME_CANVAS (container),
|
|
x, y, &world_x, &world_y);
|
|
|
|
icon = nautilus_icon_container_item_at (container, world_x, world_y);
|
|
|
|
/* 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) {
|
|
*default_action = 0;
|
|
*non_default_action = 0;
|
|
return;
|
|
}
|
|
drop_target = nautilus_icon_container_find_drop_target (container,
|
|
context, x, y, &icon_hit);
|
|
if (!drop_target) {
|
|
*default_action = 0;
|
|
*non_default_action = 0;
|
|
return;
|
|
}
|
|
nautilus_drag_default_drop_action_for_icons (context, drop_target,
|
|
container->details->dnd_info->drag_info.selection_list,
|
|
default_action, non_default_action);
|
|
g_free (drop_target);
|
|
break;
|
|
|
|
/* handle emblems by setting the action if we're over an object */
|
|
case NAUTILUS_ICON_DND_KEYWORD:
|
|
if (icon == NULL) {
|
|
*default_action = 0;
|
|
*non_default_action = 0;
|
|
} else {
|
|
*default_action = context->suggested_action;
|
|
*non_default_action = context->suggested_action;
|
|
}
|
|
|
|
break;
|
|
|
|
/* handle colors and backgrounds by setting the action if we're over the background */
|
|
case NAUTILUS_ICON_DND_COLOR:
|
|
case NAUTILUS_ICON_DND_BGIMAGE:
|
|
if (icon == NULL) {
|
|
*default_action = context->suggested_action;
|
|
*non_default_action = context->suggested_action;
|
|
} else {
|
|
*default_action = 0;
|
|
*non_default_action = 0;
|
|
}
|
|
|
|
break;
|
|
|
|
case NAUTILUS_ICON_DND_URI_LIST:
|
|
*default_action = context->suggested_action;
|
|
*non_default_action = context->suggested_action;
|
|
break;
|
|
|
|
default:
|
|
}
|
|
|
|
}
|
|
|
|
static void
|
|
set_drop_target (NautilusIconContainer *container,
|
|
NautilusIcon *icon)
|
|
{
|
|
NautilusIcon *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_icon_container_update_icon (container, old_icon);
|
|
nautilus_icon_container_update_icon (container, icon);
|
|
}
|
|
|
|
static void
|
|
nautilus_icon_dnd_update_drop_target (NautilusIconContainer *container,
|
|
GdkDragContext *context,
|
|
int x, int y)
|
|
{
|
|
NautilusIcon *icon;
|
|
double world_x, world_y;
|
|
|
|
g_assert (NAUTILUS_IS_ICON_CONTAINER (container));
|
|
if ((container->details->dnd_info->drag_info.selection_list == NULL)
|
|
&& (container->details->dnd_info->drag_info.data_type != NAUTILUS_ICON_DND_KEYWORD)) {
|
|
return;
|
|
}
|
|
|
|
gnome_canvas_window_to_world (GNOME_CANVAS (container),
|
|
x, y, &world_x, &world_y);
|
|
|
|
/* Find the item we hit with our drop, if any. */
|
|
icon = nautilus_icon_container_item_at (container, world_x, world_y);
|
|
|
|
/* FIXME bugzilla.eazel.com 2485:
|
|
* 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 != NAUTILUS_ICON_DND_KEYWORD)
|
|
&& !nautilus_drag_can_accept_items
|
|
(nautilus_file_get (
|
|
nautilus_icon_container_get_icon_uri (container, icon)),
|
|
container->details->dnd_info->drag_info.selection_list)) {
|
|
icon = NULL;
|
|
}
|
|
|
|
set_drop_target (container, icon);
|
|
}
|
|
|
|
static void
|
|
nautilus_icon_container_free_drag_data (NautilusIconContainer *container)
|
|
{
|
|
NautilusIconDndInfo *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) {
|
|
nautilus_gtk_selection_data_free_deep (dnd_info->drag_info.selection_data);
|
|
dnd_info->drag_info.selection_data = NULL;
|
|
}
|
|
}
|
|
|
|
static void
|
|
drag_leave_callback (GtkWidget *widget,
|
|
GdkDragContext *context,
|
|
guint32 time,
|
|
gpointer data)
|
|
{
|
|
NautilusIconDndInfo *dnd_info;
|
|
|
|
dnd_info = NAUTILUS_ICON_CONTAINER (widget)->details->dnd_info;
|
|
|
|
if (dnd_info->shadow != NULL)
|
|
gnome_canvas_item_hide (dnd_info->shadow);
|
|
|
|
stop_auto_scroll (NAUTILUS_ICON_CONTAINER (widget));
|
|
nautilus_icon_container_free_drag_data(NAUTILUS_ICON_CONTAINER (widget));
|
|
}
|
|
|
|
void
|
|
nautilus_icon_dnd_init (NautilusIconContainer *container,
|
|
GdkBitmap *stipple)
|
|
{
|
|
g_return_if_fail (container != NULL);
|
|
g_return_if_fail (NAUTILUS_IS_ICON_CONTAINER (container));
|
|
|
|
|
|
container->details->dnd_info = g_new0 (NautilusIconDndInfo, 1);
|
|
nautilus_drag_init (&container->details->dnd_info->drag_info,
|
|
drag_types, NAUTILUS_N_ELEMENTS (drag_types), stipple);
|
|
|
|
/* 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.)
|
|
*/
|
|
gtk_drag_dest_set (GTK_WIDGET (container),
|
|
0,
|
|
drop_types, NAUTILUS_N_ELEMENTS (drop_types),
|
|
GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK
|
|
| GDK_ACTION_ASK);
|
|
|
|
/* Messages for outgoing drag. */
|
|
gtk_signal_connect (GTK_OBJECT (container), "drag_data_get",
|
|
GTK_SIGNAL_FUNC (drag_data_get_callback), NULL);
|
|
gtk_signal_connect (GTK_OBJECT (container), "drag_end",
|
|
GTK_SIGNAL_FUNC (drag_end_callback), NULL);
|
|
|
|
/* Messages for incoming drag. */
|
|
gtk_signal_connect (GTK_OBJECT (container), "drag_data_received",
|
|
GTK_SIGNAL_FUNC (drag_data_received_callback), NULL);
|
|
gtk_signal_connect (GTK_OBJECT (container), "drag_motion",
|
|
GTK_SIGNAL_FUNC (drag_motion_callback), NULL);
|
|
gtk_signal_connect (GTK_OBJECT (container), "drag_drop",
|
|
GTK_SIGNAL_FUNC (drag_drop_callback), NULL);
|
|
gtk_signal_connect (GTK_OBJECT (container), "drag_leave",
|
|
GTK_SIGNAL_FUNC (drag_leave_callback), NULL);
|
|
|
|
}
|
|
|
|
void
|
|
nautilus_icon_dnd_fini (NautilusIconContainer *container)
|
|
{
|
|
g_return_if_fail (container != NULL);
|
|
g_return_if_fail (NAUTILUS_IS_ICON_CONTAINER (container));
|
|
g_return_if_fail (container->details->dnd_info != NULL);
|
|
|
|
stop_auto_scroll (container);
|
|
if (container->details->dnd_info->shadow != NULL) {
|
|
/* FIXME bugzilla.eazel.com 2484:
|
|
* Is a destroy really sufficient here? Who does the unref? */
|
|
gtk_object_destroy (GTK_OBJECT (container->details->dnd_info->shadow));
|
|
}
|
|
|
|
nautilus_drag_finalize (&container->details->dnd_info->drag_info);
|
|
}
|
|
|
|
void
|
|
nautilus_icon_dnd_begin_drag (NautilusIconContainer *container,
|
|
GdkDragAction actions,
|
|
int button,
|
|
GdkEventMotion *event)
|
|
{
|
|
NautilusIconDndInfo *dnd_info;
|
|
GnomeCanvas *canvas;
|
|
GdkDragContext *context;
|
|
GdkPixbuf *pixbuf;
|
|
GdkPixmap *pixmap_for_dragged_file;
|
|
GdkBitmap *mask_for_dragged_file;
|
|
int x_offset, y_offset;
|
|
ArtDRect world_rect;
|
|
ArtIRect window_rect;
|
|
|
|
g_return_if_fail (NAUTILUS_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 world coordinates, because of
|
|
the way the canvas handles events.
|
|
*/
|
|
canvas = GNOME_CANVAS (container);
|
|
gnome_canvas_world_to_window (canvas,
|
|
event->x, event->y,
|
|
&dnd_info->drag_info.start_x, &dnd_info->drag_info.start_y);
|
|
|
|
/* start the drag */
|
|
context = gtk_drag_begin (GTK_WIDGET (container),
|
|
dnd_info->drag_info.target_list,
|
|
actions,
|
|
button,
|
|
(GdkEvent *) event);
|
|
|
|
|
|
/* create a pixmap and mask to drag with */
|
|
pixbuf = nautilus_icon_canvas_item_get_image (container->details->drag_icon->item);
|
|
|
|
/* 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 */
|
|
|
|
gdk_pixbuf_render_pixmap_and_mask (pixbuf,
|
|
&pixmap_for_dragged_file,
|
|
&mask_for_dragged_file,
|
|
NAUTILUS_STANDARD_ALPHA_THRESHHOLD);
|
|
|
|
/* compute the image's offset */
|
|
nautilus_icon_canvas_item_get_icon_rectangle
|
|
(container->details->drag_icon->item, &world_rect);
|
|
nautilus_gnome_canvas_world_to_window_rectangle
|
|
(canvas, &world_rect, &window_rect);
|
|
x_offset = dnd_info->drag_info.start_x - window_rect.x0;
|
|
y_offset = dnd_info->drag_info.start_y - window_rect.y0;
|
|
|
|
/* set the pixmap and mask for dragging */
|
|
gtk_drag_set_icon_pixmap (context,
|
|
gtk_widget_get_colormap (GTK_WIDGET (container)),
|
|
pixmap_for_dragged_file,
|
|
mask_for_dragged_file,
|
|
x_offset, y_offset);
|
|
}
|
|
|
|
static gboolean
|
|
drag_motion_callback (GtkWidget *widget,
|
|
GdkDragContext *context,
|
|
int x, int y,
|
|
guint32 time)
|
|
{
|
|
int default_action, non_default_action;
|
|
int resulting_action;
|
|
|
|
nautilus_icon_container_ensure_drag_data (NAUTILUS_ICON_CONTAINER (widget), context, time);
|
|
nautilus_icon_container_position_shadow (NAUTILUS_ICON_CONTAINER (widget), x, y);
|
|
nautilus_icon_dnd_update_drop_target (NAUTILUS_ICON_CONTAINER (widget), context, x, y);
|
|
set_up_auto_scroll_if_needed (NAUTILUS_ICON_CONTAINER (widget));
|
|
/* Find out what the drop actions are based on our drag selection and
|
|
* the drop target.
|
|
*/
|
|
nautilus_icon_container_get_drop_action (NAUTILUS_ICON_CONTAINER (widget), context, x, y,
|
|
&default_action, &non_default_action);
|
|
|
|
/* set the right drop action, choose based on modifier key state
|
|
*/
|
|
resulting_action = nautilus_drag_modifier_based_action (default_action,
|
|
non_default_action);
|
|
gdk_drag_status (context, resulting_action, time);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
drag_drop_callback (GtkWidget *widget,
|
|
GdkDragContext *context,
|
|
int x,
|
|
int y,
|
|
guint32 time,
|
|
gpointer data)
|
|
{
|
|
NautilusIconDndInfo *dnd_info;
|
|
|
|
dnd_info = NAUTILUS_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;
|
|
gtk_drag_get_data (GTK_WIDGET (widget), context,
|
|
GPOINTER_TO_INT (context->targets->data),
|
|
time);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
void
|
|
nautilus_icon_dnd_end_drag (NautilusIconContainer *container)
|
|
{
|
|
NautilusIconDndInfo *dnd_info;
|
|
|
|
g_return_if_fail (NAUTILUS_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?
|
|
*/
|
|
}
|
|
|