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 startDrag();
176 m_eventLoop = new QEventLoop;
177 m_eventLoop->exec();
178 delete m_eventLoop;
179 m_eventLoop = nullptr;
180 m_drag = nullptr;
181 endDrag();
182
183 return m_executed_drop_action;
184}
185
186void QBasicDrag::cancelDrag()
187{
188 if (m_eventLoop) {
189 cancel();
190 m_eventLoop->quit();
191 }
192}
193
194void QBasicDrag::startDrag()
195{
196 QPoint pos;
197#ifndef QT_NO_CURSOR
198 pos = QCursor::pos();
199 static constexpr QGuiApplicationPrivate::QLastCursorPosition uninitializedCursorPosition;
200 if (pos == uninitializedCursorPosition) {
201 // ### fixme: no mouse pos registered. Get pos from touch...
202 pos = QPoint();
203 }
204#endif
205 m_lastPos = pos;
206 recreateShapedPixmapWindow(m_screen, pos);
207 enableEventFilter();
208}
209
210void QBasicDrag::endDrag()
211{
212}
213
214void QBasicDrag::recreateShapedPixmapWindow(QScreen *screen, const QPoint &pos)
215{
216 delete m_drag_icon_window;
217 // ### TODO Check if its really necessary to have m_drag_icon_window
218 // when QDrag is used without a pixmap - QDrag::setPixmap()
219 m_drag_icon_window = new QShapedPixmapWindow(screen);
220
221 m_drag_icon_window->setUseCompositing(m_useCompositing);
222 m_drag_icon_window->setPixmap(m_drag->pixmap());
223 m_drag_icon_window->setHotspot(m_drag->hotSpot());
224 m_drag_icon_window->updateGeometry(pos);
225 m_drag_icon_window->setVisible(true);
226}
227
228void QBasicDrag::cancel()
229{
230 disableEventFilter();
231 restoreCursor();
232 m_drag_icon_window->setVisible(false);
233}
234
235/*!
236 Move the drag label to \a globalPos, which is
237 interpreted in device independent coordinates. Typically called from reimplementations of move().
238 */
239
240void QBasicDrag::moveShapedPixmapWindow(const QPoint &globalPos)
241{
242 if (m_drag)
243 m_drag_icon_window->updateGeometry(globalPos);
244}
245
246void QBasicDrag::drop(const QPoint &, Qt::MouseButtons, Qt::KeyboardModifiers)
247{
248 disableEventFilter();
249 restoreCursor();
250 m_drag_icon_window->setVisible(false);
251}
252
253void QBasicDrag::exitDndEventLoop()
254{
255 if (m_eventLoop && m_eventLoop->isRunning())
256 m_eventLoop->exit();
257}
258
259void QBasicDrag::updateCursor(Qt::DropAction action)
260{
261#ifndef QT_NO_CURSOR
262 Qt::CursorShape cursorShape = Qt::ForbiddenCursor;
263 if (canDrop()) {
264 switch (action) {
265 case Qt::CopyAction:
266 cursorShape = Qt::DragCopyCursor;
267 break;
268 case Qt::LinkAction:
269 cursorShape = Qt::DragLinkCursor;
270 break;
271 default:
272 cursorShape = Qt::DragMoveCursor;
273 break;
274 }
275 }
276
277 QPixmap pixmap = m_drag->dragCursor(action);
278
279 if (!m_dndHasSetOverrideCursor) {
280 QCursor newCursor = !pixmap.isNull() ? QCursor(pixmap) : QCursor(cursorShape);
281 QGuiApplication::setOverrideCursor(newCursor);
282 m_dndHasSetOverrideCursor = true;
283 } else {
284 QCursor *cursor = QGuiApplication::overrideCursor();
285 if (!cursor) {
286 QGuiApplication::changeOverrideCursor(pixmap.isNull() ? QCursor(cursorShape) : QCursor(pixmap));
287 } else {
288 if (!pixmap.isNull()) {
289 if (cursor->pixmap().cacheKey() != pixmap.cacheKey())
290 QGuiApplication::changeOverrideCursor(QCursor(pixmap));
291 } else if (cursorShape != cursor->shape()) {
292 QGuiApplication::changeOverrideCursor(QCursor(cursorShape));
293 }
294 }
295 }
296#endif
297 updateAction(action);
298}
299
300void QBasicDrag::restoreCursor()
301{
302#ifndef QT_NO_CURSOR
303 if (m_dndHasSetOverrideCursor) {
304 QGuiApplication::restoreOverrideCursor();
305 m_dndHasSetOverrideCursor = false;
306 }
307#endif
308}
309
310static inline QPoint fromNativeGlobalPixels(const QPoint &point)
311{
312#ifndef QT_NO_HIGHDPISCALING
313 QPoint res = point;
314 if (QHighDpiScaling::isActive()) {
315 for (const QScreen *s : std::as_const(QGuiApplicationPrivate::screen_list)) {
316 if (s->handle()->geometry().contains(point)) {
317 res = QHighDpi::fromNativePixels(point, s);
318 break;
319 }
320 }
321 }
322 return res;
323#else
324 return point;
325#endif
326}
327
328/*!
329 \class QSimpleDrag
330 \brief QSimpleDrag implements QBasicDrag for Drag and Drop operations within the Qt Application itself.
331 \since 5.0
332 \internal
333 \ingroup qpa
334
335 The class checks whether the receiving window is a window of the Qt application
336 and sets the state accordingly. It does not take windows of other applications
337 into account.
338*/
339
340QSimpleDrag::QSimpleDrag()
341{
342}
343
344void QSimpleDrag::startDrag()
345{
346 setExecutedDropAction(Qt::IgnoreAction);
347
348 QBasicDrag::startDrag();
349 // Here we can be fairly sure that QGuiApplication::mouseButtons/keyboardModifiers() will
350 // contain sensible values as startDrag() normally is called from mouse event handlers
351 // by QDrag::exec(). A better API would be if we could pass something like "input device
352 // pointer" to QDrag::exec(). My guess is that something like that might be required for
353 // QTBUG-52430.
354 m_sourceWindow = topLevelAt(QCursor::pos());
355 m_windowUnderCursor = m_sourceWindow;
356 if (m_sourceWindow) {
357 auto nativePixelPos = QHighDpi::toNativePixels(QCursor::pos(), m_sourceWindow);
358 move(nativePixelPos, QGuiApplication::mouseButtons(), QGuiApplication::keyboardModifiers());
359 } else {
360 setCanDrop(false);
361 updateCursor(Qt::IgnoreAction);
362 }
363
364 qCDebug(lcDnd) << "drag began from" << m_sourceWindow << "cursor pos" << QCursor::pos() << "can drop?" << canDrop();
365}
366
367static void sendDragLeave(QWindow *window)
368{
369 QWindowSystemInterface::handleDrag(window, nullptr, QPoint(), Qt::IgnoreAction, { }, { });
370}
371
372void QSimpleDrag::cancel()
373{
374 QBasicDrag::cancel();
375 if (drag() && m_sourceWindow) {
376 sendDragLeave(m_sourceWindow);
377 m_sourceWindow = nullptr;
378 }
379}
380
381void QSimpleDrag::move(const QPoint &nativeGlobalPos, Qt::MouseButtons buttons,
382 Qt::KeyboardModifiers modifiers)
383{
384 QPoint globalPos = fromNativeGlobalPixels(nativeGlobalPos);
385 moveShapedPixmapWindow(globalPos);
386 QWindow *window = topLevelAt(globalPos);
387
388 if (!window || window != m_windowUnderCursor) {
389 if (m_windowUnderCursor)
390 sendDragLeave(m_windowUnderCursor);
391 m_windowUnderCursor = window;
392 if (!window) {
393 // QSimpleDrag supports only in-process dnd, we can't drop anywhere else.
394 setCanDrop(false);
395 updateCursor(Qt::IgnoreAction);
396 return;
397 }
398 }
399
400 const QPoint pos = nativeGlobalPos - window->handle()->geometry().topLeft();
401 const QPlatformDragQtResponse qt_response = QWindowSystemInterface::handleDrag(
402 window, drag()->mimeData(), pos, drag()->supportedActions(),
403 buttons, modifiers);
404
405 setCanDrop(qt_response.isAccepted());
406 updateCursor(qt_response.acceptedAction());
407}
408
409void QSimpleDrag::drop(const QPoint &nativeGlobalPos, Qt::MouseButtons buttons,
410 Qt::KeyboardModifiers modifiers)
411{
412 QPoint globalPos = fromNativeGlobalPixels(nativeGlobalPos);
413
414 QBasicDrag::drop(nativeGlobalPos, buttons, modifiers);
415 QWindow *window = topLevelAt(globalPos);
416 if (!window)
417 return;
418
419 const QPoint pos = nativeGlobalPos - window->handle()->geometry().topLeft();
420 const QPlatformDropQtResponse response = QWindowSystemInterface::handleDrop(
421 window, drag()->mimeData(), pos, drag()->supportedActions(),
422 buttons, modifiers);
423 if (response.isAccepted()) {
424 setExecutedDropAction(response.acceptedAction());
425 } else {
426 setExecutedDropAction(Qt::IgnoreAction);
427 }
428}
429
430QT_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)