nautilus/libnautilus-private/nautilus-tree-view-drag-dest.c
Carlos Soriano 749a864405 dnd: use GtkPlacesSidebar drop targets hints
Currently the dnd on the sidebar is only triggered when hovering
above the sidebar itself. However we would like to give some
feedback all along on the dnd operation.
For that GtkPlacesSidebar has set_drop_targets_visible public API,
which was implemented a few months ago and the GtkFileChooser is
already using.

I just forgot to implement the support for it on Nautilus... even if
the original work was done for Nautilus, since users will probably
use dnd more on nautilus than on the file chooser.

I'm not entirely happy with the implementation, since it uses custom
functions to access the drag data, given that we need them at drag-begin
time and in random places on the code, since nautilus is doing all the dnd
work manually and on different places.
The final result is that drag and drop is still managed mostly on its own
widgets, in this case list-view and canvas-view, and nautilus-dnd manages
a central accessor for dnd in nautilus, in this case requesting depending
on the widget that is the owner of the data, its data through the custom
functions of that particula widget. All other ways I tried to do it entirely
with only gtk_drag_* or gdk_drag_* functions were in vanish if no a complete
refactoring is done, and probably it doesn't worth the effort. Also I
actually separated the list view dnd code as well, so now at least the
pattern to handling dnd on nautilus is more or less consistent.
2015-10-06 23:36:30 +02:00

1264 lines
30 KiB
C

