DragSourceGtk3.cpp   [plain text]


/*
 * Copyright (C) 2020 Igalia S.L.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "config.h"
#include "DragSource.h"

#if ENABLE(DRAG_SUPPORT) && !USE(GTK4)

#include "WebKitWebViewBasePrivate.h"
#include <WebCore/GRefPtrGtk.h>
#include <WebCore/GtkUtilities.h>
#include <gtk/gtk.h>

namespace WebKit {
using namespace WebCore;

enum DragTargetType { Markup, Text, Image, URIList, NetscapeURL, SmartPaste };

DragSource::DragSource(GtkWidget* webView)
    : m_webView(webView)
{
    g_signal_connect(m_webView, "drag-data-get", G_CALLBACK(+[](GtkWidget*, GdkDragContext* context, GtkSelectionData* data, guint info, guint, gpointer userData) {
        auto& drag = *static_cast<DragSource*>(userData);
        if (drag.m_drag.get() != context)
            return;

        switch (info) {
        case DragTargetType::Text:
            gtk_selection_data_set_text(data, drag.m_selectionData->text().utf8().data(), -1);
            break;
        case DragTargetType::Markup: {
            CString markup = drag.m_selectionData->markup().utf8();
            gtk_selection_data_set(data, gdk_atom_intern_static_string("text/html"), 8, reinterpret_cast<const guchar*>(markup.data()), markup.length());
            break;
        }
        case DragTargetType::URIList: {
            CString uriList = drag.m_selectionData->uriList().utf8();
            gtk_selection_data_set(data, gdk_atom_intern_static_string("text/uri-list"), 8, reinterpret_cast<const guchar*>(uriList.data()), uriList.length());
            break;
        }
        case DragTargetType::NetscapeURL: {
            CString urlString = drag.m_selectionData->url().string().utf8();
            GUniquePtr<gchar> url(g_strdup_printf("%s\n%s", urlString.data(), drag.m_selectionData->hasText() ? drag.m_selectionData->text().utf8().data() : urlString.data()));
            gtk_selection_data_set(data, gdk_atom_intern_static_string("_NETSCAPE_URL"), 8, reinterpret_cast<const guchar*>(url.get()), strlen(url.get()));
            break;
        }
        case DragTargetType::Image: {
            GRefPtr<GdkPixbuf> pixbuf = adoptGRef(drag.m_selectionData->image()->getGdkPixbuf());
            gtk_selection_data_set_pixbuf(data, pixbuf.get());
            break;
        }
        case DragTargetType::SmartPaste:
            gtk_selection_data_set_text(data, "", -1);
            break;
        }
    }), this);

    g_signal_connect(m_webView, "drag-end", G_CALLBACK(+[](GtkWidget*, GdkDragContext* context, gpointer userData) {
        auto& drag = *static_cast<DragSource*>(userData);
        if (drag.m_drag.get() != context)
            return;
        if (!drag.m_selectionData)
            return;

        drag.m_selectionData = WTF::nullopt;
        drag.m_drag = nullptr;

        GdkDevice* device = gdk_drag_context_get_device(context);
        int x = 0;
        int y = 0;
        gdk_device_get_window_at_position(device, &x, &y);
        int xRoot = 0;
        int yRoot = 0;
        gdk_device_get_position(device, nullptr, &xRoot, &yRoot);

        auto* page = webkitWebViewBaseGetPage(WEBKIT_WEB_VIEW_BASE(drag.m_webView));
        ASSERT(page);
        page->dragEnded({ x, y }, { xRoot, yRoot }, gdkDragActionToDragOperation(gdk_drag_context_get_selected_action(context)));
    }), this);
}

DragSource::~DragSource()
{
    g_signal_handlers_disconnect_by_data(m_webView, this);
}

void DragSource::begin(SelectionData&& selectionData, OptionSet<DragOperation> operationMask, RefPtr<ShareableBitmap>&& image)
{
    if (m_drag) {
        gtk_drag_cancel(m_drag.get());
        m_drag = nullptr;
    }

    m_selectionData = WTFMove(selectionData);

    GRefPtr<GtkTargetList> list = adoptGRef(gtk_target_list_new(nullptr, 0));
    if (m_selectionData->hasText())
        gtk_target_list_add_text_targets(list.get(), DragTargetType::Text);
    if (m_selectionData->hasMarkup())
        gtk_target_list_add(list.get(), gdk_atom_intern_static_string("text/html"), 0, DragTargetType::Markup);
    if (m_selectionData->hasURIList())
        gtk_target_list_add_uri_targets(list.get(), DragTargetType::URIList);
    if (m_selectionData->hasURL())
        gtk_target_list_add(list.get(), gdk_atom_intern_static_string("_NETSCAPE_URL"), 0, DragTargetType::NetscapeURL);
    if (m_selectionData->hasImage())
        gtk_target_list_add_image_targets(list.get(), DragTargetType::Image, TRUE);
    if (m_selectionData->canSmartReplace())
        gtk_target_list_add(list.get(), gdk_atom_intern_static_string("application/vnd.webkitgtk.smartpaste"), 0, DragTargetType::SmartPaste);

    m_drag = gtk_drag_begin_with_coordinates(m_webView, list.get(), dragOperationToGdkDragActions(operationMask), GDK_BUTTON_PRIMARY, nullptr, -1, -1);
    if (image) {
        RefPtr<cairo_surface_t> imageSurface(image->createCairoSurface());
        // Use the center of the drag image as hotspot.
        cairo_surface_set_device_offset(imageSurface.get(), -cairo_image_surface_get_width(imageSurface.get()) / 2, -cairo_image_surface_get_height(imageSurface.get()) / 2);
        gtk_drag_set_icon_surface(m_drag.get(), imageSurface.get());
    } else
        gtk_drag_set_icon_default(m_drag.get());
}

} // namespace WebKit

#endif // ENABLE(DRAG_SUPPORT) && !USE(GTK4)