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