Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qwasmdom.cpp
Go to the documentation of this file.
1// Copyright (C) 2022 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include "qwasmdom.h"
5
6#include <QtCore/qdebug.h>
7#include <QtCore/qdir.h>
8#include <QtCore/qfile.h>
9#include <QtCore/qpoint.h>
10#include <QtCore/qrect.h>
11#include <QtGui/qimage.h>
12#include <private/qstdweb_p.h>
13#include <QtCore/qurl.h>
14
15#include <utility>
16#include <emscripten/wire.h>
17
18QT_BEGIN_NAMESPACE
19
20namespace dom {
21namespace {
22std::string dropActionToDropEffect(Qt::DropAction action)
23{
24 switch (action) {
25 case Qt::DropAction::CopyAction:
26 return "copy";
27 case Qt::DropAction::IgnoreAction:
28 return "none";
29 case Qt::DropAction::LinkAction:
30 return "link";
31 case Qt::DropAction::MoveAction:
32 case Qt::DropAction::TargetMoveAction:
33 return "move";
34 case Qt::DropAction::ActionMask:
35 Q_ASSERT(false);
36 return "";
37 }
38}
39} // namespace
40
41DataTransfer::DataTransfer(emscripten::val webDataTransfer)
42 : webDataTransfer(webDataTransfer) {
43}
44
45DataTransfer::~DataTransfer() = default;
46
47DataTransfer::DataTransfer(const DataTransfer &other) = default;
48
49DataTransfer::DataTransfer(DataTransfer &&other) = default;
50
51DataTransfer &DataTransfer::operator=(const DataTransfer &other) = default;
52
54
55void DataTransfer::setDragImage(emscripten::val element, const QPoint &hotspot)
56{
57 webDataTransfer.call<void>("setDragImage", element, emscripten::val(hotspot.x()),
58 emscripten::val(hotspot.y()));
59}
60
61void DataTransfer::setData(std::string format, std::string data)
62{
63 webDataTransfer.call<void>("setData", emscripten::val(std::move(format)),
64 emscripten::val(std::move(data)));
65}
66
67void DataTransfer::setDropAction(Qt::DropAction action)
68{
69 webDataTransfer.set("dropEffect", emscripten::val(dropActionToDropEffect(action)));
70}
71
72void DataTransfer::setDataFromMimeData(const QMimeData &mimeData)
73{
74 for (const auto &format : mimeData.formats()) {
75 auto data = mimeData.data(format);
76
77 auto encoded = format.startsWith("text/")
78 ? QString::fromLocal8Bit(data).toStdString()
79 : "QB64" + QString::fromLocal8Bit(data.toBase64()).toStdString();
80
81 setData(format.toStdString(), std::move(encoded));
82 }
83}
84
85// Converts a DataTransfer instance to a QMimeData instance. Invokes the
86// given callback when the conversion is complete. The callback takes ownership
87// of the QMimeData.
88void DataTransfer::toMimeDataWithFile(std::function<void(QMimeData *)> callback)
89{
90 enum class ItemKind {
91 File,
92 String,
93 };
94
95 class MimeContext {
96
97 public:
98 MimeContext(int itemCount, std::function<void(QMimeData *)> callback)
99 :m_remainingItemCount(itemCount), m_callback(callback)
100 {
101
102 }
103
104 void deref() {
105 if (--m_remainingItemCount > 0)
106 return;
107
108 QList<QUrl> allUrls;
109 allUrls.append(mimeData->urls());
110 allUrls.append(fileUrls);
111 mimeData->setUrls(allUrls);
112
113 m_callback(mimeData);
114
115 // Delete files; we expect that the user callback reads/copies
116 // file content before returning.
117 // Fixme: tie file lifetime to lifetime of the QMimeData?
118 for (QUrl fileUrl: fileUrls)
119 QFile(fileUrl.toLocalFile()).remove();
120
121 delete this;
122 }
123
124 QMimeData *mimeData = new QMimeData();
125 QList<QUrl> fileUrls;
126
127 private:
128 int m_remainingItemCount;
129 std::function<void(QMimeData *)> m_callback;
130 };
131
132 const auto items = webDataTransfer["items"];
133 const int itemCount = items["length"].as<int>();
134 const int fileCount = webDataTransfer["files"]["length"].as<int>();
135 MimeContext *mimeContext = new MimeContext(itemCount, callback);
136
137 for (int i = 0; i < itemCount; ++i) {
138 const auto item = items[i];
139 const auto itemKind =
140 item["kind"].as<std::string>() == "string" ? ItemKind::String : ItemKind::File;
141 const auto itemMimeType = QString::fromStdString(item["type"].as<std::string>());
142
143 switch (itemKind) {
144 case ItemKind::File: {
145 qstdweb::File webfile(item.call<emscripten::val>("getAsFile"));
146
147 if (webfile.size() > 1e+9) { // limit file size to 1 GB
148 qWarning() << "File is too large (> 1GB) and will be skipped. File size is" << webfile.size();
149 mimeContext->deref();
150 continue;
151 }
152
153 QString mimeFormat = QString::fromStdString(webfile.type());
154 QString fileName = QString::fromStdString(webfile.name());
155
156 // there's a file, now read it
157 QByteArray fileContent(webfile.size(), Qt::Uninitialized);
158 webfile.stream(fileContent.data(), [=]() {
159
160 // If we get a single file, and that file is an image, then
161 // try to decode the image data. This handles the case where
162 // image data (i.e. not an image file) is pasted. The browsers
163 // will then create a fake "image.png" file which has the image
164 // data. As a side effect Qt will also decode the image for
165 // single-image-file drops, since there is no way to differentiate
166 // the fake "image.png" from a real one.
167 if (fileCount == 1 && mimeFormat.contains("image/")) {
168 QImage image;
169 if (image.loadFromData(fileContent))
170 mimeContext->mimeData->setImageData(image);
171 }
172
173 QDir qtTmpDir("/qt/tmp/"); // "tmp": indicate that these files won't stay around
174 qtTmpDir.mkpath(qtTmpDir.path());
175
176 QUrl fileUrl = QUrl::fromLocalFile(qtTmpDir.filePath(QString::fromStdString(webfile.name())));
177 mimeContext->fileUrls.append(fileUrl);
178
179 QFile file(fileUrl.toLocalFile());
180 if (!file.open(QFile::WriteOnly)) {
181 qWarning() << "File was not opened";
182 mimeContext->deref();
183 return;
184 }
185 if (file.write(fileContent) < 0)
186 qWarning() << "Write failed";
187 file.close();
188 mimeContext->deref();
189 });
190 break;
191 }
192 case ItemKind::String:
193 if (itemMimeType.contains("STRING", Qt::CaseSensitive)
194 || itemMimeType.contains("TEXT", Qt::CaseSensitive)) {
195 mimeContext->deref();
196 break;
197 }
198 QString a;
199 QString data = QString::fromEcmaString(webDataTransfer.call<emscripten::val>(
200 "getData", emscripten::val(itemMimeType.toStdString())));
201
202 if (!data.isEmpty()) {
203 if (itemMimeType == "text/html")
204 mimeContext->mimeData->setHtml(data);
205 else if (itemMimeType.isEmpty() || itemMimeType == "text/plain")
206 mimeContext->mimeData->setText(data); // the type can be empty
207 else if (itemMimeType.isEmpty() || itemMimeType == "text/uri-list") {
208 QList<QUrl> urls;
209 urls.append(data);
210 mimeContext->mimeData->setUrls(urls);
211 } else {
212 // TODO improve encoding
213 if (data.startsWith("QB64")) {
214 data.remove(0, 4);
215 mimeContext->mimeData->setData(itemMimeType,
216 QByteArray::fromBase64(QByteArray::fromStdString(
217 data.toStdString())));
218 } else {
219 mimeContext->mimeData->setData(itemMimeType, data.toLocal8Bit());
220 }
221 }
222 }
223 mimeContext->deref();
224 break;
225 }
226 } // for items
227}
228
230{
231 auto data = new QMimeData();
232
233 QList<QUrl> uriList;
234 for (int i = 0; i < webDataTransfer["items"]["length"].as<int>(); ++i) {
235 const auto item = webDataTransfer["items"][i];
236 if (item["kind"].as<std::string>() == "file") {
237 uriList.append(QUrl("blob://placeholder"));
238 } else {
239 const auto itemMimeType = QString::fromStdString(item["type"].as<std::string>());
240 data->setData(itemMimeType, QByteArray());
241 }
242 }
243 data->setUrls(uriList);
244 return data;
245}
246
247void syncCSSClassWith(emscripten::val element, std::string cssClassName, bool flag)
248{
249 if (flag) {
250 element["classList"].call<void>("add", emscripten::val(std::move(cssClassName)));
251 return;
252 }
253
254 element["classList"].call<void>("remove", emscripten::val(std::move(cssClassName)));
255}
256
257QPointF mapPoint(emscripten::val source, emscripten::val target, const QPointF &point)
258{
259 const auto sourceBoundingRect =
260 QRectF::fromDOMRect(source.call<emscripten::val>("getBoundingClientRect"));
261 const auto targetBoundingRect =
262 QRectF::fromDOMRect(target.call<emscripten::val>("getBoundingClientRect"));
263
264 const auto offset = sourceBoundingRect.topLeft() - targetBoundingRect.topLeft();
265 return point + offset;
266}
267
268void drawImageToWebImageDataArray(const QImage &sourceImage, emscripten::val destinationImageData,
269 const QRect &sourceRect)
270{
271 Q_ASSERT_X(destinationImageData["constructor"]["name"].as<std::string>() == "ImageData",
272 Q_FUNC_INFO, "The destination should be an ImageData instance");
273
274 constexpr int BytesPerColor = 4;
275 if (sourceRect.width() == sourceImage.width()) {
276 // Copy a contiguous chunk of memory
277 // ...............
278 // OOOOOOOOOOOOOOO
279 // OOOOOOOOOOOOOOO -> image data
280 // OOOOOOOOOOOOOOO
281 // ...............
282 auto imageMemory = emscripten::typed_memory_view(sourceRect.width() * sourceRect.height()
283 * BytesPerColor,
284 sourceImage.constScanLine(sourceRect.y()));
285 destinationImageData["data"].call<void>(
286 "set", imageMemory, sourceRect.y() * sourceImage.width() * BytesPerColor);
287 } else {
288 // Go through the scanlines manually to set the individual lines in bulk. This is
289 // marginally less performant than the above.
290 // ...............
291 // ...OOOOOOOOO... r = 0 -> image data
292 // ...OOOOOOOOO... r = 1 -> image data
293 // ...OOOOOOOOO... r = 2 -> image data
294 // ...............
295 for (int row = 0; row < sourceRect.height(); ++row) {
296 auto scanlineMemory =
297 emscripten::typed_memory_view(sourceRect.width() * BytesPerColor,
298 sourceImage.constScanLine(row + sourceRect.y())
299 + BytesPerColor * sourceRect.x());
300 destinationImageData["data"].call<void>("set", scanlineMemory,
301 (sourceRect.y() + row) * sourceImage.width()
302 * BytesPerColor
303 + sourceRect.x() * BytesPerColor);
304 }
305 }
306}
307
308} // namespace dom
309
310QT_END_NAMESPACE
\inmodule QtCore\reentrant
Definition qpoint.h:29
Definition qwasmdom.h:29
DataTransfer(DataTransfer &&other)
void toMimeDataWithFile(std::function< void(QMimeData *)> callback)
Definition qwasmdom.cpp:88
DataTransfer(const DataTransfer &other)
void setDataFromMimeData(const QMimeData &mimeData)
Definition qwasmdom.cpp:72
QMimeData * toMimeDataPreview()
Definition qwasmdom.cpp:229
DataTransfer & operator=(const DataTransfer &other)
DataTransfer & operator=(DataTransfer &&other)
void setData(std::string format, std::string data)
Definition qwasmdom.cpp:61
void setDropAction(Qt::DropAction dropAction)
Definition qwasmdom.cpp:67