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