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