diff options
Diffstat (limited to 'src/plugins/platforms/wasm/qwasmdrag.cpp')
-rw-r--r-- | src/plugins/platforms/wasm/qwasmdrag.cpp | 411 |
1 files changed, 246 insertions, 165 deletions
diff --git a/src/plugins/platforms/wasm/qwasmdrag.cpp b/src/plugins/platforms/wasm/qwasmdrag.cpp index c87e4ce7c2..d07a46618f 100644 --- a/src/plugins/platforms/wasm/qwasmdrag.cpp +++ b/src/plugins/platforms/wasm/qwasmdrag.cpp @@ -1,210 +1,291 @@ -// Copyright (C) 2022 The Qt Company Ltd. +// Copyright (C) 2023 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include "qwasmdrag.h" -#include <iostream> -#include <QMimeDatabase> +#include "qwasmbase64iconstore.h" +#include "qwasmdom.h" +#include "qwasmevent.h" +#include "qwasmintegration.h" -#include <emscripten.h> -#include <emscripten/html5.h> -#include <emscripten/val.h> -#include <emscripten/bind.h> -#include <private/qstdweb_p.h> #include <qpa/qwindowsysteminterface.h> -#include <private/qsimpledrag_p.h> -#include "qwasmcompositor.h" -#include "qwasmeventtranslator.h" -#include <QtCore/QEventLoop> -#include <QMimeData> -#include <private/qshapedpixmapdndwindow_p.h> + +#include <QtCore/private/qstdweb_p.h> +#include <QtCore/qeventloop.h> +#include <QtCore/qmimedata.h> +#include <QtCore/qtimer.h> +#include <QFile> + +#include <functional> +#include <string> +#include <utility> QT_BEGIN_NAMESPACE -using namespace emscripten; +namespace { + +QWindow *windowForDrag(QDrag *drag) +{ + QWindow *window = qobject_cast<QWindow *>(drag->source()); + if (window) + return window; + if (drag->source()->metaObject()->indexOfMethod("_q_closestWindowHandle()") == -1) + return nullptr; + + QMetaObject::invokeMethod(drag->source(), "_q_closestWindowHandle", + Q_RETURN_ARG(QWindow *, window)); + return window; +} + +} // namespace -static void getTextPlainCallback(val m_string) +struct QWasmDrag::DragState +{ + class DragImage + { + public: + DragImage(const QPixmap &pixmap, const QMimeData *mimeData, QWindow *window); + ~DragImage(); + + emscripten::val htmlElement(); + + private: + emscripten::val generateDragImage(const QPixmap &pixmap, const QMimeData *mimeData); + emscripten::val generateDragImageFromText(const QMimeData *mimeData); + emscripten::val generateDefaultDragImage(); + emscripten::val generateDragImageFromPixmap(const QPixmap &pixmap); + + emscripten::val m_imageDomElement; + emscripten::val m_temporaryImageElementParent; + }; + + DragState(QDrag *drag, QWindow *window, std::function<void()> quitEventLoopClosure); + ~DragState(); + DragState(const QWasmDrag &other) = delete; + DragState(QWasmDrag &&other) = delete; + DragState &operator=(const QWasmDrag &other) = delete; + DragState &operator=(QWasmDrag &&other) = delete; + + QDrag *drag; + QWindow *window; + std::function<void()> quitEventLoopClosure; + std::unique_ptr<DragImage> dragImage; + Qt::DropAction dropAction = Qt::DropAction::IgnoreAction; +}; + +QWasmDrag::QWasmDrag() = default; + +QWasmDrag::~QWasmDrag() = default; + +QWasmDrag *QWasmDrag::instance() { - QWasmDrag *thisDrag = static_cast<QWasmDrag*>(QWasmIntegration::get()->drag()); - thisDrag->m_mimeData->setText(QString::fromStdString(m_string.as<std::string>())); - thisDrag->qWasmDrop(); + return static_cast<QWasmDrag *>(QWasmIntegration::get()->drag()); } -static void getTextUrlCallback(val m_string) +Qt::DropAction QWasmDrag::drag(QDrag *drag) { - QWasmDrag *thisDrag = static_cast<QWasmDrag*>(QWasmIntegration::get()->drag()); - thisDrag->m_mimeData->setData(QStringLiteral("text/uri-list"), - QByteArray::fromStdString(m_string.as<std::string>())); + Q_ASSERT_X(!m_dragState, Q_FUNC_INFO, "Drag already in progress"); + + QWindow *window = windowForDrag(drag); + if (!window) + return Qt::IgnoreAction; + + Qt::DropAction dragResult = Qt::IgnoreAction; + if (qstdweb::haveJspi()) { + QEventLoop loop; + m_dragState = std::make_unique<DragState>(drag, window, [&loop]() { loop.quit(); }); + loop.exec(); + dragResult = m_dragState->dropAction; + m_dragState.reset(); + } - thisDrag->qWasmDrop(); + if (dragResult == Qt::IgnoreAction) + dragResult = QBasicDrag::drag(drag); + + return dragResult; } -static void getTextHtmlCallback(val m_string) +void QWasmDrag::onNativeDragStarted(DragEvent *event) { - QWasmDrag *thisDrag = static_cast<QWasmDrag*>(QWasmIntegration::get()->drag()); - thisDrag->m_mimeData->setHtml(QString::fromStdString(m_string.as<std::string>())); + Q_ASSERT_X(event->type == EventType::DragStart, Q_FUNC_INFO, + "The event is not a DragStart event"); + // It is possible for a drag start event to arrive from another window. + if (!m_dragState || m_dragState->window != event->targetWindow) { + event->cancelDragStart(); + return; + } - thisDrag->qWasmDrop(); + m_dragState->dragImage = std::make_unique<DragState::DragImage>( + m_dragState->drag->pixmap(), m_dragState->drag->mimeData(), event->targetWindow); + event->dataTransfer.setDragImage(m_dragState->dragImage->htmlElement(), + m_dragState->drag->hotSpot()); + event->dataTransfer.setDataFromMimeData(*m_dragState->drag->mimeData()); } -static void dropEvent(val event) -{ - // someone dropped a file into the browser window - // event is dataTransfer object - // if drop event from outside browser, we do not get any mouse release, maybe mouse move - // after the drop event - - // data-context thing was not working here :( - QWasmDrag *wasmDrag = static_cast<QWasmDrag*>(QWasmIntegration::get()->drag()); - - wasmDrag->m_wasmScreen = - reinterpret_cast<QWasmScreen*>(event["target"]["data-qtdropcontext"].as<quintptr>()); - - wasmDrag->m_mouseDropPoint = QPoint(event["x"].as<int>(), event["y"].as<int>()); - if (wasmDrag->m_mimeData) - delete wasmDrag->m_mimeData; - wasmDrag->m_mimeData = new QMimeData; - int button = event["button"].as<int>(); - wasmDrag->m_qButton = QWasmEventTranslator::translateMouseButton(button); - - wasmDrag->m_keyModifiers = Qt::NoModifier; - if (event["altKey"].as<bool>()) - wasmDrag->m_keyModifiers |= Qt::AltModifier; - if (event["ctrlKey"].as<bool>()) - wasmDrag->m_keyModifiers |= Qt::ControlModifier; - if (event["metaKey"].as<bool>()) - wasmDrag->m_keyModifiers |= Qt::MetaModifier; - - event.call<void>("preventDefault"); // prevent browser from handling drop event - - std::string dEffect = event["dataTransfer"]["dropEffect"].as<std::string>(); - - wasmDrag->m_dropActions = Qt::IgnoreAction; - if (dEffect == "copy") - wasmDrag->m_dropActions = Qt::CopyAction; - if (dEffect == "move") - wasmDrag->m_dropActions = Qt::MoveAction; - if (dEffect == "link") - wasmDrag->m_dropActions = Qt::LinkAction; - - val dt = event["dataTransfer"]["items"]["length"]; - - val typesCount = event["dataTransfer"]["types"]["length"]; - - // handle mimedata - int count = dt.as<int>(); - wasmDrag->m_mimeTypesCount = count; - // kind is file type: file or string - for (int i=0; i < count; i++) { - val item = event["dataTransfer"]["items"][i]; - val kind = item["kind"]; - val fileType = item["type"]; - - if (kind.as<std::string>() == "file") { - val m_file = item.call<val>("getAsFile"); - if (m_file.isUndefined()) { - continue; - } - - qstdweb::File file(m_file); - - QString mimeFormat = QString::fromStdString(file.type()); - QByteArray fileContent; - fileContent.resize(file.size()); - - file.stream(fileContent.data(), [=]() { - if (!fileContent.isEmpty()) { - - if (mimeFormat.contains("image")) { - QImage image; - image.loadFromData(fileContent, nullptr); - wasmDrag->m_mimeData->setImageData(image); - } else { - wasmDrag->m_mimeData->setData(mimeFormat, fileContent.data()); - } - wasmDrag->qWasmDrop(); - } - }); - - } else { // string - - if (fileType.as<std::string>() == "text/uri-list" - || fileType.as<std::string>() == "text/x-moz-url") { - item.call<val>("getAsString", val::module_property("qtgetTextUrl")); - } else if (fileType.as<std::string>() == "text/html") { - item.call<val>("getAsString", val::module_property("qtgetTextHtml")); - } else { // handle everything else here as plain text - item.call<val>("getAsString", val::module_property("qtgetTextPlain")); - } - } +void QWasmDrag::onNativeDragOver(DragEvent *event) +{ + auto mimeDataPreview = event->dataTransfer.toMimeDataPreview(); + + const Qt::DropActions actions = m_dragState + ? m_dragState->drag->supportedActions() + : (Qt::DropAction::CopyAction | Qt::DropAction::MoveAction + | Qt::DropAction::LinkAction); + + const auto dragResponse = QWindowSystemInterface::handleDrag( + event->targetWindow, &*mimeDataPreview, event->pointInPage.toPoint(), actions, + event->mouseButton, event->modifiers); + event->acceptDragOver(); + if (dragResponse.isAccepted()) { + event->dataTransfer.setDropAction(dragResponse.acceptedAction()); + } else { + event->dataTransfer.setDropAction(Qt::DropAction::IgnoreAction); } } -EMSCRIPTEN_BINDINGS(drop_module) { - function("qtDrop", &dropEvent); - function("qtgetTextPlain", &getTextPlainCallback); - function("qtgetTextUrl", &getTextUrlCallback); - function("qtgetTextHtml", &getTextHtmlCallback); +void QWasmDrag::onNativeDrop(DragEvent *event) +{ + QWasmWindow *wasmWindow = QWasmWindow::fromWindow(event->targetWindow); + + const auto screenElementPos = dom::mapPoint( + event->target(), wasmWindow->platformScreen()->element(), event->localPoint); + const auto screenPos = + wasmWindow->platformScreen()->mapFromLocal(screenElementPos); + const QPoint targetWindowPos = event->targetWindow->mapFromGlobal(screenPos).toPoint(); + + const Qt::DropActions actions = m_dragState + ? m_dragState->drag->supportedActions() + : (Qt::DropAction::CopyAction | Qt::DropAction::MoveAction + | Qt::DropAction::LinkAction); + Qt::MouseButton mouseButton = event->mouseButton; + QFlags<Qt::KeyboardModifier> modifiers = event->modifiers; + + // Accept the native drop event: We are going to async read any dropped + // files, but the browser expects that accepted state is set before any + // async calls. + event->acceptDrop(); + + const auto dropCallback = [&m_dragState = m_dragState, wasmWindow, targetWindowPos, + actions, mouseButton, modifiers](QMimeData *mimeData) { + + auto dropResponse = std::make_shared<QPlatformDropQtResponse>(true, Qt::DropAction::CopyAction); + *dropResponse = QWindowSystemInterface::handleDrop(wasmWindow->window(), mimeData, + targetWindowPos, actions, + mouseButton, modifiers); + + if (dropResponse->isAccepted()) + m_dragState->dropAction = dropResponse->acceptedAction(); + + delete mimeData; + }; + + event->dataTransfer.toMimeDataWithFile(dropCallback); } +void QWasmDrag::onNativeDragFinished(DragEvent *event) +{ + m_dragState->dropAction = event->dropAction; + m_dragState->quitEventLoopClosure(); +} + +QWasmDrag::DragState::DragImage::DragImage(const QPixmap &pixmap, const QMimeData *mimeData, + QWindow *window) + : m_temporaryImageElementParent(QWasmWindow::fromWindow(window)->containerElement()) +{ + m_imageDomElement = generateDragImage(pixmap, mimeData); + + m_imageDomElement.set("className", "hidden-drag-image"); + m_temporaryImageElementParent.call<void>("appendChild", m_imageDomElement); +} + +QWasmDrag::DragState::DragImage::~DragImage() +{ + m_temporaryImageElementParent.call<void>("removeChild", m_imageDomElement); +} -QWasmDrag::QWasmDrag() +emscripten::val QWasmDrag::DragState::DragImage::generateDragImage(const QPixmap &pixmap, + const QMimeData *mimeData) { - init(); + if (!pixmap.isNull()) + return generateDragImageFromPixmap(pixmap); + if (mimeData->hasFormat("text/plain")) + return generateDragImageFromText(mimeData); + return generateDefaultDragImage(); } -QWasmDrag::~QWasmDrag() +emscripten::val +QWasmDrag::DragState::DragImage::generateDragImageFromText(const QMimeData *mimeData) { - if (m_mimeData) - delete m_mimeData; + emscripten::val dragImageElement = + emscripten::val::global("document") + .call<emscripten::val>("createElement", emscripten::val("span")); + + constexpr qsizetype MaxCharactersInDragImage = 100; + + const auto text = QString::fromUtf8(mimeData->data("text/plain")); + dragImageElement.set( + "innerText", + text.first(qMin(qsizetype(MaxCharactersInDragImage), text.length())).toStdString()); + return dragImageElement; } -void QWasmDrag::init() +emscripten::val QWasmDrag::DragState::DragImage::generateDefaultDragImage() { + emscripten::val dragImageElement = + emscripten::val::global("document") + .call<emscripten::val>("createElement", emscripten::val("div")); + + auto innerImgElement = emscripten::val::global("document") + .call<emscripten::val>("createElement", emscripten::val("img")); + innerImgElement.set("src", + "data:image/" + std::string("svg+xml") + ";base64," + + std::string(Base64IconStore::get()->getIcon( + Base64IconStore::IconType::QtLogo))); + + constexpr char DragImageSize[] = "50px"; + + dragImageElement["style"].set("width", DragImageSize); + innerImgElement["style"].set("width", DragImageSize); + dragImageElement["style"].set("display", "flex"); + + dragImageElement.call<void>("appendChild", innerImgElement); + return dragImageElement; } -void QWasmDrag::drop(const QPoint &globalPos, Qt::MouseButtons b, Qt::KeyboardModifiers mods) +emscripten::val QWasmDrag::DragState::DragImage::generateDragImageFromPixmap(const QPixmap &pixmap) { - QSimpleDrag::drop(globalPos, b, mods); + emscripten::val dragImageElement = + emscripten::val::global("document") + .call<emscripten::val>("createElement", emscripten::val("canvas")); + dragImageElement.set("width", pixmap.width()); + dragImageElement.set("height", pixmap.height()); + + dragImageElement["style"].set( + "width", std::to_string(pixmap.width() / pixmap.devicePixelRatio()) + "px"); + dragImageElement["style"].set( + "height", std::to_string(pixmap.height() / pixmap.devicePixelRatio()) + "px"); + + auto context2d = dragImageElement.call<emscripten::val>("getContext", emscripten::val("2d")); + auto imageData = context2d.call<emscripten::val>( + "createImageData", emscripten::val(pixmap.width()), emscripten::val(pixmap.height())); + + dom::drawImageToWebImageDataArray(pixmap.toImage().convertedTo(QImage::Format::Format_RGBA8888), + imageData, QRect(0, 0, pixmap.width(), pixmap.height())); + context2d.call<void>("putImageData", imageData, emscripten::val(0), emscripten::val(0)); + + return dragImageElement; } -void QWasmDrag::move(const QPoint &globalPos, Qt::MouseButtons b, Qt::KeyboardModifiers mods) +emscripten::val QWasmDrag::DragState::DragImage::htmlElement() { - QSimpleDrag::move(globalPos, b, mods); + return m_imageDomElement; } -void QWasmDrag::qWasmDrop() -{ - // collect mime - QWasmDrag *thisDrag = static_cast<QWasmDrag*>(QWasmIntegration::get()->drag()); - - if (thisDrag->m_mimeTypesCount != thisDrag->m_mimeData->formats().size()) - return; // keep collecting mimetypes - - // start drag enter - QWindowSystemInterface::handleDrag(thisDrag->m_wasmScreen->topLevelAt(thisDrag->m_mouseDropPoint), - thisDrag->m_mimeData, - thisDrag->m_mouseDropPoint, - thisDrag->m_dropActions, - thisDrag->m_qButton, - thisDrag->m_keyModifiers); - - // drag drop - QWindowSystemInterface::handleDrop(thisDrag->m_wasmScreen->topLevelAt(thisDrag->m_mouseDropPoint), - thisDrag->m_mimeData, - thisDrag->m_mouseDropPoint, - thisDrag->m_dropActions, - thisDrag->m_qButton, - thisDrag->m_keyModifiers); - - // drag leave - QWindowSystemInterface::handleDrag(thisDrag->m_wasmScreen->topLevelAt(thisDrag->m_mouseDropPoint), - nullptr, - QPoint(), - Qt::IgnoreAction, { }, { }); - - thisDrag->m_mimeData->clear(); - thisDrag->m_mimeTypesCount = 0; +QWasmDrag::DragState::DragState(QDrag *drag, QWindow *window, + std::function<void()> quitEventLoopClosure) + : drag(drag), window(window), quitEventLoopClosure(std::move(quitEventLoopClosure)) +{ } +QWasmDrag::DragState::~DragState() = default; + QT_END_NAMESPACE |