wasm: move DataTransfer to dom::

Change-Id: I069292154bafd1c08a0d0f2e8a62052f596a80f3
Done-with: Mikolaj.Boc@qt.io
Reviewed-by: Morten Johan Sørvig <morten.sorvig@qt.io>
This commit is contained in:
Lorn Potter 2023-10-11 09:41:11 +10:00
parent d048249c33
commit 6b9270fd72
8 changed files with 192 additions and 155 deletions

View File

@ -376,112 +376,6 @@ bool jsHaveAsyncify() { return false; }
bool jsHaveJspi() { return false; }
#endif
struct DataTransferReader
{
public:
using DoneCallback = std::function<void(std::unique_ptr<QMimeData>)>;
static std::shared_ptr<CancellationFlag> read(emscripten::val webDataTransfer,
std::function<QVariant(QByteArray)> imageReader,
DoneCallback onCompleted)
{
auto cancellationFlag = std::make_shared<CancellationFlag>();
(new DataTransferReader(std::move(onCompleted), std::move(imageReader), cancellationFlag))
->read(webDataTransfer);
return cancellationFlag;
}
~DataTransferReader() = default;
private:
DataTransferReader(DoneCallback onCompleted, std::function<QVariant(QByteArray)> imageReader,
std::shared_ptr<CancellationFlag> cancellationFlag)
: mimeData(std::make_unique<QMimeData>()),
imageReader(std::move(imageReader)),
onCompleted(std::move(onCompleted)),
cancellationFlag(cancellationFlag)
{
}
void read(emscripten::val webDataTransfer)
{
enum class ItemKind {
File,
String,
};
const auto items = webDataTransfer["items"];
for (int i = 0; i < items["length"].as<int>(); ++i) {
const auto item = items[i];
const auto itemKind =
item["kind"].as<std::string>() == "string" ? ItemKind::String : ItemKind::File;
const auto itemMimeType = QString::fromStdString(item["type"].as<std::string>());
switch (itemKind) {
case ItemKind::File: {
++fileCount;
qstdweb::File file(item.call<emscripten::val>("getAsFile"));
QByteArray fileContent(file.size(), Qt::Uninitialized);
file.stream(fileContent.data(), [this, itemMimeType, fileContent]() {
if (!fileContent.isEmpty()) {
if (itemMimeType.startsWith("image/"_L1)) {
mimeData->setImageData(imageReader(fileContent));
} else {
mimeData->setData(itemMimeType, fileContent.data());
}
}
++doneCount;
onFileRead();
});
break;
}
case ItemKind::String:
if (itemMimeType.contains("STRING"_L1, Qt::CaseSensitive)
|| itemMimeType.contains("TEXT"_L1, Qt::CaseSensitive)) {
break;
}
QString a;
const QString data = QString::fromEcmaString(webDataTransfer.call<emscripten::val>(
"getData", emscripten::val(itemMimeType.toStdString())));
if (!data.isEmpty()) {
if (itemMimeType == "text/html"_L1)
mimeData->setHtml(data);
else if (itemMimeType.isEmpty() || itemMimeType == "text/plain"_L1)
mimeData->setText(data); // the type can be empty
else
mimeData->setData(itemMimeType, data.toLocal8Bit());
}
break;
}
}
onFileRead();
}
void onFileRead()
{
Q_ASSERT(doneCount <= fileCount);
if (doneCount < fileCount)
return;
std::unique_ptr<DataTransferReader> deleteThisLater(this);
if (!cancellationFlag.expired())
onCompleted(std::move(mimeData));
}
int fileCount = 0;
int doneCount = 0;
std::unique_ptr<QMimeData> mimeData;
std::function<QVariant(QByteArray)> imageReader;
DoneCallback onCompleted;
std::weak_ptr<CancellationFlag> cancellationFlag;
};
} // namespace
ArrayBuffer::ArrayBuffer(uint32_t size)
@ -948,13 +842,6 @@ bool canBlockCallingThread()
return haveAsyncify() || !emscripten_is_main_runtime_thread();
}
std::shared_ptr<CancellationFlag>
readDataTransfer(emscripten::val webDataTransfer, std::function<QVariant(QByteArray)> imageReader,
std::function<void(std::unique_ptr<QMimeData>)> onDone)
{
return DataTransferReader::read(webDataTransfer, std::move(imageReader), std::move(onDone));
}
BlobIODevice::BlobIODevice(Blob blob)
: m_blob(blob)
{

View File

@ -290,11 +290,6 @@ namespace qstdweb {
{
};
Q_CORE_EXPORT std::shared_ptr<CancellationFlag>
readDataTransfer(emscripten::val webObject, std::function<QVariant(QByteArray)> imageReader,
std::function<void(std::unique_ptr<QMimeData>)> onDone);
#if QT_CONFIG(thread)
template<class T>
T proxyCall(std::function<T()> task, emscripten::ProxyingQueue *queue)

View File

@ -71,24 +71,15 @@ static void qClipboardPasteTo(val event)
event.call<void>("preventDefault"); // prevent browser from handling drop event
static std::shared_ptr<qstdweb::CancellationFlag> readDataCancellation = nullptr;
readDataCancellation = qstdweb::readDataTransfer(
event["clipboardData"],
[](QByteArray fileContent) {
QImage image;
image.loadFromData(fileContent, nullptr);
return image;
},
[event](std::unique_ptr<QMimeData> data) {
if (data->formats().isEmpty())
return;
// Persist clipboard data so that the app can read it when handling the CTRL+V
QWasmIntegration::get()->clipboard()->QPlatformClipboard::setMimeData(
data.release(), QClipboard::Clipboard);
QWindowSystemInterface::handleKeyEvent(0, QEvent::KeyPress, Qt::Key_V,
Qt::ControlModifier, "V");
});
dom::DataTransfer transfer(event["clipboardData"]);
auto data = transfer.toMimeDataWithFile();
if (data->formats().isEmpty())
return;
// Persist clipboard data so that the app can read it when handling the CTRL+V
QWasmIntegration::get()->clipboard()->QPlatformClipboard::setMimeData(data,
QClipboard::Clipboard);
QWindowSystemInterface::handleKeyEvent(0, QEvent::KeyPress, Qt::Key_V,
Qt::ControlModifier, "V");
}
EMSCRIPTEN_BINDINGS(qtClipboardModule) {
@ -300,4 +291,5 @@ void QWasmClipboard::writeToClipboard()
val document = val::global("document");
document.call<val>("execCommand", val("copy"));
}
QT_END_NAMESPACE

View File

@ -7,6 +7,7 @@
#include <QObject>
#include <qpa/qplatformclipboard.h>
#include <private/qstdweb_p.h>
#include <QMimeData>
#include <emscripten/bind.h>

View File

@ -8,12 +8,152 @@
#include <QtCore/qrect.h>
#include <QtGui/qimage.h>
#include <private/qstdweb_p.h>
#include <QtCore/qurl.h>
#include <utility>
#include <emscripten/wire.h>
QT_BEGIN_NAMESPACE
namespace dom {
namespace {
std::string dropActionToDropEffect(Qt::DropAction action)
{
switch (action) {
case Qt::DropAction::CopyAction:
return "copy";
case Qt::DropAction::IgnoreAction:
return "none";
case Qt::DropAction::LinkAction:
return "link";
case Qt::DropAction::MoveAction:
case Qt::DropAction::TargetMoveAction:
return "move";
case Qt::DropAction::ActionMask:
Q_ASSERT(false);
return "";
}
}
} // namespace
DataTransfer::DataTransfer(emscripten::val webDataTransfer) : webDataTransfer(webDataTransfer) { }
DataTransfer::~DataTransfer() = default;
DataTransfer::DataTransfer(const DataTransfer &other) = default;
DataTransfer::DataTransfer(DataTransfer &&other) = default;
DataTransfer &DataTransfer::operator=(const DataTransfer &other) = default;
DataTransfer &DataTransfer::operator=(DataTransfer &&other) = default;
void DataTransfer::setDragImage(emscripten::val element, const QPoint &hotspot)
{
webDataTransfer.call<void>("setDragImage", element, emscripten::val(hotspot.x()),
emscripten::val(hotspot.y()));
}
void DataTransfer::setData(std::string format, std::string data)
{
webDataTransfer.call<void>("setData", emscripten::val(std::move(format)),
emscripten::val(std::move(data)));
}
void DataTransfer::setDropAction(Qt::DropAction action)
{
webDataTransfer.set("dropEffect", emscripten::val(dropActionToDropEffect(action)));
}
void DataTransfer::setDataFromMimeData(const QMimeData &mimeData)
{
for (const auto &format : mimeData.formats()) {
auto data = mimeData.data(format);
auto encoded = format.startsWith("text/")
? QString::fromLocal8Bit(data).toStdString()
: "QB64" + QString::fromLocal8Bit(data.toBase64()).toStdString();
setData(format.toStdString(), std::move(encoded));
}
}
QMimeData *DataTransfer::toMimeDataWithFile()
{
QMimeData *resultMimeData = new QMimeData(); // QScopedPointer?
enum class ItemKind {
File,
String,
};
const auto items = webDataTransfer["items"];
QList<QUrl> uriList;
for (int i = 0; i < items["length"].as<int>(); ++i) {
const auto item = items[i];
const auto itemKind =
item["kind"].as<std::string>() == "string" ? ItemKind::String : ItemKind::File;
const auto itemMimeType = QString::fromStdString(item["type"].as<std::string>());
switch (itemKind) {
case ItemKind::File: {
m_webFile = item.call<emscripten::val>("getAsFile");
qstdweb::File webfile(m_webFile);
QUrl fileUrl(QStringLiteral("file:///") + QString::fromStdString(webfile.name()));
uriList.append(fileUrl);
break;
}
case ItemKind::String:
if (itemMimeType.contains("STRING", Qt::CaseSensitive)
|| itemMimeType.contains("TEXT", Qt::CaseSensitive)) {
break;
}
QString a;
QString data = QString::fromEcmaString(webDataTransfer.call<emscripten::val>(
"getData", emscripten::val(itemMimeType.toStdString())));
if (!data.isEmpty()) {
if (itemMimeType == "text/html")
resultMimeData->setHtml(data);
else if (itemMimeType.isEmpty() || itemMimeType == "text/plain")
resultMimeData->setText(data); // the type can be empty
else {
// TODO improve encoding
if (data.startsWith("QB64")) {
data.remove(0, 4);
resultMimeData->setData(itemMimeType,
QByteArray::fromBase64(QByteArray::fromStdString(
data.toStdString())));
} else {
resultMimeData->setData(itemMimeType, data.toLocal8Bit());
}
}
}
break;
}
}
if (!uriList.isEmpty())
resultMimeData->setUrls(uriList);
return resultMimeData;
}
QMimeData *DataTransfer::toMimeDataPreview()
{
auto data = new QMimeData();
QList<QUrl> uriList;
for (int i = 0; i < webDataTransfer["items"]["length"].as<int>(); ++i) {
const auto item = webDataTransfer["items"][i];
if (item["kind"].as<std::string>() == "file") {
uriList.append(QUrl("blob://placeholder"));
} else {
const auto itemMimeType = QString::fromStdString(item["type"].as<std::string>());
data->setData(itemMimeType, QByteArray());
}
}
data->setUrls(uriList);
return data;
}
void syncCSSClassWith(emscripten::val element, std::string cssClassName, bool flag)
{

View File

@ -6,6 +6,8 @@
#include <QtCore/qtconfigmacros.h>
#include <QtCore/QPointF>
#include <private/qstdweb_p.h>
#include <QtCore/qnamespace.h>
#include <emscripten/val.h>
@ -15,10 +17,36 @@
QT_BEGIN_NAMESPACE
namespace qstdweb {
struct CancellationFlag;
}
class QMimeData;
class QPoint;
class QRect;
namespace dom {
struct DataTransfer
{
explicit DataTransfer(emscripten::val webDataTransfer);
~DataTransfer();
DataTransfer(const DataTransfer &other);
DataTransfer(DataTransfer &&other);
DataTransfer &operator=(const DataTransfer &other);
DataTransfer &operator=(DataTransfer &&other);
QMimeData *toMimeDataWithFile() ;
QMimeData *toMimeDataPreview();
void setDragImage(emscripten::val element, const QPoint &hotspot);
void setData(std::string format, std::string data);
void setDropAction(Qt::DropAction dropAction);
void setDataFromMimeData(const QMimeData &mimeData);
emscripten::val webDataTransfer;
emscripten::val m_webFile = emscripten::val::undefined();
qstdweb::File m_file;
};
inline emscripten::val document()
{
return emscripten::val::global("document");

View File

@ -5,6 +5,7 @@
#define QWASMEVENT_H
#include "qwasmplatform.h"
#include "qwasmdom.h"
#include <QtCore/qglobal.h>
#include <QtCore/qnamespace.h>
@ -241,7 +242,7 @@ struct DragEvent : public MouseEvent
void acceptDrop();
Qt::DropAction dropAction;
emscripten::val dataTransfer;
dom::DataTransfer dataTransfer;
QWindow *targetWindow;
};

View File

@ -114,7 +114,6 @@ QWasmWindow::QWasmWindow(QWindow *w, QWasmDeadKeySupport *deadKeySupport,
std::make_unique<qstdweb::EventCallback>(m_qtWindow, "pointerenter", pointerCallback);
m_pointerLeaveCallback =
std::make_unique<qstdweb::EventCallback>(m_qtWindow, "pointerleave", pointerCallback);
m_dropCallback = std::make_unique<qstdweb::EventCallback>(
m_qtWindow, "drop", [this](emscripten::val event) {
if (processDrop(*DragEvent::fromWeb(event, window())))
@ -538,25 +537,19 @@ bool QWasmWindow::processPointer(const PointerEvent &event)
bool QWasmWindow::processDrop(const DragEvent &event)
{
m_dropDataReadCancellationFlag = qstdweb::readDataTransfer(
event.dataTransfer,
[](QByteArray fileContent) {
QImage image;
image.loadFromData(fileContent, nullptr);
return image;
},
[this, event](std::unique_ptr<QMimeData> data) {
QWindowSystemInterface::handleDrag(window(), data.get(),
event.pointInPage.toPoint(), event.dropAction,
event.mouseButton, event.modifiers);
dom::DataTransfer transfer(event.dataTransfer.webDataTransfer["clipboardData"]);
QMimeData *data = transfer.toMimeDataWithFile();
// TODO handle file
QWindowSystemInterface::handleDrag(window(), data,
event.pointInPage.toPoint(), event.dropAction,
event.mouseButton, event.modifiers);
QWindowSystemInterface::handleDrop(window(), data.get(),
event.pointInPage.toPoint(), event.dropAction,
event.mouseButton, event.modifiers);
QWindowSystemInterface::handleDrop(window(), data,
event.pointInPage.toPoint(), event.dropAction,
event.mouseButton, event.modifiers);
QWindowSystemInterface::handleDrag(window(), nullptr, QPoint(), Qt::IgnoreAction,
{}, {});
});
QWindowSystemInterface::handleDrag(window(), nullptr, QPoint(), Qt::IgnoreAction,
{}, {});
return true;
}