/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
/*
* Nautilus
*
* Copyright (C) 2002 Sun Microsystems, Inc.
*
* Nautilus is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* Nautilus 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
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program; if not, see <http://www.gnu.org/licenses/>.
*
* Author: Dave Camp <dave@ximian.com>
* XDS support: Benedikt Meurer <benny@xfce.org> (adapted by Amos Brocco <amos.brocco@unifr.ch>)
*/
/* nautilus-tree-view-drag-dest.c: Handles drag and drop for treeviews which
* contain a hierarchy of files
*/
#include <config.h>
#include "nautilus-tree-view-drag-dest.h"
#include "nautilus-file-dnd.h"
#include "nautilus-file-changes-queue.h"
#include "nautilus-global-preferences.h"
#include "nautilus-link.h"
#include <gtk/gtk.h>
#include <stdio.h>
#include <string.h>
#define DEBUG_FLAG NAUTILUS_DEBUG_LIST_VIEW
#include "nautilus-debug.h"
#define AUTO_SCROLL_MARGIN 20
#define HOVER_EXPAND_TIMEOUT 1
struct _NautilusTreeViewDragDestDetails {
GtkTreeView *tree_view;
gboolean drop_occurred;
gboolean have_drag_data;
guint drag_type;
GtkSelectionData *drag_data;
GList *drag_list;
guint hover_id;
guint highlight_id;
guint scroll_id;
guint expand_id;
char *direct_save_uri;
char *target_uri;
};
enum {
GET_ROOT_URI,
GET_FILE_FOR_PATH,
MOVE_COPY_ITEMS,
HANDLE_NETSCAPE_URL,
HANDLE_URI_LIST,
HANDLE_TEXT,
HANDLE_RAW,
HANDLE_HOVER,
LAST_SIGNAL
};
static guint signals[LAST_SIGNAL];
G_DEFINE_TYPE (NautilusTreeViewDragDest, nautilus_tree_view_drag_dest,
G_TYPE_OBJECT);
static const GtkTargetEntry drag_types [] = {
{ NAUTILUS_ICON_DND_GNOME_ICON_LIST_TYPE, 0, NAUTILUS_ICON_DND_GNOME_ICON_LIST },
/* prefer "_NETSCAPE_URL" over "text/uri-list" to satisfy web browsers. */
{ NAUTILUS_ICON_DND_NETSCAPE_URL_TYPE, 0, NAUTILUS_ICON_DND_NETSCAPE_URL },
{ NAUTILUS_ICON_DND_URI_LIST_TYPE, 0, NAUTILUS_ICON_DND_URI_LIST },
{ NAUTILUS_ICON_DND_XDNDDIRECTSAVE_TYPE, 0, NAUTILUS_ICON_DND_XDNDDIRECTSAVE }, /* XDS Protocol Type */
{ NAUTILUS_ICON_DND_RAW_TYPE, 0, NAUTILUS_ICON_DND_RAW }
};
static void
gtk_tree_view_vertical_autoscroll (GtkTreeView *tree_view)
{
GdkRectangle visible_rect;
GtkAdjustment *vadjustment;
GdkDeviceManager *manager;
GdkDevice *pointer;
GdkWindow *window;
int y;
int offset;
float value;
window = gtk_tree_view_get_bin_window (tree_view);
vadjustment = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (tree_view));
manager = gdk_display_get_device_manager (gtk_widget_get_display (GTK_WIDGET (tree_view)));
pointer = gdk_device_manager_get_client_pointer (manager);
gdk_window_get_device_position (window, pointer,
NULL, &y, NULL);
y += gtk_adjustment_get_value (vadjustment);
gtk_tree_view_get_visible_rect (tree_view, &visible_rect);
offset = y - (visible_rect.y + 2 * AUTO_SCROLL_MARGIN);
if (offset > 0) {
offset = y - (visible_rect.y + visible_rect.height - 2 * AUTO_SCROLL_MARGIN);
if (offset < 0) {
return;
}
}
value = CLAMP (gtk_adjustment_get_value (vadjustment) + offset, 0.0,
gtk_adjustment_get_upper (vadjustment) - gtk_adjustment_get_page_size (vadjustment));
gtk_adjustment_set_value (vadjustment, value);
}
static int
scroll_timeout (gpointer data)
{
GtkTreeView *tree_view = GTK_TREE_VIEW (data);
gtk_tree_view_vertical_autoscroll (tree_view);
return TRUE;
}
static void
remove_scroll_timeout (NautilusTreeViewDragDest *dest)
{
if (dest->details->scroll_id) {
g_source_remove (dest->details->scroll_id);
dest->details->scroll_id = 0;
}
}
static int
expand_timeout (gpointer data)
{
GtkTreeView *tree_view;
GtkTreePath *drop_path;
tree_view = GTK_TREE_VIEW (data);
gtk_tree_view_get_drag_dest_row (tree_view, &drop_path, NULL);
if (drop_path) {
gtk_tree_view_expand_row (tree_view, drop_path, FALSE);
gtk_tree_path_free (drop_path);
}
return FALSE;
}
static void
remove_expand_timer (NautilusTreeViewDragDest *dest)
{
if (dest->details->expand_id) {
g_source_remove (dest->details->expand_id);
dest->details->expand_id = 0;
}
}
static gboolean
highlight_draw (GtkWidget *widget,
cairo_t *cr,
gpointer data)
{
GdkWindow *bin_window;
int width;
int height;
GtkStyleContext *style;
/* FIXMEchpe: is bin window right here??? */
bin_window = gtk_tree_view_get_bin_window (GTK_TREE_VIEW (widget));
width = gdk_window_get_width (bin_window);
height = gdk_window_get_height (bin_window);
style = gtk_widget_get_style_context (widget);
gtk_style_context_save (style);
gtk_style_context_add_class (style, "treeview-drop-indicator");
gtk_render_focus (style,
cr,
0, 0, width, height);
gtk_style_context_restore (style);
return FALSE;
}
static void
set_widget_highlight (NautilusTreeViewDragDest *dest, gboolean highlight)
{
if (!highlight && dest->details->highlight_id) {
g_signal_handler_disconnect (dest->details->tree_view,
dest->details->highlight_id);
dest->details->highlight_id = 0;
gtk_widget_queue_draw (GTK_WIDGET (dest->details->tree_view));
}
if (highlight && !dest->details->highlight_id) {
dest->details->highlight_id =
g_signal_connect_object (dest->details->tree_view,
"draw",
G_CALLBACK (highlight_draw),
dest, 0);
gtk_widget_queue_draw (GTK_WIDGET (dest->details->tree_view));
}
}
static void
set_drag_dest_row (NautilusTreeViewDragDest *dest,
GtkTreePath *path)
{
if (path) {
set_widget_highlight (dest, FALSE);
gtk_tree_view_set_drag_dest_row
(dest->details->tree_view,
path,
GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
} else {
set_widget_highlight (dest, TRUE);
gtk_tree_view_set_drag_dest_row (dest->details->tree_view,
NULL,
0);
}
}
static void
clear_drag_dest_row (NautilusTreeViewDragDest *dest)
{
gtk_tree_view_set_drag_dest_row (dest->details->tree_view, NULL, 0);
set_widget_highlight (dest, FALSE);
}
static gboolean
get_drag_data (NautilusTreeViewDragDest *dest,
GdkDragContext *context,
guint32 time)
{
GdkAtom target;
target = gtk_drag_dest_find_target (GTK_WIDGET (dest->details->tree_view),
context,
NULL);
if (target == GDK_NONE) {
return FALSE;
}
if (target == gdk_atom_intern (NAUTILUS_ICON_DND_XDNDDIRECTSAVE_TYPE, FALSE) &&
!dest->details->drop_occurred) {
dest->details->drag_type = NAUTILUS_ICON_DND_XDNDDIRECTSAVE;
dest->details->have_drag_data = TRUE;
return TRUE;
}
gtk_drag_get_data (GTK_WIDGET (dest->details->tree_view),
context, target, time);
return TRUE;
}
static void
remove_hover_timer (NautilusTreeViewDragDest *dest)
{
if (dest->details->hover_id != 0) {
g_source_remove (dest->details->hover_id);
dest->details->hover_id = 0;
}
}
static void
free_drag_data (NautilusTreeViewDragDest *dest)
{
dest->details->have_drag_data = FALSE;
if (dest->details->drag_data) {
gtk_selection_data_free (dest->details->drag_data);
dest->details->drag_data = NULL;
}
if (dest->details->drag_list) {
nautilus_drag_destroy_selection_list (dest->details->drag_list);
dest->details->drag_list = NULL;
}
g_free (dest->details->direct_save_uri);
dest->details->direct_save_uri = NULL;
g_free (dest->details->target_uri);
dest->details->target_uri = NULL;
remove_hover_timer (dest);
remove_expand_timer (dest);
}
static gboolean
hover_timer (gpointer user_data)
{
NautilusTreeViewDragDest *dest = user_data;
dest->details->hover_id = 0;
g_signal_emit (dest, signals[HANDLE_HOVER], 0, dest->details->target_uri);
return FALSE;
}
static void
check_hover_timer (NautilusTreeViewDragDest *dest,
const char *uri)
{
GtkSettings *settings;
guint timeout;
if (g_strcmp0 (uri, dest->details->target_uri) == 0) {
return;
}
remove_hover_timer (dest);
settings = gtk_widget_get_settings (GTK_WIDGET (dest->details->tree_view));
g_object_get (settings, "gtk-timeout-expand", &timeout, NULL);
g_free (dest->details->target_uri);
dest->details->target_uri = NULL;
if (uri != NULL) {
dest->details->target_uri = g_strdup (uri);
dest->details->hover_id =
gdk_threads_add_timeout (timeout,
hover_timer,
dest);
}
}
static void
check_expand_timer (NautilusTreeViewDragDest *dest,
GtkTreePath *drop_path,
GtkTreePath *old_drop_path)
{
GtkTreeModel *model;
GtkTreeIter drop_iter;
model = gtk_tree_view_get_model (dest->details->tree_view);
if (drop_path == NULL ||
(old_drop_path != NULL && gtk_tree_path_compare (old_drop_path, drop_path) != 0)) {
remove_expand_timer (dest);
}
if (dest->details->expand_id == 0 &&
drop_path != NULL) {
gtk_tree_model_get_iter (model, &drop_iter, drop_path);
if (gtk_tree_model_iter_has_child (model, &drop_iter)) {
dest->details->expand_id =
g_timeout_add_seconds (HOVER_EXPAND_TIMEOUT,
expand_timeout,
dest->details->tree_view);
}
}
}
static char *
get_root_uri (NautilusTreeViewDragDest *dest)
{
char *uri;
g_signal_emit (dest, signals[GET_ROOT_URI], 0, &uri);
return uri;
}
static NautilusFile *
file_for_path (NautilusTreeViewDragDest *dest, GtkTreePath *path)
{
NautilusFile *file;
char *uri;
if (path) {
g_signal_emit (dest, signals[GET_FILE_FOR_PATH], 0, path, &file);
} else {
uri = get_root_uri (dest);
file = NULL;
if (uri != NULL) {
file = nautilus_file_get_by_uri (uri);
}
g_free (uri);
}
return file;
}
static char *
get_drop_target_uri_for_path (NautilusTreeViewDragDest *dest,
GtkTreePath *path)
{
NautilusFile *file;
char *target = NULL;
gboolean can;
file = file_for_path (dest, path);
if (file == NULL) {
return NULL;
}
can = nautilus_drag_can_accept_info (file,
dest->details->drag_type,
dest->details->drag_list);
if (can) {
target = nautilus_file_get_drop_target_uri (file);
}
nautilus_file_unref (file);
return target;
}
static void
check_hover_expand_timer (NautilusTreeViewDragDest *dest,
GtkTreePath *path,
GtkTreePath *drop_path,
GtkTreePath *old_drop_path)
{
gboolean use_tree = g_settings_get_boolean (nautilus_list_view_preferences,
NAUTILUS_PREFERENCES_LIST_VIEW_USE_TREE);
if (use_tree) {
check_expand_timer (dest, drop_path, old_drop_path);
} else {
char *uri;
uri = get_drop_target_uri_for_path (dest, path);
check_hover_timer (dest, uri);
g_free (uri);
}
}
static GtkTreePath *
get_drop_path (NautilusTreeViewDragDest *dest,
GtkTreePath *path)
{
NautilusFile *file;
GtkTreePath *ret;
if (!path || !dest->details->have_drag_data) {
return NULL;
}
ret = gtk_tree_path_copy (path);
file = file_for_path (dest, ret);
/* Go up the tree until we find a file that can accept a drop */
while (file == NULL /* dummy row */ ||
!nautilus_drag_can_accept_info (file,
dest->details->drag_type,
dest->details->drag_list)) {
if (gtk_tree_path_get_depth (ret) == 1) {
gtk_tree_path_free (ret);
ret = NULL;
break;
} else {
gtk_tree_path_up (ret);
nautilus_file_unref (file);
file = file_for_path (dest, ret);
}
}
nautilus_file_unref (file);
return ret;
}
static guint
get_drop_action (NautilusTreeViewDragDest *dest,
GdkDragContext *context,
GtkTreePath *path)
{
char *drop_target;
int action;
if (!dest->details->have_drag_data ||
(dest->details->drag_type == NAUTILUS_ICON_DND_GNOME_ICON_LIST &&
dest->details->drag_list == NULL)) {
return 0;
}
drop_target = get_drop_target_uri_for_path (dest, path);
if (drop_target == NULL) {
return 0;
}
action = 0;
switch (dest->details->drag_type) {
case NAUTILUS_ICON_DND_GNOME_ICON_LIST :
nautilus_drag_default_drop_action_for_icons
(context,
drop_target,
dest->details->drag_list,
0,
&action);
break;
case NAUTILUS_ICON_DND_NETSCAPE_URL:
action = nautilus_drag_default_drop_action_for_netscape_url (context);
break;
case NAUTILUS_ICON_DND_URI_LIST :
action = gdk_drag_context_get_suggested_action (context);
break;
case NAUTILUS_ICON_DND_TEXT:
case NAUTILUS_ICON_DND_RAW:
case NAUTILUS_ICON_DND_XDNDDIRECTSAVE:
action = GDK_ACTION_COPY;
break;
}
g_free (drop_target);
return action;
}
static gboolean
drag_motion_callback (GtkWidget *widget,
GdkDragContext *context,
int x,
int y,
guint32 time,
gpointer data)
{
NautilusTreeViewDragDest *dest;
GtkTreePath *path;
GtkTreePath *drop_path, *old_drop_path;
GtkTreeViewDropPosition pos;
GdkWindow *bin_window;
guint action;
gboolean res = TRUE;
dest = NAUTILUS_TREE_VIEW_DRAG_DEST (data);
gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget),
x, y, &path, &pos);
if (pos == GTK_TREE_VIEW_DROP_BEFORE ||
pos == GTK_TREE_VIEW_DROP_AFTER) {
gtk_tree_path_free (path);
path = NULL;
}
if (!dest->details->have_drag_data) {
res = get_drag_data (dest, context, time);
}
if (!res) {
return FALSE;
}
drop_path = get_drop_path (dest, path);
action = 0;
bin_window = gtk_tree_view_get_bin_window (GTK_TREE_VIEW (widget));
if (bin_window != NULL) {
int bin_x, bin_y;
gdk_window_get_position (bin_window, &bin_x, &bin_y);
if (bin_y <= y) {
/* ignore drags on the header */
action = get_drop_action (dest, context, drop_path);
}
}
gtk_tree_view_get_drag_dest_row (GTK_TREE_VIEW (widget), &old_drop_path,
NULL);
if (action) {
set_drag_dest_row (dest, drop_path);
check_hover_expand_timer (dest, path, drop_path, old_drop_path);
} else {
clear_drag_dest_row (dest);
remove_hover_timer (dest);
remove_expand_timer (dest);
}
if (path) {
gtk_tree_path_free (path);
}
if (drop_path) {
gtk_tree_path_free (drop_path);
}
if (old_drop_path) {
gtk_tree_path_free (old_drop_path);
}
if (dest->details->scroll_id == 0) {
dest->details->scroll_id =
g_timeout_add (150,
scroll_timeout,
dest->details->tree_view);
}
gdk_drag_status (context, action, time);
return TRUE;
}
static void
drag_leave_callback (GtkWidget *widget,
GdkDragContext *context,
guint32 time,
gpointer data)
{
NautilusTreeViewDragDest *dest;
dest = NAUTILUS_TREE_VIEW_DRAG_DEST (data);
clear_drag_dest_row (dest);
free_drag_data (dest);
remove_scroll_timeout (dest);
}
static char *
get_drop_target_uri_at_pos (NautilusTreeViewDragDest *dest, int x, int y)
{
char *drop_target = NULL;
GtkTreePath *path;
GtkTreePath *drop_path;
GtkTreeViewDropPosition pos;
gtk_tree_view_get_dest_row_at_pos (dest->details->tree_view, x, y,
&path, &pos);
if (pos == GTK_TREE_VIEW_DROP_BEFORE ||
pos == GTK_TREE_VIEW_DROP_AFTER) {
gtk_tree_path_free (path);
path = NULL;
}
drop_path = get_drop_path (dest, path);
drop_target = get_drop_target_uri_for_path (dest, drop_path);
if (path != NULL) {
gtk_tree_path_free (path);
}
if (drop_path != NULL) {
gtk_tree_path_free (drop_path);
}
return drop_target;
}
static void
receive_uris (NautilusTreeViewDragDest *dest,
GdkDragContext *context,
GList *source_uris,
int x, int y)
{
char *drop_target;
GdkDragAction action, real_action;
drop_target = get_drop_target_uri_at_pos (dest, x, y);
g_assert (drop_target != NULL);
real_action = gdk_drag_context_get_selected_action (context);
if (real_action == GDK_ACTION_ASK) {
if (nautilus_drag_selection_includes_special_link (dest->details->drag_list)) {
/* We only want to move the trash */
action = GDK_ACTION_MOVE;
} else {
action = GDK_ACTION_MOVE | GDK_ACTION_COPY | GDK_ACTION_LINK;
}
real_action = nautilus_drag_drop_action_ask
(GTK_WIDGET (dest->details->tree_view), action);
}
/* We only want to copy external uris */
if (dest->details->drag_type == NAUTILUS_ICON_DND_URI_LIST) {
real_action = GDK_ACTION_COPY;
}
if (real_action > 0) {
if (!nautilus_drag_uris_local (drop_target, source_uris)
|| real_action != GDK_ACTION_MOVE) {
g_signal_emit (dest, signals[MOVE_COPY_ITEMS], 0,
source_uris,
drop_target,
real_action,
x, y);
}
}
g_free (drop_target);
}
static void
receive_dropped_icons (NautilusTreeViewDragDest *dest,
GdkDragContext *context,
int x, int y)
{
GList *source_uris;
GList *l;
/* FIXME: ignore local only moves */
if (!dest->details->drag_list) {
return;
}
source_uris = NULL;
for (l = dest->details->drag_list; l != NULL; l = l->next) {
source_uris = g_list_prepend (source_uris,
((NautilusDragSelectionItem *)l->data)->uri);
}
source_uris = g_list_reverse (source_uris);
receive_uris (dest, context, source_uris, x, y);
g_list_free (source_uris);
}
static void
receive_dropped_uri_list (NautilusTreeViewDragDest *dest,
GdkDragContext *context,
int x, int y)
{
char *drop_target;
if (!dest->details->drag_data) {
return;
}
drop_target = get_drop_target_uri_at_pos (dest, x, y);
g_assert (drop_target != NULL);
g_signal_emit (dest, signals[HANDLE_URI_LIST], 0,
(char*) gtk_selection_data_get_data (dest->details->drag_data),
drop_target,
gdk_drag_context_get_selected_action (context),
x, y);
g_free (drop_target);
}
static void
receive_dropped_text (NautilusTreeViewDragDest *dest,
GdkDragContext *context,
int x, int y)
{
char *drop_target;
guchar *text;
if (!dest->details->drag_data) {
return;
}
drop_target = get_drop_target_uri_at_pos (dest, x, y);
g_assert (drop_target != NULL);
text = gtk_selection_data_get_text (dest->details->drag_data);
g_signal_emit (dest, signals[HANDLE_TEXT], 0,
(char *) text, drop_target,
gdk_drag_context_get_selected_action (context),
x, y);
g_free (text);
g_free (drop_target);
}
static void
receive_dropped_raw (NautilusTreeViewDragDest *dest,
const char *raw_data, int length,
GdkDragContext *context,
int x, int y)
{
char *drop_target;
if (!dest->details->drag_data) {
return;
}
drop_target = get_drop_target_uri_at_pos (dest, x, y);
g_assert (drop_target != NULL);
g_signal_emit (dest, signals[HANDLE_RAW], 0,
raw_data, length, drop_target,
dest->details->direct_save_uri,
gdk_drag_context_get_selected_action (context),
x, y);
g_free (drop_target);
}
static void
receive_dropped_netscape_url (NautilusTreeViewDragDest *dest,
GdkDragContext *context,
int x, int y)
{
char *drop_target;
if (!dest->details->drag_data) {
return;
}
drop_target = get_drop_target_uri_at_pos (dest, x, y);
g_assert (drop_target != NULL);
g_signal_emit (dest, signals[HANDLE_NETSCAPE_URL], 0,
(char*) gtk_selection_data_get_data (dest->details->drag_data),
drop_target,
gdk_drag_context_get_selected_action (context),
x, y);
g_free (drop_target);
}
static gboolean
receive_xds (NautilusTreeViewDragDest *dest,
GtkWidget *widget,
guint32 time,
GdkDragContext *context,
int x, int y)
{
GFile *location;
const guchar *selection_data;
gint selection_format;
gint selection_length;
selection_data = gtk_selection_data_get_data (dest->details->drag_data);
selection_format = gtk_selection_data_get_format (dest->details->drag_data);
selection_length = gtk_selection_data_get_length (dest->details->drag_data);
if (selection_format == 8
&& selection_length == 1
&& selection_data[0] == 'F') {
gtk_drag_get_data (widget, context,
gdk_atom_intern (NAUTILUS_ICON_DND_RAW_TYPE,
FALSE),
time);
return FALSE;
} else if (selection_format == 8
&& selection_length == 1
&& selection_data[0] == 'S') {
g_assert (dest->details->direct_save_uri != NULL);
location = g_file_new_for_uri (dest->details->direct_save_uri);
nautilus_file_changes_queue_file_added (location);
nautilus_file_changes_consume_changes (TRUE);
g_object_unref (location);
}
return TRUE;
}
static gboolean
drag_data_received_callback (GtkWidget *widget,
GdkDragContext *context,
int x,
int y,
GtkSelectionData *selection_data,
guint info,
guint32 time,
gpointer data)
{
NautilusTreeViewDragDest *dest;
const gchar *tmp;
int length;
gboolean success, finished;
dest = NAUTILUS_TREE_VIEW_DRAG_DEST (data);
if (!dest->details->have_drag_data) {
dest->details->have_drag_data = TRUE;
dest->details->drag_type = info;
dest->details->drag_data =
gtk_selection_data_copy (selection_data);
if (info == NAUTILUS_ICON_DND_GNOME_ICON_LIST) {
dest->details->drag_list =
nautilus_drag_build_selection_list (selection_data);
}
}
if (dest->details->drop_occurred) {
success = FALSE;
finished = TRUE;
switch (info) {
case NAUTILUS_ICON_DND_GNOME_ICON_LIST :
receive_dropped_icons (dest, context, x, y);
success = TRUE;
break;
case NAUTILUS_ICON_DND_NETSCAPE_URL :
receive_dropped_netscape_url (dest, context, x, y);
success = TRUE;
break;
case NAUTILUS_ICON_DND_URI_LIST :
receive_dropped_uri_list (dest, context, x, y);
success = TRUE;
break;
case NAUTILUS_ICON_DND_TEXT:
receive_dropped_text (dest, context, x, y);
success = TRUE;
break;
case NAUTILUS_ICON_DND_RAW:
length = gtk_selection_data_get_length (selection_data);
tmp = (const gchar *) gtk_selection_data_get_data (selection_data);
receive_dropped_raw (dest, tmp, length, context, x, y);
success = TRUE;
break;
case NAUTILUS_ICON_DND_XDNDDIRECTSAVE:
finished = receive_xds (dest, widget, time, context, x, y);
success = TRUE;
break;
}
if (finished) {
dest->details->drop_occurred = FALSE;
free_drag_data (dest);
gtk_drag_finish (context, success, FALSE, time);
}
}
/* appease GtkTreeView by preventing its drag_data_receive
* from being called */
g_signal_stop_emission_by_name (dest->details->tree_view,
"drag-data-received");
return TRUE;
}
static char *
get_direct_save_filename (GdkDragContext *context)
{
guchar *prop_text;
gint prop_len;
if (!gdk_property_get (gdk_drag_context_get_source_window (context), gdk_atom_intern (NAUTILUS_ICON_DND_XDNDDIRECTSAVE_TYPE, FALSE),
gdk_atom_intern ("text/plain", FALSE), 0, 1024, FALSE, NULL, NULL,
&prop_len, &prop_text)) {
return NULL;
}
/* Zero-terminate the string */
prop_text = g_realloc (prop_text, prop_len + 1);
prop_text[prop_len] = '\0';
/* Verify that the file name provided by the source is valid */
if (*prop_text == '\0' ||
strchr ((const gchar *) prop_text, G_DIR_SEPARATOR) != NULL) {
DEBUG ("Invalid filename provided by XDS drag site");
g_free (prop_text);
return NULL;
}
return (gchar *) prop_text;
}
static gboolean
set_direct_save_uri (NautilusTreeViewDragDest *dest,
GdkDragContext *context,
int x, int y)
{
GFile *base, *child;
char *drop_uri;
char *filename, *uri;
g_assert (dest->details->direct_save_uri == NULL);
uri = NULL;
drop_uri = get_drop_target_uri_at_pos (dest, x, y);
if (drop_uri != NULL) {
filename = get_direct_save_filename (context);
if (filename != NULL) {
/* Resolve relative path */
base = g_file_new_for_uri (drop_uri);
child = g_file_get_child (base, filename);
uri = g_file_get_uri (child);
g_object_unref (base);
g_object_unref (child);
/* Change the property */
gdk_property_change (gdk_drag_context_get_source_window (context),
gdk_atom_intern (NAUTILUS_ICON_DND_XDNDDIRECTSAVE_TYPE, FALSE),
gdk_atom_intern ("text/plain", FALSE), 8,
GDK_PROP_MODE_REPLACE, (const guchar *) uri,
strlen (uri));
dest->details->direct_save_uri = uri;
} else {
DEBUG ("Invalid filename provided by XDS drag site");
}
} else {
DEBUG ("Could not retrieve XDS drop destination");
}
return uri != NULL;
}
static gboolean
drag_drop_callback (GtkWidget *widget,
GdkDragContext *context,
int x,
int y,
guint32 time,
gpointer data)
{
NautilusTreeViewDragDest *dest;
guint info;
GdkAtom target;
dest = NAUTILUS_TREE_VIEW_DRAG_DEST (data);
target = gtk_drag_dest_find_target (GTK_WIDGET (dest->details->tree_view),
context,
NULL);
if (target == GDK_NONE) {
return FALSE;
}
info = dest->details->drag_type;
if (info == NAUTILUS_ICON_DND_XDNDDIRECTSAVE) {
/* We need to set this or get_drop_path will fail, and it
was unset by drag_leave_callback */
dest->details->have_drag_data = TRUE;
if (!set_direct_save_uri (dest, context, x, y)) {
return FALSE;
}
dest->details->have_drag_data = FALSE;
}
dest->details->drop_occurred = TRUE;
get_drag_data (dest, context, time);
remove_scroll_timeout (dest);
clear_drag_dest_row (dest);
return TRUE;
}
static void
tree_view_weak_notify (gpointer user_data,
GObject *object)
{
NautilusTreeViewDragDest *dest;
dest = NAUTILUS_TREE_VIEW_DRAG_DEST (user_data);
remove_scroll_timeout (dest);
dest->details->tree_view = NULL;
}
static void
nautilus_tree_view_drag_dest_dispose (GObject *object)
{
NautilusTreeViewDragDest *dest;
dest = NAUTILUS_TREE_VIEW_DRAG_DEST (object);
if (dest->details->tree_view) {
g_object_weak_unref (G_OBJECT (dest->details->tree_view),
tree_view_weak_notify,
dest);
}
remove_scroll_timeout (dest);
G_OBJECT_CLASS (nautilus_tree_view_drag_dest_parent_class)->dispose (object);
}
static void
nautilus_tree_view_drag_dest_finalize (GObject *object)
{
NautilusTreeViewDragDest *dest;
dest = NAUTILUS_TREE_VIEW_DRAG_DEST (object);
free_drag_data (dest);
G_OBJECT_CLASS (nautilus_tree_view_drag_dest_parent_class)->finalize (object);
}
static void
nautilus_tree_view_drag_dest_init (NautilusTreeViewDragDest *dest)
{
dest->details = G_TYPE_INSTANCE_GET_PRIVATE (dest, NAUTILUS_TYPE_TREE_VIEW_DRAG_DEST,
NautilusTreeViewDragDestDetails);
}
static void
nautilus_tree_view_drag_dest_class_init (NautilusTreeViewDragDestClass *class)
{
GObjectClass *gobject_class;
gobject_class = G_OBJECT_CLASS (class);
gobject_class->dispose = nautilus_tree_view_drag_dest_dispose;
gobject_class->finalize = nautilus_tree_view_drag_dest_finalize;
g_type_class_add_private (class, sizeof (NautilusTreeViewDragDestDetails));
signals[GET_ROOT_URI] =
g_signal_new ("get-root-uri",
G_TYPE_FROM_CLASS (class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (NautilusTreeViewDragDestClass,
get_root_uri),
NULL, NULL,
g_cclosure_marshal_generic,
G_TYPE_STRING, 0);
signals[GET_FILE_FOR_PATH] =
g_signal_new ("get-file-for-path",
G_TYPE_FROM_CLASS (class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (NautilusTreeViewDragDestClass,
get_file_for_path),
NULL, NULL,
g_cclosure_marshal_generic,
NAUTILUS_TYPE_FILE, 1,
GTK_TYPE_TREE_PATH);
signals[MOVE_COPY_ITEMS] =
g_signal_new ("move-copy-items",
G_TYPE_FROM_CLASS (class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (NautilusTreeViewDragDestClass,
move_copy_items),
NULL, NULL,
g_cclosure_marshal_generic,
G_TYPE_NONE, 5,
G_TYPE_POINTER,
G_TYPE_STRING,
GDK_TYPE_DRAG_ACTION,
G_TYPE_INT,
G_TYPE_INT);
signals[HANDLE_NETSCAPE_URL] =
g_signal_new ("handle-netscape-url",
G_TYPE_FROM_CLASS (class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (NautilusTreeViewDragDestClass,
handle_netscape_url),
NULL, NULL,
g_cclosure_marshal_generic,
G_TYPE_NONE, 5,
G_TYPE_STRING,
G_TYPE_STRING,
GDK_TYPE_DRAG_ACTION,
G_TYPE_INT,
G_TYPE_INT);
signals[HANDLE_URI_LIST] =
g_signal_new ("handle-uri-list",
G_TYPE_FROM_CLASS (class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (NautilusTreeViewDragDestClass,
handle_uri_list),
NULL, NULL,
g_cclosure_marshal_generic,
G_TYPE_NONE, 5,
G_TYPE_STRING,
G_TYPE_STRING,
GDK_TYPE_DRAG_ACTION,
G_TYPE_INT,
G_TYPE_INT);
signals[HANDLE_TEXT] =
g_signal_new ("handle-text",
G_TYPE_FROM_CLASS (class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (NautilusTreeViewDragDestClass,
handle_text),
NULL, NULL,
g_cclosure_marshal_generic,
G_TYPE_NONE, 5,
G_TYPE_STRING,
G_TYPE_STRING,
GDK_TYPE_DRAG_ACTION,
G_TYPE_INT,
G_TYPE_INT);
signals[HANDLE_RAW] =
g_signal_new ("handle-raw",
G_TYPE_FROM_CLASS (class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (NautilusTreeViewDragDestClass,
handle_raw),
NULL, NULL,
g_cclosure_marshal_generic,
G_TYPE_NONE, 7,
G_TYPE_POINTER,
G_TYPE_INT,
G_TYPE_STRING,
G_TYPE_STRING,
GDK_TYPE_DRAG_ACTION,
G_TYPE_INT,
G_TYPE_INT);
signals[HANDLE_HOVER] =
g_signal_new ("handle-hover",
G_TYPE_FROM_CLASS (class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (NautilusTreeViewDragDestClass,
handle_hover),
NULL, NULL,
g_cclosure_marshal_generic,
G_TYPE_NONE, 1,
G_TYPE_STRING);
}
NautilusTreeViewDragDest *
nautilus_tree_view_drag_dest_new (GtkTreeView *tree_view)
{
NautilusTreeViewDragDest *dest;
GtkTargetList *targets;
dest = g_object_new (NAUTILUS_TYPE_TREE_VIEW_DRAG_DEST, NULL);
dest->details->tree_view = tree_view;
g_object_weak_ref (G_OBJECT (dest->details->tree_view),
tree_view_weak_notify, dest);
gtk_drag_dest_set (GTK_WIDGET (tree_view),
0, drag_types, G_N_ELEMENTS (drag_types),
GDK_ACTION_MOVE | GDK_ACTION_COPY | GDK_ACTION_LINK | GDK_ACTION_ASK);
targets = gtk_drag_dest_get_target_list (GTK_WIDGET (tree_view));
gtk_target_list_add_text_targets (targets, NAUTILUS_ICON_DND_TEXT);
g_signal_connect_object (tree_view,
"drag-motion",
G_CALLBACK (drag_motion_callback),
dest, 0);
g_signal_connect_object (tree_view,
"drag-leave",
G_CALLBACK (drag_leave_callback),
dest, 0);
g_signal_connect_object (tree_view,
"drag-drop",
G_CALLBACK (drag_drop_callback),
dest, 0);
g_signal_connect_object (tree_view,
"drag-data-received",
G_CALLBACK (drag_data_received_callback),
dest, 0);
return dest;
}