Blob Blame History Raw
/* nautilus-list-view-dnd.c
 *
 * Copyright (C) 2015 Carlos Soriano <csoriano@gnome.org>
 *
 * This program 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 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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/>.
 */

#include <config.h>

#include "nautilus-list-view-dnd.h"
#include "nautilus-list-view-private.h"

static GtkTargetList *source_target_list = NULL;

static void
drag_info_data_free (NautilusListView *list_view);

static void
drag_data_get_callback (GtkWidget        *widget,
                        GdkDragContext   *context,
                        GtkSelectionData *selection_data,
                        guint             info,
                        guint             time,
                        gpointer          user_data)
{
    GtkTreeView *tree_view;
    GtkTreeModel *model;
    NautilusListView *list_view;

    tree_view = GTK_TREE_VIEW (widget);
    list_view = NAUTILUS_LIST_VIEW (user_data);

    model = gtk_tree_view_get_model (tree_view);

    if (model == NULL)
    {
        return;
    }

    if (list_view->details->drag_source_info == NULL ||
        list_view->details->drag_source_info->selection_cache == NULL)
    {
        return;
    }

    nautilus_drag_drag_data_get_from_cache (list_view->details->drag_source_info->selection_cache,
                                            context, selection_data, info, time);
}

static cairo_surface_t *
get_drag_surface (NautilusListView *view)
{
    GtkTreeModel *model;
    GtkTreePath *path;
    GtkTreeIter iter;
    cairo_surface_t *ret;
    GdkRectangle cell_area;

    ret = NULL;

    if (gtk_tree_view_get_path_at_pos (view->details->tree_view,
                                       view->details->drag_x,
                                       view->details->drag_y,
                                       &path, NULL, NULL, NULL))
    {
        model = gtk_tree_view_get_model (view->details->tree_view);
        gtk_tree_model_get_iter (model, &iter, path);
        gtk_tree_model_get (model, &iter,
                            nautilus_list_model_get_column_id_from_zoom_level (view->details->zoom_level),
                            &ret,
                            -1);
    }

    gtk_tree_view_get_cell_area (view->details->tree_view,
                                 path,
                                 view->details->file_name_column,
                                 &cell_area);

    gtk_tree_path_free (path);

    return ret;
}

/* iteration glue struct */
typedef struct
{
    NautilusListView *view;
    NautilusDragEachSelectedItemDataGet iteratee;
    gpointer iteratee_data;
} ListGetDataBinderContext;

static void
item_get_data_binder (GtkTreeModel *model,
                      GtkTreePath  *path,
                      GtkTreeIter  *iter,
                      gpointer      data)
{
    ListGetDataBinderContext *context = data;
    NautilusFile *file;
    GtkTreeView *treeview;
    GtkTreeViewColumn *column;
    GdkRectangle cell_area;
    int drag_begin_y = 0;
    char *uri;

    treeview = nautilus_list_model_get_drag_view (context->view->details->model,
                                                  NULL,
                                                  &drag_begin_y);
    column = gtk_tree_view_get_column (treeview, 0);

    file = nautilus_list_model_file_for_path (NAUTILUS_LIST_MODEL (model), path);
    if (file == NULL)
    {
        return;
    }

    gtk_tree_view_get_cell_area (treeview,
                                 path,
                                 column,
                                 &cell_area);

    if (nautilus_file_is_nautilus_link (file))
    {
        uri = nautilus_file_get_uri (file);
    }
    else
    {
        uri = nautilus_file_get_activation_uri (file);
    }

    nautilus_file_unref (file);

    /* pass the uri, mouse-relative x/y and icon width/height */
    context->iteratee (uri,
                       0,
                       cell_area.y - drag_begin_y,
                       cell_area.width,
                       cell_area.height,
                       context->iteratee_data);

    g_free (uri);
}

static void
each_item_get_data_binder (NautilusDragEachSelectedItemDataGet iteratee,
                           gpointer                            iterator_context,
                           gpointer                            data)
{
    NautilusListView *view = NAUTILUS_LIST_VIEW (iterator_context);
    ListGetDataBinderContext context;
    GtkTreeSelection *selection;

    context.view = view;
    context.iteratee = iteratee;
    context.iteratee_data = data;

    selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view->details->tree_view));
    gtk_tree_selection_selected_foreach (selection, item_get_data_binder, &context);
}

