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
qsimpledrag.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
6
7#include "qbitmap.h"
8#include "qdrag.h"
9#include "qpixmap.h"
10#include "qevent.h"
11#include "qfile.h"
13#include "qpoint.h"
14#include "qbuffer.h"
15#include "qimage.h"
16#include "qdir.h"
17#include "qimagereader.h"
18#include "qimagewriter.h"
21
22#include <QtCore/QEventLoop>
23#include <QtCore/QDebug>
24#include <QtCore/QLoggingCategory>
25
26#include <private/qguiapplication_p.h>
27#include <private/qdnd_p.h>
28
29#include <private/qshapedpixmapdndwindow_p.h>
30#include <private/qhighdpiscaling_p.h>
31
33
34Q_STATIC_LOGGING_CATEGORY(lcDnd, "qt.gui.dnd")
35
36static QWindow* topLevelAt(const QPoint &pos)
37{
38 const QWindowList list = QGuiApplication::topLevelWindows();
39 const auto crend = list.crend();
40 for (auto it = list.crbegin(); it != crend; ++it) {
41 QWindow *w = *it;
42 if (w->isVisible() && w->handle() && w->geometry().contains(pos) && !qobject_cast<QShapedPixmapWindow*>(w))
43 return w;
44 }
45 return nullptr;
46}
47
48/*!
49 \class QBasicDrag
50 \brief QBasicDrag is a base class for implementing platform drag and drop.
51 \since 5.0
52 \internal
53 \ingroup qpa
54
55 QBasicDrag implements QPlatformDrag::drag() by running a local event loop in which
56 it tracks mouse movements and moves the drag icon (QShapedPixmapWindow) accordingly.
57 It provides new virtuals allowing for querying whether the receiving window
58 (within the Qt application or outside) accepts the drag and sets the state accordingly.
59*/
60
61QBasicDrag::QBasicDrag()
62{
63}
64
65QBasicDrag::~QBasicDrag()
66{
67 delete m_drag_icon_window;
68}
69
70void QBasicDrag::enableEventFilter()
71{
72 qApp->installEventFilter(this);
73}
74
75void QBasicDrag::disableEventFilter()
76{
77 qApp->removeEventFilter(this);
78}
79
80
81static inline QPoint getNativeMousePos(QEvent *e, QWindow *window)
82{
83 return QHighDpi::toNativePixels(static_cast<QMouseEvent *>(e)->globalPosition().toPoint(), window);
84}
85
86bool QBasicDrag::eventFilter(QObject *o, QEvent *e)
87{
88 Q_UNUSED(o);
89
90 if (!m_drag) {
91 if (e->type() == QEvent::KeyRelease && static_cast<QKeyEvent*>(e)->key() == Qt::Key_Escape) {
92 disableEventFilter();
93 exitDndEventLoop();
94 return true; // block the key release
95 }
96 return false;
97 }
98
99 switch (e->type()) {
100 case QEvent::ShortcutOverride:
101 // prevent accelerators from firing while dragging
102 e->accept();
103 return true;
104
105 case QEvent::KeyPress:
106 case QEvent::KeyRelease:
107 {
108 QKeyEvent *ke = static_cast<QKeyEvent *>(e);
109 if (ke->key() == Qt::Key_Escape && e->type() == QEvent::KeyPress) {
110 cancel();
111 disableEventFilter();
112 exitDndEventLoop();
113
114 } else if (ke->modifiers() != QGuiApplication::keyboardModifiers()) {
115 move(m_lastPos, QGuiApplication::mouseButtons(), ke->modifiers());
116 }
117 return true; // Eat all key events
118 }
119
120 case QEvent::MouseMove:
121 {
122 m_lastPos = getNativeMousePos(e, m_drag_icon_window);
123 auto mouseMove = static_cast<QMouseEvent *>(e);
124 move(m_lastPos, mouseMove->buttons(), mouseMove->modifiers());
125 return true; // Eat all mouse move events
126 }
127 case QEvent::MouseButtonRelease:
128 {
129 QPointer<QObject> objGuard(o);
130 disableEventFilter();
131 if (canDrop()) {
132 QPoint nativePosition = getNativeMousePos(e, m_drag_icon_window);
133 auto mouseRelease = static_cast<QMouseEvent *>(e);
134 drop(nativePosition, mouseRelease->buttons(), mouseRelease->modifiers());
135 } else {
136 cancel();
137 }
138 exitDndEventLoop();
139 if (!objGuard)
140 return true;
141
142 // If a QShapedPixmapWindow (drag feedback) is being dragged along, the
143 // mouse event's localPos() will be relative to that, which is useless.
144 // We want a position relative to the window where the drag ends, if possible (?).
145 // If there is no such window (belonging to this Qt application),
146 // make the event relative to the window where the drag started. (QTBUG-66103)
147 const QMouseEvent *release = static_cast<QMouseEvent *>(e);
148 const QWindow *releaseWindow = topLevelAt(release->globalPosition().toPoint());
149 qCDebug(lcDnd) << "mouse released over" << releaseWindow << "after drag from" << m_sourceWindow << "globalPos" << release->globalPosition().toPoint();
150 if (!releaseWindow)
151 releaseWindow = m_sourceWindow;
152 QPointF releaseWindowPos = (releaseWindow ? releaseWindow->mapFromGlobal(release->globalPosition()) : release->globalPosition());
153 QMouseEvent *newRelease = new QMouseEvent(release->type(),
154 releaseWindowPos, releaseWindowPos, release->globalPosition(),
155 release->button(), release->buttons(),
156 release->modifiers(), release->source(), release->pointingDevice());
157 QCoreApplication::postEvent(o, newRelease);
158 return true; // defer mouse release events until drag event loop has returned
159 }
160 case QEvent::MouseButtonDblClick:
161 case QEvent::Wheel:
162 return true;
163 default:
164 break;
165 }
166 return false;
167}
168
169Qt::DropAction QBasicDrag::drag(QDrag *o)
170{
171 m_drag = o;
172 m_executed_drop_action = Qt::IgnoreAction;
173 m_can_drop = false;
174
175 // Create the loop before starting the drag. startDrag() installs the event filter
176 // and delivers the first drag events, so the drag can already be cancelled,
177 // dropped or destroyed before we get here, and exitDndEventLoop() and cancelDrag()
178 // both no-op while m_eventLoop is null. QEventLoop::exec() clears any pending exit
179 // when it starts, so we need a flag to avoid losing track of an early exit.
180 m_dragEndRequested = false;
181 m_eventLoop = new QEventLoop;
182 startDrag();
183 qCDebug(lcDnd) << "entering drag event loop; drag" << m_drag
184 << "already ended?" << m_dragEndRequested;
185 if (m_drag && !m_dragEndRequested)
186 m_eventLoop->exec();
187 qCDebug(lcDnd) << "left drag event loop; action" << m_executed_drop_action;
188 delete m_eventLoop;
189 m_eventLoop = nullptr;
190 m_drag = nullptr;
191 endDrag();
192
193 return m_executed_drop_action;
194}
195
196void QBasicDrag::cancelDrag()
197{
198 qCDebug(lcDnd) << "cancelling drag";
199 if (m_eventLoop) {
200 cancel();
201 m_dragEndRequested = true;
202 m_eventLoop->quit();
203 }
204}
205
206void QBasicDrag::startDrag()
207{
208 QPoint pos;
209#ifndef QT_NO_CURSOR
210 pos = QCursor::pos();
211 static constexpr QGuiApplicationPrivate::QLastCursorPosition uninitializedCursorPosition;
212 if (pos == uninitializedCursorPosition) {
213 // ### fixme: no mouse pos registered. Get pos from touch...
214 pos = QPoint();
215 }
216#endif
217 m_lastPos = pos;
218 recreateShapedPixmapWindow(m_screen, pos);
219 enableEventFilter();
220}
221
222void QBasicDrag::endDrag()
223{
224}
225
226void QBasicDrag::recreateShapedPixmapWindow(QScreen *screen, const QPoint &pos)
227{
228 delete m_drag_icon_window;
229 // ### TODO Check if its really necessary to have m_drag_icon_window
230 // when QDrag is used without a pixmap - QDrag::setPixmap()
231 m_drag_icon_window = new QShapedPixmapWindow(screen);
232
233 m_drag_icon_window->setUseCompositing(m_useCompositing);
234 m_drag_icon_window->setPixmap(m_drag->pixmap());
235 m_drag_icon_window->setHotspot(m_drag->hotSpot());
236 m_drag_icon_window->updateGeometry(pos);
237 m_drag_icon_window->setVisible(true);
238}
239
240void QBasicDrag::cancel()
241{
242 disableEventFilter();
243 restoreCursor();
244 // Can be reached before startDrag() has created the icon window, now that the event
245 // loop exists for the duration of startDrag() too.
246 if (m_drag_icon_window)
247 m_drag_icon_window->setVisible(false);
248}
249
250/*!
251 Move the drag label to \a globalPos, which is
252 interpreted in device independent coordinates. Typically called from reimplementations of move().
253 */
254
255void QBasicDrag::moveShapedPixmapWindow(const QPoint &globalPos)
256{
257 if (m_drag)
258 m_drag_icon_window->updateGeometry(globalPos);
259}
260
261void QBasicDrag::drop(const QPoint &, Qt::MouseButtons, Qt::KeyboardModifiers)
262{
263 disableEventFilter();
264 restoreCursor();
265 m_drag_icon_window->setVisible(false);
266}
267
268void QBasicDrag::exitDndEventLoop()
269{
270 m_dragEndRequested = true;
271 qCDebug(lcDnd) << "ending drag event loop; running?"
272 << (m_eventLoop && m_eventLoop->isRunning());
273 if (m_eventLoop && m_eventLoop->isRunning())
274 m_eventLoop->exit();
275}
276
277void QBasicDrag::updateCursor(Qt::DropAction action)
278{
279 // In case QDrag is destroyed from a drag event handler, cancel() has
280 // already restored the cursor: nothing left to do.
281 if (!m_drag)
282 return;
283
284#ifndef QT_NO_CURSOR
285 Qt::CursorShape cursorShape = Qt::ForbiddenCursor;
286 if (canDrop()) {
287 switch (action) {
288 case Qt::CopyAction:
289 cursorShape = Qt::DragCopyCursor;
290 break;
291 case Qt::LinkAction:
292 cursorShape = Qt::DragLinkCursor;
293 break;
294 default:
295 cursorShape = Qt::DragMoveCursor;
296 break;
297 }
298 }
299
300 QPixmap pixmap = m_drag->dragCursor(action);
301
302 if (!m_dndHasSetOverrideCursor) {
303 QCursor newCursor = !pixmap.isNull() ? QCursor(pixmap) : QCursor(cursorShape);
304 QGuiApplication::setOverrideCursor(newCursor);
305 m_dndHasSetOverrideCursor = true;
306 } else {
307 QCursor *cursor = QGuiApplication::overrideCursor();
308 if (!cursor) {
309 QGuiApplication::changeOverrideCursor(pixmap.isNull() ? QCursor(cursorShape) : QCursor(pixmap));
310 } else {
311 if (!pixmap.isNull()) {
312 if (cursor->pixmap().cacheKey() != pixmap.cacheKey())
313 QGuiApplication::changeOverrideCursor(QCursor(pixmap));
314 } else if (cursorShape != cursor->shape()) {
315 QGuiApplication::changeOverrideCursor(QCursor(cursorShape));
316 }
317 }
318 }
319#endif
320 updateAction(action);
321}
322
323void QBasicDrag::restoreCursor()
324{
325#ifndef QT_NO_CURSOR
326 if (m_dndHasSetOverrideCursor) {
327 QGuiApplication::restoreOverrideCursor();
328 m_dndHasSetOverrideCursor = false;
329 }
330#endif
331}
332
333static inline QPoint fromNativeGlobalPixels(const QPoint &point)
334{
335#ifndef QT_NO_HIGHDPISCALING
336 QPoint res = point;
337 if (QHighDpiScaling::isActive()) {
338 for (const QScreen *s : std::as_const(QGuiApplicationPrivate::screen_list)) {
339 if (s->handle()->geometry().contains(point)) {
340 res = QHighDpi::fromNativePixels(point, s);
341 break;
342 }
343 }
344 }
345 return res;
346#else
347 return point;
348#endif
349}
350
351/*!
352 \class QSimpleDrag
353 \brief QSimpleDrag implements QBasicDrag for Drag and Drop operations within the Qt Application itself.
354 \since 5.0
355 \internal
356 \ingroup qpa
357
358 The class checks whether the receiving window is a window of the Qt application
359 and sets the state accordingly. It does not take windows of other applications
360 into account.
361*/
362
363QSimpleDrag::QSimpleDrag()
364{
365}
366
367void QSimpleDrag::startDrag()
368{
369 setExecutedDropAction(Qt::IgnoreAction);
370
371 QBasicDrag::startDrag();
372 // Here we can be fairly sure that QGuiApplication::mouseButtons/keyboardModifiers() will
373 // contain sensible values as startDrag() normally is called from mouse event handlers
374 // by QDrag::exec(). A better API would be if we could pass something like "input device
375 // pointer" to QDrag::exec(). My guess is that something like that might be required for
376 // QTBUG-52430.
377 m_sourceWindow = topLevelAt(QCursor::pos());
378 m_windowUnderCursor = m_sourceWindow;
379 if (m_sourceWindow) {
380 auto nativePixelPos = QHighDpi::toNativePixels(QCursor::pos(), m_sourceWindow);
381 move(nativePixelPos, QGuiApplication::mouseButtons(), QGuiApplication::keyboardModifiers());
382 } else {
383 setCanDrop(false);
384 updateCursor(Qt::IgnoreAction);
385 }
386
387 qCDebug(lcDnd) << "drag began from" << m_sourceWindow << "cursor pos" << QCursor::pos() << "can drop?" << canDrop();
388}
389
390static void sendDragLeave(QWindow *window)
391{
392 QWindowSystemInterface::handleDrag(window, nullptr, QPoint(), Qt::IgnoreAction, { }, { });
393}
394
395void QSimpleDrag::cancel()
396{
397 QBasicDrag::cancel();
398 if (drag()) {
399 // Leave the window the drag is currently over, which is only the window it
400 // started from until the first move() onto another window. Leaving
401 // m_sourceWindow instead would send it a second QDragLeaveEvent and leave the
402 // window actually under the cursor believing a drag is still in progress.
403 if (m_windowUnderCursor)
404 sendDragLeave(m_windowUnderCursor);
405 m_windowUnderCursor = nullptr;
406 m_sourceWindow = nullptr;
407 }
408}
409
410void QSimpleDrag::move(const QPoint &nativeGlobalPos, Qt::MouseButtons buttons,
411 Qt::KeyboardModifiers modifiers)
412{
413 if (!drag())
414 return;
415
416 QPoint globalPos = fromNativeGlobalPixels(nativeGlobalPos);
417 moveShapedPixmapWindow(globalPos);
418 QWindow *window = topLevelAt(globalPos);
419
420 if (!window || window != m_windowUnderCursor) {
421 if (m_windowUnderCursor)
422 sendDragLeave(m_windowUnderCursor);
423 m_windowUnderCursor = window;
424 if (!window) {
425 // QSimpleDrag supports only in-process dnd, we can't drop anywhere else.
426 setCanDrop(false);
427 updateCursor(Qt::IgnoreAction);
428 return;
429 }
430 }
431
432 const QPoint pos = nativeGlobalPos - window->handle()->geometry().topLeft();
433 const QPlatformDragQtResponse qt_response = QWindowSystemInterface::handleDrag(
434 window, drag()->mimeData(), pos, drag()->supportedActions(),
435 buttons, modifiers);
436
437 setCanDrop(qt_response.isAccepted());
438 updateCursor(qt_response.acceptedAction());
439}
440
441void QSimpleDrag::drop(const QPoint &nativeGlobalPos, Qt::MouseButtons buttons,
442 Qt::KeyboardModifiers modifiers)
443{
444 if (!drag())
445 return;
446
447 QPoint globalPos = fromNativeGlobalPixels(nativeGlobalPos);
448
449 QBasicDrag::drop(nativeGlobalPos, buttons, modifiers);
450 QWindow *window = topLevelAt(globalPos);
451 if (!window)
452 return;
453
454 const QPoint pos = nativeGlobalPos - window->handle()->geometry().topLeft();
455 const QPlatformDropQtResponse response = QWindowSystemInterface::handleDrop(
456 window, drag()->mimeData(), pos, drag()->supportedActions(),
457 buttons, modifiers);
458 if (response.isAccepted()) {
459 setExecutedDropAction(response.acceptedAction());
460 } else {
461 setExecutedDropAction(Qt::IgnoreAction);
462 }
463}
464
465QT_END_NAMESPACE
\inmodule QtCore\reentrant
Definition qpoint.h:30
Combined button and popup list for selecting options.
#define qApp
static QPoint fromNativeGlobalPixels(const QPoint &point)
static void sendDragLeave(QWindow *window)
static QPoint getNativeMousePos(QEvent *e, QWindow *window)