123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310 | // Copyright (C) 2022 The Qt Company Ltd.// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only#include"qwasmdom.h"#include <QtCore/qdebug.h>#include <QtCore/qdir.h>#include <QtCore/qfile.h>#include <QtCore/qpoint.h>#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) {caseQt::DropAction::CopyAction:return"copy";caseQt::DropAction::IgnoreAction:return"none";caseQt::DropAction::LinkAction:return"link";caseQt::DropAction::MoveAction:caseQt::DropAction::TargetMoveAction:return"move";caseQt::DropAction::ActionMask:Q_ASSERT(false);return"";}}}// namespaceDataTransfer::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;voidDataTransfer::setDragImage(emscripten::val element,const QPoint &hotspot){ webDataTransfer.call<void>("setDragImage", element,emscripten::val(hotspot.x()),emscripten::val(hotspot.y()));}voidDataTransfer::setData(std::string format,std::string data){ webDataTransfer.call<void>("setData",emscripten::val(std::move(format)),emscripten::val(std::move(data)));}voidDataTransfer::setDropAction(Qt::DropAction action){ webDataTransfer.set("dropEffect",emscripten::val(dropActionToDropEffect(action)));}voidDataTransfer::setDataFromMimeData(const QMimeData &mimeData){for(constauto&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));}}// Converts a DataTransfer instance to a QMimeData instance. Invokes the// given callback when the conversion is complete. The callback takes ownership// of the QMimeData.voidDataTransfer::toMimeDataWithFile(std::function<void(QMimeData *)> callback){enumclass ItemKind { File, String,};class MimeContext {public:MimeContext(int itemCount,std::function<void(QMimeData *)> callback):m_remainingItemCount(itemCount),m_callback(callback){}voidderef() {if(--m_remainingItemCount >0)return; QList<QUrl> allUrls; allUrls.append(mimeData->urls()); allUrls.append(fileUrls); mimeData->setUrls(allUrls);m_callback(mimeData);// Delete files; we expect that the user callback reads/copies// file content before returning.// Fixme: tie file lifetime to lifetime of the QMimeData?for(QUrl fileUrl: fileUrls)QFile(fileUrl.toLocalFile()).remove();delete this;} QMimeData *mimeData =newQMimeData(); QList<QUrl> fileUrls;private:int m_remainingItemCount;std::function<void(QMimeData *)> m_callback;};constauto items = webDataTransfer["items"];const int itemCount = items["length"].as<int>();const int fileCount = webDataTransfer["files"]["length"].as<int>(); MimeContext *mimeContext =newMimeContext(itemCount, callback);for(int i =0; i < itemCount; ++i) {constauto item = items[i];constauto itemKind = item["kind"].as<std::string>() =="string"?ItemKind::String :ItemKind::File;constauto itemMimeType =QString::fromStdString(item["type"].as<std::string>());switch(itemKind) {caseItemKind::File: {qstdweb::File webfile(item.call<emscripten::val>("getAsFile"));if(webfile.size() >1e+9) {// limit file size to 1 GBqWarning() <<"File is too large (> 1GB) and will be skipped. File size is"<< webfile.size(); mimeContext->deref();continue;} QString mimeFormat =QString::fromStdString(webfile.type()); QString fileName =QString::fromStdString(webfile.name());// there's a file, now read it QByteArray fileContent(webfile.size(),Qt::Uninitialized); webfile.stream(fileContent.data(), [=]() {// If we get a single file, and that file is an image, then// try to decode the image data. This handles the case where// image data (i.e. not an image file) is pasted. The browsers// will then create a fake "image.png" file which has the image// data. As a side effect Qt will also decode the image for // single-image-file drops, since there is no way to differentiate// the fake "image.png" from a real one.if(fileCount ==1&& mimeFormat.contains("image/")) { QImage image;if(image.loadFromData(fileContent)) mimeContext->mimeData->setImageData(image);} QDir qtTmpDir("/qt/tmp/");// "tmp": indicate that these files won't stay around qtTmpDir.mkpath(qtTmpDir.path()); QUrl fileUrl =QUrl::fromLocalFile(qtTmpDir.filePath(QString::fromStdString(webfile.name()))); mimeContext->fileUrls.append(fileUrl); QFile file(fileUrl.toLocalFile());if(!file.open(QFile::WriteOnly)) {qWarning() <<"File was not opened"; mimeContext->deref();return;}if(file.write(fileContent) <0)qWarning() <<"Write failed"; file.close(); mimeContext->deref();});break;}caseItemKind::String:if(itemMimeType.contains("STRING",Qt::CaseSensitive)|| itemMimeType.contains("TEXT",Qt::CaseSensitive)) { mimeContext->deref();break;} QString a; QString data =QString::fromEcmaString(webDataTransfer.call<emscripten::val>("getData",emscripten::val(itemMimeType.toStdString())));if(!data.isEmpty()) {if(itemMimeType =="text/html") mimeContext->mimeData->setHtml(data);else if(itemMimeType.isEmpty() || itemMimeType =="text/plain") mimeContext->mimeData->setText(data);// the type can be emptyelse if(itemMimeType.isEmpty() || itemMimeType =="text/uri-list") { QList<QUrl> urls; urls.append(data); mimeContext->mimeData->setUrls(urls);}else{// TODO improve encodingif(data.startsWith("QB64")) { data.remove(0,4); mimeContext->mimeData->setData(itemMimeType,QByteArray::fromBase64(QByteArray::fromStdString( data.toStdString())));}else{ mimeContext->mimeData->setData(itemMimeType, data.toLocal8Bit());}}} mimeContext->deref();break;}}// for items} QMimeData *DataTransfer::toMimeDataPreview(){auto data =newQMimeData(); QList<QUrl> uriList;for(int i =0; i < webDataTransfer["items"]["length"].as<int>(); ++i) {constauto item = webDataTransfer["items"][i];if(item["kind"].as<std::string>() =="file") { uriList.append(QUrl("blob://placeholder"));}else{constauto itemMimeType =QString::fromStdString(item["type"].as<std::string>()); data->setData(itemMimeType,QByteArray());}} data->setUrls(uriList);return data;}voidsyncCSSClassWith(emscripten::val element,std::string cssClassName,bool flag){if(flag) { element["classList"].call<void>("add",emscripten::val(std::move(cssClassName)));return;} element["classList"].call<void>("remove",emscripten::val(std::move(cssClassName)));} QPointF mapPoint(emscripten::val source,emscripten::val target,const QPointF &point){constauto sourceBoundingRect =QRectF::fromDOMRect(source.call<emscripten::val>("getBoundingClientRect"));constauto targetBoundingRect =QRectF::fromDOMRect(target.call<emscripten::val>("getBoundingClientRect"));constauto offset = sourceBoundingRect.topLeft() - targetBoundingRect.topLeft();return point + offset;}voiddrawImageToWebImageDataArray(const QImage &sourceImage,emscripten::val destinationImageData,const QRect &sourceRect){Q_ASSERT_X(destinationImageData["constructor"]["name"].as<std::string>() =="ImageData", Q_FUNC_INFO,"The destination should be an ImageData instance");constexprint BytesPerColor =4;if(sourceRect.width() == sourceImage.width()) {// Copy a contiguous chunk of memory// ...............// OOOOOOOOOOOOOOO// OOOOOOOOOOOOOOO -> image data// OOOOOOOOOOOOOOO// ...............auto imageMemory =emscripten::typed_memory_view(sourceRect.width() * sourceRect.height()* BytesPerColor, sourceImage.constScanLine(sourceRect.y())); destinationImageData["data"].call<void>("set", imageMemory, sourceRect.y() * sourceImage.width() * BytesPerColor);}else{// Go through the scanlines manually to set the individual lines in bulk. This is// marginally less performant than the above.// ...............// ...OOOOOOOOO... r = 0 -> image data// ...OOOOOOOOO... r = 1 -> image data// ...OOOOOOOOO... r = 2 -> image data// ...............for(int row =0; row < sourceRect.height(); ++row) {auto scanlineMemory =emscripten::typed_memory_view(sourceRect.width() * BytesPerColor, sourceImage.constScanLine(row + sourceRect.y())+ BytesPerColor * sourceRect.x()); destinationImageData["data"].call<void>("set", scanlineMemory,(sourceRect.y() + row) * sourceImage.width()* BytesPerColor + sourceRect.x() * BytesPerColor);}}}}// namespace dom QT_END_NAMESPACE
|