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 
67 QT_BEGIN_NAMESPACE
68 
69 Q_LOGGING_CATEGORY(lcDnd, "qt.gui.dnd")
70 
topLevelAt(const QPoint & pos)71 static 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 
QBasicDrag()95 QBasicDrag::QBasicDrag()
96 {
97 }
98 
~QBasicDrag()99 QBasicDrag::~QBasicDrag()
100 {
101     delete m_drag_icon_window;
102 }
103 
enableEventFilter()104 void QBasicDrag::enableEventFilter()
105 {
106     qApp->installEventFilter(this);
107 }
108 
disableEventFilter()109 void QBasicDrag::disableEventFilter()
110 {
111     qApp->removeEventFilter(this);
112 }
113 
114 
getNativeMousePos(QEvent * e,QWindow * window)115 static inline QPoint getNativeMousePos(QEvent *e, QWindow *window)
116 {
117     return QHighDpi::toNativePixels(static_cast<QMouseEvent *>(e)->globalPos(), window);
118 }
119 
eventFilter(QObject * o,QEvent * e)120 bool 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->globalPos());
180             qCDebug(lcDnd) << "mouse released over" << releaseWindow << "after drag from" << m_sourceWindow << "globalPos" << release->globalPos();
181             if (!releaseWindow)
182                 releaseWindow = m_sourceWindow;
183             QPoint releaseWindowPos = (releaseWindow ? releaseWindow->mapFromGlobal(release->globalPos()) : release->globalPos());
184             QMouseEvent *newRelease = new QMouseEvent(release->type(),
185                 releaseWindowPos, releaseWindowPos, release->screenPos(),
186                 release->button(), release->buttons(),
187                 release->modifiers(), release->source());
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 
drag(QDrag * o)200 Qt::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 
cancelDrag()217 void QBasicDrag::cancelDrag()
218 {
219     if (m_eventLoop) {
220         cancel();
221         m_eventLoop->quit();
222     }
223 }
224 
startDrag()225 void 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 
endDrag()240 void QBasicDrag::endDrag()
241 {
242 }
243 
recreateShapedPixmapWindow(QScreen * screen,const QPoint & pos)244 void 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 
cancel()258 void 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 
moveShapedPixmapWindow(const QPoint & globalPos)270 void QBasicDrag::moveShapedPixmapWindow(const QPoint &globalPos)
271 {
272     if (m_drag)
273         m_drag_icon_window->updateGeometry(globalPos);
274 }
275 
drop(const QPoint &,Qt::MouseButtons,Qt::KeyboardModifiers)276 void QBasicDrag::drop(const QPoint &, Qt::MouseButtons, Qt::KeyboardModifiers)
277 {
278     disableEventFilter();
279     restoreCursor();
280     m_drag_icon_window->setVisible(false);
281 }
282 
exitDndEventLoop()283 void  QBasicDrag::exitDndEventLoop()
284 {
285     if (m_eventLoop && m_eventLoop->isRunning())
286         m_eventLoop->exit();
287 }
288 
updateCursor(Qt::DropAction action)289 void 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 
restoreCursor()330 void QBasicDrag::restoreCursor()
331 {
332 #ifndef QT_NO_CURSOR
333     if (m_dndHasSetOverrideCursor) {
334         QGuiApplication::restoreOverrideCursor();
335         m_dndHasSetOverrideCursor = false;
336     }
337 #endif
338 }
339 
fromNativeGlobalPixels(const QPoint & point)340 static 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 
QSimpleDrag()370 QSimpleDrag::QSimpleDrag()
371 {
372 }
373 
startDrag()374 void 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 
sendDragLeave(QWindow * window)397 static void sendDragLeave(QWindow *window)
398 {
399     QWindowSystemInterface::handleDrag(window, nullptr, QPoint(), Qt::IgnoreAction, { }, { });
400 }
401 
cancel()402 void QSimpleDrag::cancel()
403 {
404     QBasicDrag::cancel();
405     if (drag() && m_sourceWindow) {
406         sendDragLeave(m_sourceWindow);
407         m_sourceWindow = nullptr;
408     }
409 }
410 
move(const QPoint & nativeGlobalPos,Qt::MouseButtons buttons,Qt::KeyboardModifiers modifiers)411 void 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 
drop(const QPoint & nativeGlobalPos,Qt::MouseButtons buttons,Qt::KeyboardModifiers modifiers)439 void 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 
460 QT_END_NAMESPACE
461