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