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
qwasmdrag.cpp
Go to the documentation of this file.
1// Copyright (C) 2023 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
5#include "qwasmdrag.h"
6
8#include "qwasmdom.h"
9#include "qwasmevent.h"
11
12#include <qpa/qwindowsysteminterface.h>
13
14#include <QtCore/private/qstdweb_p.h>
15#include <QtCore/qeventloop.h>
16#include <QtCore/qmimedata.h>
17#include <QtCore/qtimer.h>
18#include <QtGui/QPainter>
19#include <QtGui/QImage>
20#include <QtGui/QPixmap>
21#include <QFile>
22
23#include <private/qshapedpixmapdndwindow_p.h>
24#include <private/qdnd_p.h>
25
26#include <functional>
27#include <string>
28#include <utility>
29
31
32namespace {
33
34QWindow *windowForDrag(QDrag *drag)
35{
36 QObject *source = drag->source();
37 QWindow *window = nullptr;
38 while (source && !window) {
39 window = qobject_cast<QWindow *>(source);
40 if (!window && source->metaObject()->indexOfMethod("_q_closestWindowHandle()") != -1)
41 QMetaObject::invokeMethod(source, "_q_closestWindowHandle",
42 Q_RETURN_ARG(QWindow *, window));
43 if (!window)
44 source = source->parent();
45 }
46 return window;
47}
48
49} // namespace
50
51struct QWasmDrag::DragState
52{
54 {
55 public:
56 DragImage(const QPixmap &pixmap, const QMimeData *mimeData, QWindow *window);
58
59 emscripten::val htmlElement() { return m_imageDomElement; }
60 QPixmap pixmap() const { return m_pixmap; }
61
62 private:
63 void generateDragImage(const QPixmap &pixmap, const QMimeData *mimeData);
64 void generateDragImageFromText(const QMimeData *mimeData);
65 void generateDefaultDragImage();
66 void generateDragImageFromPixmap(const QPixmap &pixmap);
67
68 emscripten::val m_imageDomElement = emscripten::val::undefined();
69 emscripten::val m_temporaryImageElementParent = emscripten::val::undefined();
70 QPixmap m_pixmap;
71 };
72
73 DragState(QDrag *drag, QWindow *window, std::function<void()> quitEventLoopClosure);
75 DragState(const QWasmDrag &other) = delete;
76 DragState(QWasmDrag &&other) = delete;
77 DragState &operator=(const QWasmDrag &other) = delete;
78 DragState &operator=(QWasmDrag &&other) = delete;
79
81 QWindow *window;
82 std::function<void()> quitEventLoopClosure;
85};
86
87QWasmDrag::QWasmDrag() = default;
88
89QWasmDrag::~QWasmDrag() = default;
90
91QWasmDrag *QWasmDrag::instance()
92{
93 return static_cast<QWasmDrag *>(QWasmIntegration::get()->drag());
94}
95
96Qt::DropAction QWasmDrag::drag(QDrag *drag)
97{
98 Q_ASSERT_X(!m_dragState, Q_FUNC_INFO, "Drag already in progress");
99
100 QWindow *window = windowForDrag(drag);
101
102 Qt::DropAction dragResult = Qt::IgnoreAction;
103 if (!qstdweb::haveAsyncify())
104 return dragResult;
105
106 auto dragState = std::make_shared<DragState>(drag, window, [this]() { QSimpleDrag::cancelDrag(); });
107
108 if (!m_isInEnterDrag)
109 m_dragState = dragState;
110
111 if (m_isInEnterDrag)
112 drag->setPixmap(QPixmap());
113 else if (drag->pixmap().size() == QSize(0, 0))
114 drag->setPixmap(dragState->dragImage->pixmap());
115
116 dragResult = QSimpleDrag::drag(drag);
117 m_dragState.reset();
118
119 return dragResult;
120}
121
122void QWasmDrag::onNativeDragStarted(DragEvent *event)
123{
124 Q_ASSERT_X(event->type == EventType::DragStart, Q_FUNC_INFO,
125 "The event is not a DragStart event");
126
127 // It is possible for a drag start event to arrive from another window.
128 if (!m_dragState || m_dragState->window != event->targetWindow) {
130 return;
131 }
132 setExecutedDropAction(event->dropAction);
133
134 // We have our own window
135 if (shapedPixmapWindow())
136 shapedPixmapWindow()->setVisible(false);
137
138 event->dataTransfer.setDragImage(m_dragState->dragImage->htmlElement(),
139 m_dragState->drag->hotSpot());
140 event->dataTransfer.setDataFromMimeData(*m_dragState->drag->mimeData());
141}
142
143void QWasmDrag::onNativeDragOver(DragEvent *event)
144{
145 event->webEvent.call<void>("preventDefault");
146
147 auto mimeDataPreview = event->dataTransfer.toMimeDataPreview();
148
149 const Qt::DropActions actions = m_dragState
150 ? m_dragState->drag->supportedActions()
151 : (Qt::DropAction::CopyAction | Qt::DropAction::MoveAction
152 | Qt::DropAction::LinkAction);
153
154 const auto dragResponse = QWindowSystemInterface::handleDrag(
155 event->targetWindow, &*mimeDataPreview, event->pointInPage.toPoint(), actions,
156 event->mouseButton, event->modifiers);
158 if (dragResponse.isAccepted()) {
159 setExecutedDropAction(dragResponse.acceptedAction());
160 event->dataTransfer.setDropAction(dragResponse.acceptedAction());
161 } else {
162 setExecutedDropAction(Qt::DropAction::IgnoreAction);
163 event->dataTransfer.setDropAction(Qt::DropAction::IgnoreAction);
164 }
165}
166
167void QWasmDrag::onNativeDrop(DragEvent *event)
168{
169 event->webEvent.call<void>("preventDefault");
170
172
173 const auto screenElementPos = dom::mapPoint(
174 event->target(), wasmWindow->platformScreen()->element(), event->localPoint);
175 const auto screenPos =
176 wasmWindow->platformScreen()->mapFromLocal(screenElementPos);
177 const QPoint targetWindowPos = event->targetWindow->mapFromGlobal(screenPos).toPoint();
178
179 const Qt::DropActions actions = m_dragState
180 ? m_dragState->drag->supportedActions()
181 : (Qt::DropAction::CopyAction | Qt::DropAction::MoveAction
182 | Qt::DropAction::LinkAction);
183 Qt::MouseButton mouseButton = event->mouseButton;
184 QFlags<Qt::KeyboardModifier> modifiers = event->modifiers;
185
186 // Accept the native drop event: We are going to async read any dropped
187 // files, but the browser expects that accepted state is set before any
188 // async calls.
189 event->acceptDrop();
190 setExecutedDropAction(event->dropAction);
191 std::shared_ptr<DragState> dragState = m_dragState;
192
193 const auto dropCallback = [this, dragState, wasmWindow, targetWindowPos,
194 actions, mouseButton, modifiers](QMimeData *mimeData) {
195
196 if (mimeData) {
197 const QPlatformDropQtResponse dropResponse =
198 QWindowSystemInterface::handleDrop(wasmWindow->window(), mimeData,
199 targetWindowPos, actions,
200 mouseButton, modifiers);
201
202 if (dragState && dropResponse.isAccepted())
203 dragState->dropAction = dropResponse.acceptedAction();
204
205 delete mimeData;
206 }
207
208 if (!dragState)
209 QSimpleDrag::cancelDrag();
210 };
211
212 event->dataTransfer.toMimeDataWithFile(dropCallback);
213}
214
215void QWasmDrag::onNativeDragFinished(DragEvent *event, QWasmScreen *platformScreen)
216{
217 // Keep sourcewindow before it is reset
218 QPointer<QWindow> sourceWindow = m_sourceWindow;
219
220 event->webEvent.call<void>("preventDefault");
221
222 if (m_dragState)
223 m_dragState->dropAction = event->dropAction;
224
225 setExecutedDropAction(event->dropAction);
226
227 if (m_dragState)
228 m_dragState->quitEventLoopClosure();
229
230
231 // Send synthetic mouserelease event
232 const MouseEvent mouseEvent(*event);
233
234 const auto pointInScreen = platformScreen->mapFromLocal(
235 dom::mapPoint(mouseEvent.target(), platformScreen->element(), mouseEvent.localPoint));
236 const auto geometryF = platformScreen->geometry().toRectF();
237 QPointF targetPointClippedToScreen(
238 qBound(geometryF.left(), pointInScreen.x(), geometryF.right()),
239 qBound(geometryF.top(), pointInScreen.y(), geometryF.bottom()));
240
241 QTimer::singleShot(0, [sourceWindow, targetPointClippedToScreen, mouseEvent]() {
242 if (sourceWindow) {
243 const QEvent::Type eventType = QEvent::MouseButtonRelease;
244 QWindowSystemInterface::handleMouseEvent(
245 sourceWindow, QWasmIntegration::getTimestamp(),
246 sourceWindow->mapFromGlobal(targetPointClippedToScreen),
247 targetPointClippedToScreen, mouseEvent.mouseButtons, mouseEvent.mouseButton,
248 eventType, mouseEvent.modifiers);
249 }
250 });
251}
252
253void QWasmDrag::onNativeDragEnter(DragEvent *event)
254{
255 event->webEvent.call<void>("preventDefault");
256
257 // Already dragging
258 if (QDragManager::self() && QDragManager::self()->object())
259 return;
260
261 // Event coming from external browser, start a drag
262 if (m_dragState)
263 m_dragState->dropAction = event->dropAction;
264
265 setExecutedDropAction(event->dropAction);
266
267 m_isInEnterDrag = true;
268 QDrag *drag = new QDrag(this);
269 drag->setMimeData(event->dataTransfer.toMimeDataPreview());
270 drag->exec(Qt::CopyAction | Qt::MoveAction, Qt::CopyAction);
271 m_isInEnterDrag = false;
272}
273
274void QWasmDrag::onNativeDragLeave(DragEvent *event)
275{
276 event->webEvent.call<void>("preventDefault");
277 if (m_dragState)
278 m_dragState->dropAction = event->dropAction;
279 setExecutedDropAction(event->dropAction);
280
281 event->dataTransfer.setDropAction(Qt::DropAction::IgnoreAction);
282
283 // If we started the drag from onNativeDragEnter
284 // it is correct to cancel the drag.
285 if (m_isInEnterDrag)
286 cancelDrag();
287}
288
289QWasmDrag::DragState::DragImage::DragImage(const QPixmap &pixmap, const QMimeData *mimeData,
290 QWindow *window)
291{
292 if (window)
293 m_temporaryImageElementParent = QWasmWindow::fromWindow(window)->containerElement();
294 generateDragImage(pixmap, mimeData);
295
296 m_imageDomElement.set("className", "hidden-drag-image");
297
298 // chromium requires the image to be the first child
299 if (m_temporaryImageElementParent.isUndefined())
300 ;
301 else if (m_temporaryImageElementParent["childElementCount"].as<int>() == 0)
302 m_temporaryImageElementParent.call<void>("appendChild", m_imageDomElement);
303 else
304 m_temporaryImageElementParent.call<void>("insertBefore", m_imageDomElement, m_temporaryImageElementParent["children"][0]);
305}
306
308{
309 if (!m_temporaryImageElementParent.isUndefined())
310 m_temporaryImageElementParent.call<void>("removeChild", m_imageDomElement);
311}
312
313void QWasmDrag::DragState::DragImage::generateDragImage(
314 const QPixmap &pixmap,
315 const QMimeData *mimeData)
316{
317 if (!pixmap.isNull())
318 generateDragImageFromPixmap(pixmap);
319 else if (mimeData && mimeData->hasFormat("text/plain"))
320 generateDragImageFromText(mimeData);
321 else
322 generateDefaultDragImage();
323}
324
325void QWasmDrag::DragState::DragImage::generateDragImageFromText(const QMimeData *mimeData)
326{
327 emscripten::val dragImageElement =
328 emscripten::val::global("document")
329 .call<emscripten::val>("createElement", emscripten::val("span"));
330
331 constexpr qsizetype MaxCharactersInDragImage = 100;
332
333 const auto text = QString::fromUtf8(mimeData->data("text/plain"));
334 dragImageElement.set(
335 "innerText",
336 text.first(qMin(qsizetype(MaxCharactersInDragImage), text.length())).toStdString());
337
338 QRect bounds;
339 {
340 QPixmap image(QSize(200,200));
341 if (!image.isNull()) {
342 QPainter painter(&image);
343 bounds = painter.boundingRect(0, 0, 200, 200, 0, text);
344 }
345 }
346 QImage image(bounds.size(), QImage::Format_RGBA8888);
347 if (!image.isNull()) {
348 QPainter painter(&image);
349 painter.fillRect(bounds, QColor(255,255,255, 255)); // Transparency does not work very well :-(
350 painter.setPen(Qt::black);
351 painter.drawText(bounds, text);
352
353 m_pixmap = QPixmap::fromImage(image);
354 }
355 m_imageDomElement = dragImageElement;
356}
357
358void QWasmDrag::DragState::DragImage::generateDefaultDragImage()
359{
360 emscripten::val dragImageElement =
361 emscripten::val::global("document")
362 .call<emscripten::val>("createElement", emscripten::val("div"));
363
364 auto innerImgElement = emscripten::val::global("document")
365 .call<emscripten::val>("createElement", emscripten::val("img"));
366 innerImgElement.set("src",
367 "data:image/" + std::string("svg+xml") + ";base64,"
368 + std::string(Base64IconStore::get()->getIcon(
369 Base64IconStore::IconType::QtLogo)));
370
371 constexpr char DragImageSize[] = "50px";
372
373 dragImageElement["style"].set("width", DragImageSize);
374 innerImgElement["style"].set("width", DragImageSize);
375 dragImageElement["style"].set("display", "flex");
376
377 dragImageElement.call<void>("appendChild", innerImgElement);
378
379 QByteArray pixmap_ba =
380 QByteArray::fromBase64(
381 QString::fromLatin1(
382 Base64IconStore::get()->getIcon(
383 Base64IconStore::IconType::QtLogo)).toLocal8Bit());
384
385 if (!m_pixmap.loadFromData(pixmap_ba))
386 qWarning() << " Load of Qt logo failed";
387
388 m_pixmap = m_pixmap.scaled(50, 50);
389 m_imageDomElement = dragImageElement;
390}
391
392void QWasmDrag::DragState::DragImage::generateDragImageFromPixmap(const QPixmap &pixmap)
393{
394 emscripten::val dragImageElement =
395 emscripten::val::global("document")
396 .call<emscripten::val>("createElement", emscripten::val("canvas"));
397 dragImageElement.set("width", pixmap.width());
398 dragImageElement.set("height", pixmap.height());
399
400 dragImageElement["style"].set(
401 "width", std::to_string(pixmap.width() / pixmap.devicePixelRatio()) + "px");
402 dragImageElement["style"].set(
403 "height", std::to_string(pixmap.height() / pixmap.devicePixelRatio()) + "px");
404
405 auto context2d = dragImageElement.call<emscripten::val>("getContext", emscripten::val("2d"));
406 auto imageData = context2d.call<emscripten::val>(
407 "createImageData", emscripten::val(pixmap.width()), emscripten::val(pixmap.height()));
408
409 dom::drawImageToWebImageDataArray(pixmap.toImage().convertedTo(QImage::Format::Format_RGBA8888),
410 imageData, QRect(0, 0, pixmap.width(), pixmap.height()));
411 context2d.call<void>("putImageData", imageData, emscripten::val(0), emscripten::val(0));
412
413 m_pixmap = pixmap;
414 m_imageDomElement = dragImageElement;
415}
416
417QWasmDrag::DragState::DragState(QDrag *drag, QWindow *window,
418 std::function<void()> quitEventLoopClosure)
420{
421 dragImage =
422 std::make_unique<DragState::DragImage>(
423 drag->pixmap(),
424 drag->mimeData(),
425 window);
426}
427
428QWasmDrag::DragState::~DragState() = default;
429
430QT_END_NAMESPACE
\inmodule QtCore\reentrant
Definition qpoint.h:30
DragImage(const QPixmap &pixmap, const QMimeData *mimeData, QWindow *window)
emscripten::val htmlElement()
Definition qwasmdrag.cpp:59
Qt::DropAction drag(QDrag *drag) final
Definition qwasmdrag.cpp:96
void onNativeDrop(DragEvent *event)
void onNativeDragEnter(DragEvent *event)
void onNativeDragLeave(DragEvent *event)
void onNativeDragOver(DragEvent *event)
~QWasmDrag() override
static QWasmDrag * instance()
Definition qwasmdrag.cpp:91
void onNativeDragStarted(DragEvent *event)
void onNativeDragFinished(DragEvent *event, QWasmScreen *platformScreen)
static QWasmIntegration * get()
emscripten::val element() const
QRect geometry() const override
Reimplement in subclass to return the pixel geometry of the screen.
static QWasmWindow * fromWindow(const QWindow *window)
friend class QWasmCompositor
QWasmScreen * platformScreen() const
Combined button and popup list for selecting options.
QWindow * windowForDrag(QDrag *drag)
Definition qwasmdrag.cpp:34
Definition qwasmdom.h:30
EventType
Definition qwasmevent.h:23
void acceptDrop()
void acceptDragOver()
void cancelDragStart()
QWindow * targetWindow
Definition qwasmevent.h:152
DragState(QWasmDrag &&other)=delete
std::function< void()> quitEventLoopClosure
Definition qwasmdrag.cpp:82
DragState(QDrag *drag, QWindow *window, std::function< void()> quitEventLoopClosure)
std::unique_ptr< DragImage > dragImage
Definition qwasmdrag.cpp:83
Qt::DropAction dropAction
Definition qwasmdrag.cpp:84
DragState(const QWasmDrag &other)=delete
DragState & operator=(QWasmDrag &&other)=delete
DragState & operator=(const QWasmDrag &other)=delete