diff options
-rw-r--r-- | src/gui/kernel/qguiapplication.cpp | 9 | ||||
-rw-r--r-- | src/gui/kernel/qguiapplication_p.h | 1 | ||||
-rw-r--r-- | src/gui/kernel/qplatformintegration.cpp | 4 | ||||
-rw-r--r-- | src/gui/kernel/qsimpledrag.cpp | 105 | ||||
-rw-r--r-- | src/gui/kernel/qsimpledrag_p.h | 24 | ||||
-rw-r--r-- | src/plugins/platforms/xcb/qxcbdrag.cpp | 3 | ||||
-rw-r--r-- | src/plugins/platforms/xcb/qxcbintegration.cpp | 9 | ||||
-rw-r--r-- | tests/auto/widgets/kernel/qwidget_window/tst_qwidget_window.cpp | 87 |
8 files changed, 177 insertions, 65 deletions
diff --git a/src/gui/kernel/qguiapplication.cpp b/src/gui/kernel/qguiapplication.cpp index 1ebdde3426..10defabd4f 100644 --- a/src/gui/kernel/qguiapplication.cpp +++ b/src/gui/kernel/qguiapplication.cpp @@ -143,6 +143,8 @@ Qt::ApplicationState QGuiApplicationPrivate::applicationState = Qt::ApplicationI bool QGuiApplicationPrivate::highDpiScalingUpdated = false; +QPointer<QWindow> QGuiApplicationPrivate::currentDragWindow; + QVector<QGuiApplicationPrivate::TabletPointData> QGuiApplicationPrivate::tabletDevicePoints; QPlatformIntegration *QGuiApplicationPrivate::platform_integration = 0; @@ -668,6 +670,7 @@ QGuiApplication::~QGuiApplication() QGuiApplicationPrivate::currentMousePressWindow = QGuiApplicationPrivate::currentMouseWindow = nullptr; QGuiApplicationPrivate::applicationState = Qt::ApplicationInactive; QGuiApplicationPrivate::highDpiScalingUpdated = false; + QGuiApplicationPrivate::currentDragWindow = nullptr; QGuiApplicationPrivate::tabletDevicePoints.clear(); #ifndef QT_NO_SESSIONMANAGER QGuiApplicationPrivate::is_fallback_session_management_enabled = true; @@ -3092,7 +3095,6 @@ QPlatformDragQtResponse QGuiApplicationPrivate::processDrag(QWindow *w, const QM { updateMouseAndModifierButtonState(buttons, modifiers); - static QPointer<QWindow> currentDragWindow; static Qt::DropAction lastAcceptedDropAction = Qt::IgnoreAction; QPlatformDrag *platformDrag = platformIntegration()->drag(); if (!platformDrag || (w && w->d_func()->blockedByModalWindow)) { @@ -3101,8 +3103,7 @@ QPlatformDragQtResponse QGuiApplicationPrivate::processDrag(QWindow *w, const QM } if (!dropData) { - if (currentDragWindow.data() == w) - currentDragWindow = 0; + currentDragWindow = nullptr; QDragLeaveEvent e; QGuiApplication::sendEvent(w, &e); lastAcceptedDropAction = Qt::IgnoreAction; @@ -3141,6 +3142,8 @@ QPlatformDropQtResponse QGuiApplicationPrivate::processDrop(QWindow *w, const QM { updateMouseAndModifierButtonState(buttons, modifiers); + currentDragWindow = nullptr; + QDropEvent de(p, supportedActions, dropData, buttons, modifiers); QGuiApplication::sendEvent(w, &de); diff --git a/src/gui/kernel/qguiapplication_p.h b/src/gui/kernel/qguiapplication_p.h index b7ee9f86b5..145e764244 100644 --- a/src/gui/kernel/qguiapplication_p.h +++ b/src/gui/kernel/qguiapplication_p.h @@ -217,6 +217,7 @@ public: static QWindow *currentMousePressWindow; static Qt::ApplicationState applicationState; static bool highDpiScalingUpdated; + static QPointer<QWindow> currentDragWindow; struct TabletPointData { TabletPointData(qint64 devId = 0) : deviceId(devId), state(Qt::NoButton), target(nullptr) {} diff --git a/src/gui/kernel/qplatformintegration.cpp b/src/gui/kernel/qplatformintegration.cpp index dfb8f60915..6e285a8fa5 100644 --- a/src/gui/kernel/qplatformintegration.cpp +++ b/src/gui/kernel/qplatformintegration.cpp @@ -99,8 +99,8 @@ QPlatformClipboard *QPlatformIntegration::clipboard() const /*! Accessor for the platform integration's drag object. - Default implementation returns 0, implying no drag and drop support. - + Default implementation returns QSimpleDrag. This class supports only drag + and drop operations within the same Qt application. */ QPlatformDrag *QPlatformIntegration::drag() const { diff --git a/src/gui/kernel/qsimpledrag.cpp b/src/gui/kernel/qsimpledrag.cpp index 35896514b0..a84f873437 100644 --- a/src/gui/kernel/qsimpledrag.cpp +++ b/src/gui/kernel/qsimpledrag.cpp @@ -94,11 +94,7 @@ static QWindow* topLevelAt(const QPoint &pos) (within the Qt application or outside) accepts the drag and sets the state accordingly. */ -QBasicDrag::QBasicDrag() : - m_current_window(nullptr), m_restoreCursor(false), m_eventLoop(nullptr), - m_executed_drop_action(Qt::IgnoreAction), m_can_drop(false), - m_drag(nullptr), m_drag_icon_window(nullptr), m_useCompositing(true), - m_screen(nullptr) +QBasicDrag::QBasicDrag() { } @@ -181,9 +177,9 @@ bool QBasicDrag::eventFilter(QObject *o, QEvent *e) // make the event relative to the window where the drag started. (QTBUG-66103) const QMouseEvent *release = static_cast<QMouseEvent *>(e); const QWindow *releaseWindow = topLevelAt(release->globalPos()); - qCDebug(lcDnd) << "mouse released over" << releaseWindow << "after drag from" << m_current_window << "globalPos" << release->globalPos(); + qCDebug(lcDnd) << "mouse released over" << releaseWindow << "after drag from" << m_sourceWindow << "globalPos" << release->globalPos(); if (!releaseWindow) - releaseWindow = m_current_window; + releaseWindow = m_sourceWindow; QPoint releaseWindowPos = (releaseWindow ? releaseWindow->mapFromGlobal(release->globalPos()) : release->globalPos()); QMouseEvent *newRelease = new QMouseEvent(release->type(), releaseWindowPos, releaseWindowPos, release->screenPos(), @@ -206,18 +202,15 @@ Qt::DropAction QBasicDrag::drag(QDrag *o) m_drag = o; m_executed_drop_action = Qt::IgnoreAction; m_can_drop = false; - m_restoreCursor = true; -#ifndef QT_NO_CURSOR - qApp->setOverrideCursor(Qt::DragCopyCursor); - updateCursor(m_executed_drop_action); -#endif + startDrag(); m_eventLoop = new QEventLoop; m_eventLoop->exec(); delete m_eventLoop; - m_eventLoop = 0; - m_drag = 0; + m_eventLoop = nullptr; + m_drag = nullptr; endDrag(); + return m_executed_drop_action; } @@ -229,16 +222,6 @@ void QBasicDrag::cancelDrag() } } -void QBasicDrag::restoreCursor() -{ - if (m_restoreCursor) { -#ifndef QT_NO_CURSOR - QGuiApplication::restoreOverrideCursor(); -#endif - m_restoreCursor = false; - } -} - void QBasicDrag::startDrag() { QPoint pos; @@ -320,25 +303,34 @@ void QBasicDrag::updateCursor(Qt::DropAction action) } } - QCursor *cursor = QGuiApplication::overrideCursor(); QPixmap pixmap = m_drag->dragCursor(action); - if (!cursor) { - QGuiApplication::changeOverrideCursor((pixmap.isNull()) ? QCursor(cursorShape) : QCursor(pixmap)); + + if (!m_dndHasSetOverrideCursor) { + QCursor newCursor = !pixmap.isNull() ? QCursor(pixmap) : QCursor(cursorShape); + QGuiApplication::setOverrideCursor(newCursor); + m_dndHasSetOverrideCursor = true; } else { + QCursor *cursor = QGuiApplication::overrideCursor(); if (!pixmap.isNull()) { - if ((cursor->pixmap().cacheKey() != pixmap.cacheKey())) { + if (cursor->pixmap().cacheKey() != pixmap.cacheKey()) QGuiApplication::changeOverrideCursor(QCursor(pixmap)); - } - } else { - if (cursorShape != cursor->shape()) { - QGuiApplication::changeOverrideCursor(QCursor(cursorShape)); - } + } else if (cursorShape != cursor->shape()) { + QGuiApplication::changeOverrideCursor(QCursor(cursorShape)); } } #endif updateAction(action); } +void QBasicDrag::restoreCursor() +{ +#ifndef QT_NO_CURSOR + if (m_dndHasSetOverrideCursor) { + QGuiApplication::restoreOverrideCursor(); + m_dndHasSetOverrideCursor = false; + } +#endif +} static inline QPoint fromNativeGlobalPixels(const QPoint &point) { @@ -376,35 +368,38 @@ QSimpleDrag::QSimpleDrag() void QSimpleDrag::startDrag() { + setExecutedDropAction(Qt::IgnoreAction); + QBasicDrag::startDrag(); // Here we can be fairly sure that QGuiApplication::mouseButtons/keyboardModifiers() will // contain sensible values as startDrag() normally is called from mouse event handlers // by QDrag::exec(). A better API would be if we could pass something like "input device // pointer" to QDrag::exec(). My guess is that something like that might be required for // QTBUG-52430. - m_current_window = topLevelAt(QCursor::pos()); - if (m_current_window) { - auto nativePixelPos = QHighDpi::toNativePixels(QCursor::pos(), m_current_window); - QPlatformDragQtResponse response = QWindowSystemInterface::handleDrag( - m_current_window, drag()->mimeData(), nativePixelPos, - drag()->supportedActions(), QGuiApplication::mouseButtons(), - QGuiApplication::keyboardModifiers()); - setCanDrop(response.isAccepted()); - updateCursor(response.acceptedAction()); + m_sourceWindow = topLevelAt(QCursor::pos()); + m_windowUnderCursor = m_sourceWindow; + if (m_sourceWindow) { + auto nativePixelPos = QHighDpi::toNativePixels(QCursor::pos(), m_sourceWindow); + move(nativePixelPos, QGuiApplication::mouseButtons(), QGuiApplication::keyboardModifiers()); } else { setCanDrop(false); updateCursor(Qt::IgnoreAction); } - setExecutedDropAction(Qt::IgnoreAction); - qCDebug(lcDnd) << "drag began from" << m_current_window<< "cursor pos" << QCursor::pos() << "can drop?" << canDrop(); + + qCDebug(lcDnd) << "drag began from" << m_sourceWindow << "cursor pos" << QCursor::pos() << "can drop?" << canDrop(); +} + +static void sendDragLeave(QWindow *window) +{ + QWindowSystemInterface::handleDrag(window, nullptr, QPoint(), Qt::IgnoreAction, 0, 0); } void QSimpleDrag::cancel() { QBasicDrag::cancel(); - if (drag() && m_current_window) { - QWindowSystemInterface::handleDrag(m_current_window, nullptr, QPoint(), Qt::IgnoreAction, 0, 0); - m_current_window = nullptr; + if (drag() && m_sourceWindow) { + sendDragLeave(m_sourceWindow); + m_sourceWindow = nullptr; } } @@ -414,16 +409,26 @@ void QSimpleDrag::move(const QPoint &nativeGlobalPos, Qt::MouseButtons buttons, QPoint globalPos = fromNativeGlobalPixels(nativeGlobalPos); moveShapedPixmapWindow(globalPos); QWindow *window = topLevelAt(globalPos); - if (!window) - return; + + if (!window || window != m_windowUnderCursor) { + if (m_windowUnderCursor) + sendDragLeave(m_windowUnderCursor); + m_windowUnderCursor = window; + if (!window) { + // QSimpleDrag supports only in-process dnd, we can't drop anywhere else. + setCanDrop(false); + updateCursor(Qt::IgnoreAction); + return; + } + } const QPoint pos = nativeGlobalPos - window->handle()->geometry().topLeft(); const QPlatformDragQtResponse qt_response = QWindowSystemInterface::handleDrag( window, drag()->mimeData(), pos, drag()->supportedActions(), buttons, modifiers); - updateCursor(qt_response.acceptedAction()); setCanDrop(qt_response.isAccepted()); + updateCursor(qt_response.acceptedAction()); } void QSimpleDrag::drop(const QPoint &nativeGlobalPos, Qt::MouseButtons buttons, diff --git a/src/gui/kernel/qsimpledrag_p.h b/src/gui/kernel/qsimpledrag_p.h index ddd32bc5a5..f9e8a83a39 100644 --- a/src/gui/kernel/qsimpledrag_p.h +++ b/src/gui/kernel/qsimpledrag_p.h @@ -55,13 +55,14 @@ #include <qpa/qplatformdrag.h> #include <QtCore/QObject> +#include <QtCore/QPointer> +#include <QtGui/QWindow> QT_REQUIRE_CONFIG(draganddrop); QT_BEGIN_NAMESPACE class QMouseEvent; -class QWindow; class QEventLoop; class QDropData; class QShapedPixmapWindow; @@ -106,7 +107,8 @@ protected: QDrag *drag() const { return m_drag; } protected: - QWindow *m_current_window; + QWindow *m_sourceWindow = nullptr; + QPointer<QWindow> m_windowUnderCursor = nullptr; private: void enableEventFilter(); @@ -114,14 +116,16 @@ private: void restoreCursor(); void exitDndEventLoop(); - bool m_restoreCursor; - QEventLoop *m_eventLoop; - Qt::DropAction m_executed_drop_action; - bool m_can_drop; - QDrag *m_drag; - QShapedPixmapWindow *m_drag_icon_window; - bool m_useCompositing; - QScreen *m_screen; +#ifndef QT_NO_CURSOR + bool m_dndHasSetOverrideCursor = false; +#endif + QEventLoop *m_eventLoop = nullptr; + Qt::DropAction m_executed_drop_action = Qt::IgnoreAction; + bool m_can_drop = false; + QDrag *m_drag = nullptr; + QShapedPixmapWindow *m_drag_icon_window = nullptr; + bool m_useCompositing = true; + QScreen *m_screen = nullptr; }; class Q_GUI_EXPORT QSimpleDrag : public QBasicDrag diff --git a/src/plugins/platforms/xcb/qxcbdrag.cpp b/src/plugins/platforms/xcb/qxcbdrag.cpp index 999ae16897..20b01225cf 100644 --- a/src/plugins/platforms/xcb/qxcbdrag.cpp +++ b/src/plugins/platforms/xcb/qxcbdrag.cpp @@ -201,6 +201,9 @@ void QXcbDrag::startDrag() QBasicDrag::startDrag(); if (connection()->mouseGrabber() == nullptr) shapedPixmapWindow()->setMouseGrabEnabled(true); + + auto nativePixelPos = QHighDpi::toNativePixels(QCursor::pos(), initiatorWindow); + move(nativePixelPos, QGuiApplication::mouseButtons(), QGuiApplication::keyboardModifiers()); } void QXcbDrag::endDrag() diff --git a/src/plugins/platforms/xcb/qxcbintegration.cpp b/src/plugins/platforms/xcb/qxcbintegration.cpp index b8a0e85465..49649eb816 100644 --- a/src/plugins/platforms/xcb/qxcbintegration.cpp +++ b/src/plugins/platforms/xcb/qxcbintegration.cpp @@ -379,8 +379,17 @@ QPlatformClipboard *QXcbIntegration::clipboard() const #endif #if QT_CONFIG(draganddrop) +#include <private/qsimpledrag_p.h> QPlatformDrag *QXcbIntegration::drag() const { + static const bool useSimpleDrag = qEnvironmentVariableIsSet("QT_XCB_USE_SIMPLE_DRAG"); + if (Q_UNLIKELY(useSimpleDrag)) { // This is useful for testing purposes + static QSimpleDrag *simpleDrag = nullptr; + if (!simpleDrag) + simpleDrag = new QSimpleDrag(); + return simpleDrag; + } + return m_connections.at(0)->drag(); } #endif diff --git a/tests/auto/widgets/kernel/qwidget_window/tst_qwidget_window.cpp b/tests/auto/widgets/kernel/qwidget_window/tst_qwidget_window.cpp index 0813a13693..f0b4bd6dd9 100644 --- a/tests/auto/widgets/kernel/qwidget_window/tst_qwidget_window.cpp +++ b/tests/auto/widgets/kernel/qwidget_window/tst_qwidget_window.cpp @@ -83,6 +83,7 @@ private slots: #if QT_CONFIG(draganddrop) void tst_dnd(); + void tst_dnd_events(); #endif void tst_qtbug35600(); @@ -597,6 +598,92 @@ void tst_QWidget_window::tst_dnd() QCOMPARE(log, expectedLog); } + +class DnDEventRecorder : public QWidget +{ + Q_OBJECT +public: + QString _dndEvents; + DnDEventRecorder() { setAcceptDrops(true); } + +protected: + void mousePressEvent(QMouseEvent *) + { + QMimeData *mimeData = new QMimeData; + mimeData->setData("application/x-dnditemdata", "some data"); + QDrag *drag = new QDrag(this); + drag->setMimeData(mimeData); + drag->exec(); + } + + void dragEnterEvent(QDragEnterEvent *e) + { + e->accept(); + _dndEvents.append(QStringLiteral("DragEnter ")); + } + void dragMoveEvent(QDragMoveEvent *e) + { + e->accept(); + _dndEvents.append(QStringLiteral("DragMove ")); + emit releaseMouseButton(); + } + void dragLeaveEvent(QDragLeaveEvent *e) + { + e->accept(); + _dndEvents.append(QStringLiteral("DragLeave ")); + } + void dropEvent(QDropEvent *e) + { + e->accept(); + _dndEvents.append(QStringLiteral("DropEvent ")); + } + +signals: + void releaseMouseButton(); +}; + +void tst_QWidget_window::tst_dnd_events() +{ + // Note: This test is somewhat a hack as testing DnD with qtestlib is not + // supported at the moment. The test verifies that we get an expected event + // sequence on dnd operation that does not move a mouse. This logic is implemented + // in QGuiApplication, so we have to go via QWindowSystemInterface API (QTest::mouse*). + const auto platformName = QGuiApplication::platformName().toLower(); + // The test is known to work with XCB and platforms that use the default dnd + // implementation QSimpleDrag (e.g. qnx). Running on XCB should be sufficient to + // catch regressions at cross platform code: QGuiApplication::processDrag/Leave(). + if (platformName != "xcb") + return; + + const QString expectedDndEvents = "DragEnter DragMove DropEvent DragEnter DragMove " + "DropEvent DragEnter DragMove DropEvent "; + DnDEventRecorder dndWidget; + dndWidget.setGeometry(100, 100, 200, 200); + dndWidget.show(); + QVERIFY(QTest::qWaitForWindowExposed(&dndWidget)); + QVERIFY(QTest::qWaitForWindowActive(&dndWidget)); + + // ### FIXME - QTBUG-35117 ??? + auto targetCenter = QPoint(dndWidget.width(), dndWidget.height()) / 2; + auto targetCenterGlobal = dndWidget.mapToGlobal(targetCenter); + QCursor::setPos(targetCenterGlobal); + QVERIFY(QTest::qWaitFor([&]() { return QCursor::pos() == targetCenterGlobal; })); + QCoreApplication::processEvents(); // clear mouse events generated from cursor + + auto window = dndWidget.window()->windowHandle(); + + // Some dnd implementation rely on running internal event loops, so we have to use + // the following queued signal hack to simulate mouse clicks in the widget. + QObject::connect(&dndWidget, &DnDEventRecorder::releaseMouseButton, this, [=]() { + QTest::mouseRelease(window, Qt::LeftButton); + }, Qt::QueuedConnection); + + QTest::mousePress(window, Qt::LeftButton); + QTest::mousePress(window, Qt::LeftButton); + QTest::mousePress(window, Qt::LeftButton); + + QCOMPARE(dndWidget._dndEvents, expectedDndEvents); +} #endif void tst_QWidget_window::tst_qtbug35600() |