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 | |
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(p: pos) && !qobject_cast<QShapedPixmapWindow*>(object: 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 | |
95 | QBasicDrag::QBasicDrag() |
96 | { |
97 | } |
98 | |
99 | QBasicDrag::~QBasicDrag() |
100 | { |
101 | delete m_drag_icon_window; |
102 | } |
103 | |
104 | void QBasicDrag::enableEventFilter() |
105 | { |
106 | qApp->installEventFilter(filterObj: this); |
107 | } |
108 | |
109 | void QBasicDrag::disableEventFilter() |
110 | { |
111 | qApp->removeEventFilter(obj: this); |
112 | } |
113 | |
114 | |
115 | static inline QPoint getNativeMousePos(QEvent *e, QWindow *window) |
116 | { |
117 | return QHighDpi::toNativePixels(value: static_cast<QMouseEvent *>(e)->globalPos(), context: window); |
118 | } |
119 | |
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(globalPos: m_lastPos, b: QGuiApplication::mouseButtons(), mods: ke->modifiers()); |
150 | } |
151 | return true; // Eat all key events |
152 | } |
153 | |
154 | case QEvent::MouseMove: |
155 | { |
156 | m_lastPos = getNativeMousePos(e, window: m_drag_icon_window); |
157 | auto mouseMove = static_cast<QMouseEvent *>(e); |
158 | move(globalPos: m_lastPos, b: mouseMove->buttons(), mods: 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, window: m_drag_icon_window); |
166 | auto mouseRelease = static_cast<QMouseEvent *>(e); |
167 | drop(globalPos: nativePosition, b: mouseRelease->buttons(), mods: 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(pos: 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(pos: 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(receiver: o, event: 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 | |
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 | |
217 | void QBasicDrag::cancelDrag() |
218 | { |
219 | if (m_eventLoop) { |
220 | cancel(); |
221 | m_eventLoop->quit(); |
222 | } |
223 | } |
224 | |
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(screen: m_screen, pos); |
237 | enableEventFilter(); |
238 | } |
239 | |
240 | void QBasicDrag::endDrag() |
241 | { |
242 | } |
243 | |
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 | |
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 | |
270 | void QBasicDrag::moveShapedPixmapWindow(const QPoint &globalPos) |
271 | { |
272 | if (m_drag) |
273 | m_drag_icon_window->updateGeometry(pos: globalPos); |
274 | } |
275 | |
276 | void QBasicDrag::drop(const QPoint &, Qt::MouseButtons, Qt::KeyboardModifiers) |
277 | { |
278 | disableEventFilter(); |
279 | restoreCursor(); |
280 | m_drag_icon_window->setVisible(false); |
281 | } |
282 | |
283 | void QBasicDrag::exitDndEventLoop() |
284 | { |
285 | if (m_eventLoop && m_eventLoop->isRunning()) |
286 | m_eventLoop->exit(); |
287 | } |
288 | |
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 | |
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 | |
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(t&: QGuiApplicationPrivate::screen_list)) { |
346 | if (s->handle()->geometry().contains(p: point)) { |
347 | res = QHighDpi::fromNativePixels(value: point, context: 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 | |
370 | QSimpleDrag::QSimpleDrag() |
371 | { |
372 | } |
373 | |
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(pos: QCursor::pos()); |
385 | m_windowUnderCursor = m_sourceWindow; |
386 | if (m_sourceWindow) { |
387 | auto nativePixelPos = QHighDpi::toNativePixels(value: QCursor::pos(), context: m_sourceWindow); |
388 | move(globalPos: nativePixelPos, b: QGuiApplication::mouseButtons(), mods: QGuiApplication::keyboardModifiers()); |
389 | } else { |
390 | setCanDrop(false); |
391 | updateCursor(action: Qt::IgnoreAction); |
392 | } |
393 | |
394 | qCDebug(lcDnd) << "drag began from" << m_sourceWindow << "cursor pos" << QCursor::pos() << "can drop?" << canDrop(); |
395 | } |
396 | |
397 | static void sendDragLeave(QWindow *window) |
398 | { |
399 | QWindowSystemInterface::handleDrag(window, dropData: nullptr, p: QPoint(), supportedActions: Qt::IgnoreAction, buttons: { }, modifiers: { }); |
400 | } |
401 | |
402 | void QSimpleDrag::cancel() |
403 | { |
404 | QBasicDrag::cancel(); |
405 | if (drag() && m_sourceWindow) { |
406 | sendDragLeave(window: m_sourceWindow); |
407 | m_sourceWindow = nullptr; |
408 | } |
409 | } |
410 | |
411 | void QSimpleDrag::move(const QPoint &nativeGlobalPos, Qt::MouseButtons buttons, |
412 | Qt::KeyboardModifiers modifiers) |
413 | { |
414 | QPoint globalPos = fromNativeGlobalPixels(point: nativeGlobalPos); |
415 | moveShapedPixmapWindow(globalPos); |
416 | QWindow *window = topLevelAt(pos: globalPos); |
417 | |
418 | if (!window || window != m_windowUnderCursor) { |
419 | if (m_windowUnderCursor) |
420 | sendDragLeave(window: 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(action: 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, dropData: drag()->mimeData(), p: pos, supportedActions: drag()->supportedActions(), |
433 | buttons, modifiers); |
434 | |
435 | setCanDrop(qt_response.isAccepted()); |
436 | updateCursor(action: qt_response.acceptedAction()); |
437 | } |
438 | |
439 | void QSimpleDrag::drop(const QPoint &nativeGlobalPos, Qt::MouseButtons buttons, |
440 | Qt::KeyboardModifiers modifiers) |
441 | { |
442 | QPoint globalPos = fromNativeGlobalPixels(point: nativeGlobalPos); |
443 | |
444 | QBasicDrag::drop(nativeGlobalPos, buttons, modifiers); |
445 | QWindow *window = topLevelAt(pos: globalPos); |
446 | if (!window) |
447 | return; |
448 | |
449 | const QPoint pos = nativeGlobalPos - window->handle()->geometry().topLeft(); |
450 | const QPlatformDropQtResponse response = QWindowSystemInterface::handleDrop( |
451 | window, dropData: drag()->mimeData(), p: pos, supportedActions: 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 | |