static void
drag_begin_callback (GtkWidget        *widget,
                     GdkDragContext   *context,
                     NautilusListView *view)
{
    cairo_surface_t *surface;
    NautilusWindow *window;
    GList *dragged_files;

    window = nautilus_files_view_get_window (NAUTILUS_FILES_VIEW (view));
    surface = get_drag_surface (view);
    if (surface)
    {
        gtk_drag_set_icon_surface (context, surface);
        cairo_surface_destroy (surface);
    }
    else
    {
        gtk_drag_set_icon_default (context);
    }

    view->details->drag_button = 0;
    view->details->drag_started = TRUE;

    view->details->drag_source_info->selection_cache = nautilus_drag_create_selection_cache (view,
                                                                                             each_item_get_data_binder);

    dragged_files = nautilus_drag_file_list_from_selection_list (view->details->drag_source_info->selection_cache);
    if (nautilus_file_list_are_all_folders (dragged_files))
    {
        nautilus_window_start_dnd (window, context);
    }
    g_list_free_full (dragged_files, g_object_unref);
}

static void
drag_end_callback (GtkWidget        *widget,
                   GdkDragContext   *context,
                   NautilusListView *list_view)
{
    NautilusWindow *window;

    window = nautilus_files_view_get_window (NAUTILUS_FILES_VIEW (list_view));

    nautilus_window_end_dnd (window, context);

    drag_info_data_free (list_view);
}

static void
drag_info_data_free (NautilusListView *list_view)
{
    nautilus_drag_destroy_selection_list (list_view->details->drag_source_info->selection_cache);
    list_view->details->drag_source_info->selection_cache = NULL;

    g_free (list_view->details->drag_source_info);
    list_view->details->drag_source_info = NULL;

    g_signal_handlers_disconnect_by_func (list_view->details->tree_view, drag_begin_callback, list_view);
    g_signal_handlers_disconnect_by_func (list_view->details->tree_view, drag_data_get_callback, list_view);
    g_signal_handlers_disconnect_by_func (list_view->details->tree_view, drag_end_callback, list_view);
}

NautilusDragInfo *
nautilus_list_view_dnd_get_drag_source_data (NautilusListView *list_view,
                                             GdkDragContext   *context)
{
    GtkTreeView *tree_view;
    GtkTreeModel *model;

    tree_view = GTK_TREE_VIEW (list_view->details->tree_view);

    model = gtk_tree_view_get_model (tree_view);

    if (model == NULL)
    {
        return NULL;
    }

    if (list_view->details->drag_source_info == NULL ||
        list_view->details->drag_source_info->selection_cache == NULL)
    {
        return NULL;
    }

    return list_view->details->drag_source_info;
}

void
nautilus_list_view_dnd_init (NautilusListView *list_view)
{
    if (list_view->details->drag_source_info != NULL)
    {
        return;
    }

    list_view->details->drag_source_info = g_new0 (NautilusDragInfo, 1);

    g_signal_connect_object (list_view->details->tree_view, "drag-begin",
                             G_CALLBACK (drag_begin_callback), list_view, 0);
    g_signal_connect_object (list_view->details->tree_view, "drag-end",
                             G_CALLBACK (drag_end_callback), list_view, 0);
    g_signal_connect_object (list_view->details->tree_view, "drag-data-get",
                             G_CALLBACK (drag_data_get_callback), list_view, 0);
}

gboolean
nautilus_list_view_dnd_drag_begin (NautilusListView *list_view,
                                   GdkEventMotion   *event)
{
    if (list_view->details->drag_button != 0)
    {
        if (!source_target_list)
        {
            source_target_list = nautilus_list_model_get_drag_target_list ();
        }

        if (gtk_drag_check_threshold (GTK_WIDGET (list_view->details->tree_view),
                                      list_view->details->drag_x,
                                      list_view->details->drag_y,
                                      event->x,
                                      event->y))
        {
            guint32 actions;

            actions = GDK_ACTION_MOVE | GDK_ACTION_COPY | GDK_ACTION_LINK | GDK_ACTION_ASK;
            list_view->details->drag_source_info->source_actions = actions;
            gtk_drag_begin_with_coordinates (GTK_WIDGET (list_view->details->tree_view),
                                             source_target_list,
                                             actions,
                                             list_view->details->drag_button,
                                             (GdkEvent *) event,
                                             -1,
                                             -1);
        }
        return TRUE;
    }

    return FALSE;
}