summaryrefslogtreecommitdiffstats
path: root/src/plugins/platforms/wasm/qwasmdrag.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/platforms/wasm/qwasmdrag.cpp')
-rw-r--r--src/plugins/platforms/wasm/qwasmdrag.cpp411
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