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 <QFile>
18
19#include <functional>
20#include <string>
21#include <utility>
22
24
25namespace {
26
27QWindow *windowForDrag(QDrag *drag)
28{
29 QWindow *window = qobject_cast<QWindow *>(drag->source());
30 if (window)
31 return window;
32 if (drag->source()->metaObject()->indexOfMethod("_q_closestWindowHandle()") == -1)
33 return nullptr;
34
35 QMetaObject::invokeMethod(drag->source(), "_q_closestWindowHandle",
36 Q_RETURN_ARG(QWindow *, window));
37 return window;
38}
39
40} // namespace
41
42struct QWasmDrag::DragState
43{
45 {
46 public:
47 DragImage(const QPixmap &pixmap, const QMimeData *mimeData, QWindow *window);
49
51
52 private:
53 emscripten::val generateDragImage(const QPixmap &pixmap, const QMimeData *mimeData);
54 emscripten::val generateDragImageFromText(const QMimeData *mimeData);
55 emscripten::val generateDefaultDragImage();
56 emscripten::val generateDragImageFromPixmap(const QPixmap &pixmap);
57
58 emscripten::val m_imageDomElement;
59 emscripten::val m_temporaryImageElementParent;
60 };
61
62 DragState(QDrag *drag, QWindow *window, std::function<void()> quitEventLoopClosure);
64 DragState(const QWasmDrag &other) = delete;
65 DragState(QWasmDrag &&other) = delete;
66 DragState &operator=(const QWasmDrag &other) = delete;
67 DragState &operator=(QWasmDrag &&other) = delete;
68
70 QWindow *window;
71 std::function<void()> quitEventLoopClosure;
74};
75
76QWasmDrag::QWasmDrag() = default;
77
78QWasmDrag::~QWasmDrag() = default;
79
80QWasmDrag *QWasmDrag::instance()
81{
82 return static_cast<QWasmDrag *>(QWasmIntegration::get()->drag());
83}
84
85Qt::DropAction QWasmDrag::drag(QDrag *drag)
86{
87 Q_ASSERT_X(!m_dragState, Q_FUNC_INFO, "Drag already in progress");
88
89 QWindow *window = windowForDrag(drag);
90 if (!window)
91 return Qt::IgnoreAction;
92
93 Qt::DropAction dragResult = Qt::IgnoreAction;
94 if (qstdweb::haveJspi()) {
95 QEventLoop loop;
96 m_dragState = std::make_unique<DragState>(drag, window, [&loop]() { loop.quit(); });
97 loop.exec();
98 dragResult = m_dragState->dropAction;
99 m_dragState.reset();
100 }
101
102 if (dragResult == Qt::IgnoreAction)
103 dragResult = QBasicDrag::drag(drag);
104
105 return dragResult;
106}
107
108void QWasmDrag::onNativeDragStarted(DragEvent *event)
109{
110 Q_ASSERT_X(event->type == EventType::DragStart, Q_FUNC_INFO,
111 "The event is not a DragStart event");
112
113 event->webEvent.call<void>("preventDefault");
114
115 // It is possible for a drag start event to arrive from another window.
116 if (!m_dragState || m_dragState->window != event->targetWindow) {
118 return;
119 }
120
121 m_dragState->dragImage = std::make_unique<DragState::DragImage>(
122 m_dragState->drag->pixmap(), m_dragState->drag->mimeData(), event->targetWindow);
123 event->dataTransfer.setDragImage(m_dragState->dragImage->htmlElement(),
124 m_dragState->drag->hotSpot());
125 event->dataTransfer.setDataFromMimeData(*m_dragState->drag->mimeData());
126}
127
128void QWasmDrag::onNativeDragOver(DragEvent *event)
129{
130 event->webEvent.call<void>("preventDefault");
131
132 auto mimeDataPreview = event->dataTransfer.toMimeDataPreview();
133
134 const Qt::DropActions actions = m_dragState
135 ? m_dragState->drag->supportedActions()
136 : (Qt::DropAction::CopyAction | Qt::DropAction::MoveAction
137 | Qt::DropAction::LinkAction);
138
139 const auto dragResponse = QWindowSystemInterface::handleDrag(
140 event->targetWindow, &*mimeDataPreview, event->pointInPage.toPoint(), actions,
141 event->mouseButton, event->modifiers);
143 if (dragResponse.isAccepted()) {
144 event->dataTransfer.setDropAction(dragResponse.acceptedAction());
145 } else {
146 event->dataTransfer.setDropAction(Qt::DropAction::IgnoreAction);
147 }
148}
149
150void QWasmDrag::onNativeDrop(DragEvent *event)
151{
152 event->webEvent.call<void>("preventDefault");
153
155
156 const auto screenElementPos = dom::mapPoint(
157 event->target(), wasmWindow->platformScreen()->element(), event->localPoint);
158 const auto screenPos =
159 wasmWindow->platformScreen()->mapFromLocal(screenElementPos);
160 const QPoint targetWindowPos = event->targetWindow->mapFromGlobal(screenPos).toPoint();
161
162 const Qt::DropActions actions = m_dragState
163 ? m_dragState->drag->supportedActions()
164 : (Qt::DropAction::CopyAction | Qt::DropAction::MoveAction
165 | Qt::DropAction::LinkAction);
166 Qt::MouseButton mouseButton = event->mouseButton;
167 QFlags<Qt::KeyboardModifier> modifiers = event->modifiers;
168
169 // Accept the native drop event: We are going to async read any dropped
170 // files, but the browser expects that accepted state is set before any
171 // async calls.
172 event->acceptDrop();
173
174 const auto dropCallback = [&m_dragState = m_dragState, wasmWindow, targetWindowPos,
175 actions, mouseButton, modifiers](QMimeData *mimeData) {
176
177 auto dropResponse = std::make_shared<QPlatformDropQtResponse>(true, Qt::DropAction::CopyAction);
178 *dropResponse = QWindowSystemInterface::handleDrop(wasmWindow->window(), mimeData,
179 targetWindowPos, actions,
180 mouseButton, modifiers);
181
182 if (dropResponse->isAccepted())
183 m_dragState->dropAction = dropResponse->acceptedAction();
184
185 delete mimeData;
186 };
187
188 event->dataTransfer.toMimeDataWithFile(dropCallback);
189}
190
191void QWasmDrag::onNativeDragFinished(DragEvent *event)
192{
193 event->webEvent.call<void>("preventDefault");
194 m_dragState->dropAction = event->dropAction;
195 m_dragState->quitEventLoopClosure();
196}
197
198void QWasmDrag::onNativeDragLeave(DragEvent *event)
199{
200 event->webEvent.call<void>("preventDefault");
201 m_dragState->dropAction = event->dropAction;
202 event->dataTransfer.setDropAction(Qt::DropAction::IgnoreAction);
203}
204
205QWasmDrag::DragState::DragImage::DragImage(const QPixmap &pixmap, const QMimeData *mimeData,
206 QWindow *window)
208{
209 m_imageDomElement = generateDragImage(pixmap, mimeData);
210
211 m_imageDomElement.set("className", "hidden-drag-image");
212 m_temporaryImageElementParent.call<void>("appendChild", m_imageDomElement);
213}
214
216{
217 m_temporaryImageElementParent.call<void>("removeChild", m_imageDomElement);
218}
219
220emscripten::val QWasmDrag::DragState::DragImage::generateDragImage(const QPixmap &pixmap,
221 const QMimeData *mimeData)
222{
223 if (!pixmap.isNull())
224 return generateDragImageFromPixmap(pixmap);
225 if (mimeData->hasFormat("text/plain"))
226 return generateDragImageFromText(mimeData);
227 return generateDefaultDragImage();
228}
229
230emscripten::val
231QWasmDrag::DragState::DragImage::generateDragImageFromText(const QMimeData *mimeData)
232{
233 emscripten::val dragImageElement =
234 emscripten::val::global("document")
235 .call<emscripten::val>("createElement", emscripten::val("span"));
236
237 constexpr qsizetype MaxCharactersInDragImage = 100;
238
239 const auto text = QString::fromUtf8(mimeData->data("text/plain"));
240 dragImageElement.set(
241 "innerText",
242 text.first(qMin(qsizetype(MaxCharactersInDragImage), text.length())).toStdString());
243 return dragImageElement;
244}
245
246emscripten::val QWasmDrag::DragState::DragImage::generateDefaultDragImage()
247{
248 emscripten::val dragImageElement =
249 emscripten::val::global("document")
250 .call<emscripten::val>("createElement", emscripten::val("div"));
251
252 auto innerImgElement = emscripten::val::global("document")
253 .call<emscripten::val>("createElement", emscripten::val("img"));
254 innerImgElement.set("src",
255 "data:image/" + std::string("svg+xml") + ";base64,"
256 + std::string(Base64IconStore::get()->getIcon(
257 Base64IconStore::IconType::QtLogo)));
258
259 constexpr char DragImageSize[] = "50px";
260
261 dragImageElement["style"].set("width", DragImageSize);
262 innerImgElement["style"].set("width", DragImageSize);
263 dragImageElement["style"].set("display", "flex");
264
265 dragImageElement.call<void>("appendChild", innerImgElement);
266 return dragImageElement;
267}
268
269emscripten::val QWasmDrag::DragState::DragImage::generateDragImageFromPixmap(const QPixmap &pixmap)
270{
271 emscripten::val dragImageElement =
272 emscripten::val::global("document")
273 .call<emscripten::val>("createElement", emscripten::val("canvas"));
274 dragImageElement.set("width", pixmap.width());
275 dragImageElement.set("height", pixmap.height());
276
277 dragImageElement["style"].set(
278 "width", std::to_string(pixmap.width() / pixmap.devicePixelRatio()) + "px");
279 dragImageElement["style"].set(
280 "height", std::to_string(pixmap.height() / pixmap.devicePixelRatio()) + "px");
281
282 auto context2d = dragImageElement.call<emscripten::val>("getContext", emscripten::val("2d"));
283 auto imageData = context2d.call<emscripten::val>(
284 "createImageData", emscripten::val(pixmap.width()), emscripten::val(pixmap.height()));
285
286 dom::drawImageToWebImageDataArray(pixmap.toImage().convertedTo(QImage::Format::Format_RGBA8888),
287 imageData, QRect(0, 0, pixmap.width(), pixmap.height()));
288 context2d.call<void>("putImageData", imageData, emscripten::val(0), emscripten::val(0));
289
290 return dragImageElement;
291}
292
294{
295 return m_imageDomElement;
296}
297
298QWasmDrag::DragState::DragState(QDrag *drag, QWindow *window,
299 std::function<void()> quitEventLoopClosure)
301{
302}
303
304QWasmDrag::DragState::~DragState() = default;
305
306QT_END_NAMESPACE
\inmodule QtCore\reentrant
Definition qpoint.h:29
DragImage(const QPixmap &pixmap, const QMimeData *mimeData, QWindow *window)
void onNativeDragFinished(DragEvent *event)
Qt::DropAction drag(QDrag *drag) final
Definition qwasmdrag.cpp:85
void onNativeDrop(DragEvent *event)
void onNativeDragLeave(DragEvent *event)
void onNativeDragOver(DragEvent *event)
~QWasmDrag() override
static QWasmDrag * instance()
Definition qwasmdrag.cpp:80
void onNativeDragStarted(DragEvent *event)
static QWasmIntegration * get()
emscripten::val element() const
static QWasmWindow * fromWindow(const QWindow *window)
friend class QWasmCompositor
QWasmScreen * platformScreen() const
QWindow * windowForDrag(QDrag *drag)
Definition qwasmdrag.cpp:27
void acceptDrop()
void acceptDragOver()
void cancelDragStart()
QWindow * targetWindow
Definition qwasmevent.h:150
DragState(QWasmDrag &&other)=delete
std::function< void()> quitEventLoopClosure
Definition qwasmdrag.cpp:71
DragState(QDrag *drag, QWindow *window, std::function< void()> quitEventLoopClosure)
std::unique_ptr< DragImage > dragImage
Definition qwasmdrag.cpp:72
Qt::DropAction dropAction
Definition qwasmdrag.cpp:73
DragState(const QWasmDrag &other)=delete
DragState & operator=(QWasmDrag &&other)=delete
DragState & operator=(const QWasmDrag &other)=delete