From 1e34d3525f175340f03bad83600306c23703a92b Mon Sep 17 00:00:00 2001 From: Friedemann Kleint Date: Wed, 29 Aug 2012 16:06:05 +0200 Subject: Fix updating of drag icons. Try to find a target widget that accepts drops; ignore the event if none can be found. Split the handleDrag*() functions to reduce indentation. Add an autotest. Task-number: QTBUG-22987 Change-Id: I516ac5f0c002caaf83c52ac16f821246e565230f Reviewed-by: Lars Knoll --- src/widgets/kernel/qwidgetwindow.cpp | 115 +++++++------ src/widgets/kernel/qwidgetwindow_qpa_p.h | 4 +- .../kernel/qwidget_window/tst_qwidget_window.cpp | 186 ++++++++++++++++++++- 3 files changed, 251 insertions(+), 54 deletions(-) diff --git a/src/widgets/kernel/qwidgetwindow.cpp b/src/widgets/kernel/qwidgetwindow.cpp index 66313424a0..81a341c78c 100644 --- a/src/widgets/kernel/qwidgetwindow.cpp +++ b/src/widgets/kernel/qwidgetwindow.cpp @@ -165,10 +165,14 @@ bool QWidgetWindow::event(QEvent *event) #ifndef QT_NO_DRAGANDDROP case QEvent::DragEnter: - case QEvent::DragLeave: case QEvent::DragMove: + handleDragEnterMoveEvent(static_cast(event)); + return true; + case QEvent::DragLeave: + handleDragLeaveEvent(static_cast(event)); + return true; case QEvent::Drop: - handleDragEvent(event); + handleDropEvent(static_cast(event)); return true; #endif @@ -426,62 +430,69 @@ void QWidgetWindow::handleWheelEvent(QWheelEvent *event) #ifndef QT_NO_DRAGANDDROP -void QWidgetWindow::handleDragEvent(QEvent *event) +void QWidgetWindow::handleDragEnterMoveEvent(QDragMoveEvent *event) { - switch (event->type()) { - case QEvent::DragEnter: - Q_ASSERT(!m_dragTarget); - // fall through - case QEvent::DragMove: - { - QDragMoveEvent *de = static_cast(event); - QWidget *widget = m_widget->childAt(de->pos()); - if (!widget) - widget = m_widget; - - if (widget != m_dragTarget.data()) { - if (m_dragTarget.data()) { - QDragLeaveEvent le; - QGuiApplication::sendSpontaneousEvent(m_dragTarget.data(), &le); - } - m_dragTarget = widget; - QPoint mapped = widget->mapFrom(m_widget, de->pos()); - QDragEnterEvent translated(mapped, de->possibleActions(), de->mimeData(), de->mouseButtons(), de->keyboardModifiers()); - QGuiApplication::sendSpontaneousEvent(widget, &translated); - if (translated.isAccepted()) - event->accept(); - de->setDropAction(translated.dropAction()); + Q_ASSERT(event->type() ==QEvent::DragMove || !m_dragTarget); + // Find a target widget under mouse that accepts drops (QTBUG-22987). + QWidget *widget = m_widget->childAt(event->pos()); + if (!widget) + widget = m_widget; + for ( ; widget && widget != m_widget && !widget->acceptDrops(); widget = widget->parentWidget()) ; + if (widget && !widget->acceptDrops()) + widget = 0; + // Target widget unchanged: DragMove + if (widget && widget == m_dragTarget.data()) { + Q_ASSERT(event->type() == QEvent::DragMove); + const QPoint mapped = widget->mapFrom(m_widget, event->pos()); + QDragMoveEvent translated(mapped, event->possibleActions(), event->mimeData(), event->mouseButtons(), event->keyboardModifiers()); + translated.setDropAction(event->dropAction()); + QGuiApplication::sendSpontaneousEvent(widget, &translated); + if (translated.isAccepted()) { + event->accept(); } else { - Q_ASSERT(event->type() == QEvent::DragMove); - QPoint mapped = widget->mapFrom(m_widget, de->pos()); - QDragMoveEvent translated(mapped, de->possibleActions(), de->mimeData(), de->mouseButtons(), de->keyboardModifiers()); - translated.setDropAction(de->dropAction()); - QGuiApplication::sendSpontaneousEvent(widget, &translated); - if (translated.isAccepted()) - event->accept(); - de->setDropAction(translated.dropAction()); + event->ignore(); } - break; + event->setDropAction(translated.dropAction()); + return; } - case QEvent::DragLeave: - if (m_dragTarget) - QGuiApplication::sendSpontaneousEvent(m_dragTarget.data(), event); - m_dragTarget = (QWidget *)0; - break; - case QEvent::Drop: - { - QDropEvent *de = static_cast(event); - QPoint mapped = m_dragTarget.data()->mapFrom(m_widget, de->pos()); - QDropEvent translated(mapped, de->possibleActions(), de->mimeData(), de->mouseButtons(), de->keyboardModifiers()); - QGuiApplication::sendSpontaneousEvent(m_dragTarget.data(), &translated); - if (translated.isAccepted()) - event->accept(); - de->setDropAction(translated.dropAction()); - m_dragTarget = (QWidget *)0; + // Target widget changed: Send DragLeave to previous, DragEnter to new if there is any + if (m_dragTarget.data()) { + QDragLeaveEvent le; + QGuiApplication::sendSpontaneousEvent(m_dragTarget.data(), &le); + m_dragTarget = 0; } - default: - break; + if (!widget) { + event->ignore(); + return; + } + m_dragTarget = widget; + const QPoint mapped = widget->mapFrom(m_widget, event->pos()); + QDragEnterEvent translated(mapped, event->possibleActions(), event->mimeData(), event->mouseButtons(), event->keyboardModifiers()); + QGuiApplication::sendSpontaneousEvent(widget, &translated); + if (translated.isAccepted()) { + event->accept(); + } else { + event->ignore(); } + event->setDropAction(translated.dropAction()); +} + +void QWidgetWindow::handleDragLeaveEvent(QDragLeaveEvent *event) +{ + if (m_dragTarget) + QGuiApplication::sendSpontaneousEvent(m_dragTarget.data(), event); + m_dragTarget = 0; +} + +void QWidgetWindow::handleDropEvent(QDropEvent *event) +{ + const QPoint mapped = m_dragTarget.data()->mapFrom(m_widget, event->pos()); + QDropEvent translated(mapped, event->possibleActions(), event->mimeData(), event->mouseButtons(), event->keyboardModifiers()); + QGuiApplication::sendSpontaneousEvent(m_dragTarget.data(), &translated); + if (translated.isAccepted()) + event->accept(); + event->setDropAction(translated.dropAction()); + m_dragTarget = 0; } #endif // QT_NO_DRAGANDDROP diff --git a/src/widgets/kernel/qwidgetwindow_qpa_p.h b/src/widgets/kernel/qwidgetwindow_qpa_p.h index 5750a05d40..f9cd539dbc 100644 --- a/src/widgets/kernel/qwidgetwindow_qpa_p.h +++ b/src/widgets/kernel/qwidgetwindow_qpa_p.h @@ -80,7 +80,9 @@ protected: void handleResizeEvent(QResizeEvent *); void handleWheelEvent(QWheelEvent *); #ifndef QT_NO_DRAGANDDROP - void handleDragEvent(QEvent *); + void handleDragEnterMoveEvent(QDragMoveEvent *); + void handleDragLeaveEvent(QDragLeaveEvent *); + void handleDropEvent(QDropEvent *); #endif void handleExposeEvent(QExposeEvent *); void handleWindowStateChangedEvent(QWindowStateChangeEvent *event); 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 bccd9a5951..17a8d7911d 100644 --- a/tests/auto/widgets/kernel/qwidget_window/tst_qwidget_window.cpp +++ b/tests/auto/widgets/kernel/qwidget_window/tst_qwidget_window.cpp @@ -42,6 +42,10 @@ #include #include +#include +#include +#include +#include #include #include @@ -79,6 +83,10 @@ private slots: void tst_showWithoutActivating(); void tst_paintEventOnSecondShow(); + +#ifndef QT_NO_DRAGANDDROP + void tst_dnd(); +#endif }; void tst_QWidget_window::initTestCase() @@ -90,7 +98,8 @@ void tst_QWidget_window::cleanupTestCase() } /* Test if the maximum/minimum size constraints - * are propagated from the widget to the QWidgetWindow + * are propagated from the wid src/widgets/kernel/qwidgetwindow_qpa_p.h +get to the QWidgetWindow * independently of whether they were set before or after * window creation (QTBUG-26745). */ @@ -363,5 +372,180 @@ void tst_QWidget_window::tst_paintEventOnSecondShow() QTRY_VERIFY(w.paintEventReceived); } +#ifndef QT_NO_DRAGANDDROP + +/* DnD test for QWidgetWindow (handleDrag*Event() functions). + * Simulates a drop onto a QWidgetWindow of a top level widget + * that has 3 child widgets in a vertical layout with a frame. Only the lower 2 + * child widgets accepts drops (QTBUG-22987), the bottom child has another child + * that does not accept drops. + * Sends a series of DnD events to the QWidgetWindow, + * entering the top level at the top frame and move + * down in steps of 5 pixels, drop onto the bottom widget. + * The test compares the sequences of events received by the widgets in readable format. + * It also checks whether the address of the mimedata received is the same as the + * sending one, that is, no conversion/serialization of text mime data occurs in the + * process. */ + +static const char *expectedLogC[] = { + "Event at 11,1 ignored", + "Event at 11,21 ignored", + "Event at 11,41 ignored", + "Event at 11,61 ignored", + "Event at 11,81 ignored", + "Event at 11,101 ignored", + "acceptingDropsWidget1::dragEnterEvent at 1,11 action=1 MIME_DATA_ADDRESS 'testmimetext'", + "Event at 11,121 accepted", + "acceptingDropsWidget1::dragMoveEvent at 1,31 action=1 MIME_DATA_ADDRESS 'testmimetext'", + "Event at 11,141 accepted", + "acceptingDropsWidget1::dragMoveEvent at 1,51 action=1 MIME_DATA_ADDRESS 'testmimetext'", + "Event at 11,161 accepted", + "acceptingDropsWidget1::dragMoveEvent at 1,71 action=1 MIME_DATA_ADDRESS 'testmimetext'", + "Event at 11,181 accepted", + "acceptingDropsWidget1::dragLeaveEvent QDragLeaveEvent", + "Event at 11,201 ignored", + "acceptingDropsWidget2::dragEnterEvent at 1,11 action=1 MIME_DATA_ADDRESS 'testmimetext'", + "Event at 11,221 accepted", + "acceptingDropsWidget2::dragMoveEvent at 1,31 action=1 MIME_DATA_ADDRESS 'testmimetext'", + "Event at 11,241 accepted", + "acceptingDropsWidget2::dropEvent at 1,51 action=1 MIME_DATA_ADDRESS 'testmimetext'", + "Event at 11,261 accepted" +}; + +// A widget that logs the DnD events it receives into a QStringList. +class DnDEventLoggerWidget : public QWidget +{ +public: + DnDEventLoggerWidget(QStringList *log, QWidget *w = 0) : QWidget(w), m_log(log) {} + +protected: + void dragEnterEvent(QDragEnterEvent *); + void dragMoveEvent(QDragMoveEvent *); + void dragLeaveEvent(QDragLeaveEvent *); + void dropEvent(QDropEvent *); + +private: + void formatDropEvent(const char *function, const QDropEvent *e, QTextStream &str) const; + QStringList *m_log; +}; + +void DnDEventLoggerWidget::formatDropEvent(const char *function, const QDropEvent *e, QTextStream &str) const +{ + str << objectName() << "::" << function << " at " << e->pos().x() << ',' << e->pos().y() + << " action=" << e->dropAction() + << ' ' << quintptr(e->mimeData()) << " '" << e->mimeData()->text() << '\''; +} + +void DnDEventLoggerWidget::dragEnterEvent(QDragEnterEvent *e) +{ + e->accept(); + QString message; + QTextStream str(&message); + formatDropEvent("dragEnterEvent", e, str); + m_log->push_back(message); +} + +void DnDEventLoggerWidget::dragMoveEvent(QDragMoveEvent *e) +{ + e->accept(); + QString message; + QTextStream str(&message); + formatDropEvent("dragMoveEvent", e, str); + m_log->push_back(message); +} + +void DnDEventLoggerWidget::dragLeaveEvent(QDragLeaveEvent *e) +{ + e->accept(); + m_log->push_back(objectName() + QLatin1String("::") + QLatin1String("dragLeaveEvent") + QLatin1String(" QDragLeaveEvent")); +} + +void DnDEventLoggerWidget::dropEvent(QDropEvent *e) +{ + e->accept(); + QString message; + QTextStream str(&message); + formatDropEvent("dropEvent", e, str); + m_log->push_back(message); +} + +static QString msgEventAccepted(const QDropEvent &e) +{ + QString message; + QTextStream str(&message); + str << "Event at " << e.pos().x() << ',' << e.pos().y() << ' ' << (e.isAccepted() ? "accepted" : "ignored"); + return message; +} + +void tst_QWidget_window::tst_dnd() +{ + QStringList log; + DnDEventLoggerWidget dndTestWidget(&log); + + dndTestWidget.setObjectName(QLatin1String("dndTestWidget")); + dndTestWidget.setWindowTitle(dndTestWidget.objectName()); + dndTestWidget.resize(200, 300); + + QWidget *dropsRefusingWidget1 = new DnDEventLoggerWidget(&log, &dndTestWidget); + dropsRefusingWidget1->setObjectName(QLatin1String("dropsRefusingWidget1")); + dropsRefusingWidget1->resize(180, 80); + dropsRefusingWidget1->move(10, 10); + + QWidget *dropsAcceptingWidget1 = new DnDEventLoggerWidget(&log, &dndTestWidget); + dropsAcceptingWidget1->setAcceptDrops(true); + dropsAcceptingWidget1->setObjectName(QLatin1String("acceptingDropsWidget1")); + dropsAcceptingWidget1->resize(180, 80); + dropsAcceptingWidget1->move(10, 110); + + QWidget *dropsAcceptingWidget2 = new DnDEventLoggerWidget(&log, &dndTestWidget); + dropsAcceptingWidget2->setAcceptDrops(true); + dropsAcceptingWidget2->setObjectName(QLatin1String("acceptingDropsWidget2")); + dropsAcceptingWidget2->resize(180, 80); + dropsAcceptingWidget2->move(10, 210); + + QWidget *dropsRefusingWidget2 = new DnDEventLoggerWidget(&log, dropsAcceptingWidget2); + dropsRefusingWidget2->setObjectName(QLatin1String("dropsRefusingDropsWidget2")); + dropsRefusingWidget2->resize(160, 60); + dropsRefusingWidget2->move(10, 10); + + dndTestWidget.show(); + qApp->setActiveWindow(&dndTestWidget); + QVERIFY(QTest::qWaitForWindowActive(&dndTestWidget)); + + QMimeData mimeData; + mimeData.setText(QLatin1String("testmimetext")); + + // Simulate DnD events on the QWidgetWindow. + QPoint position = QPoint(11, 1); + QDragEnterEvent e(position, Qt::CopyAction, &mimeData, Qt::LeftButton, Qt::NoModifier); + QWindow *window = dndTestWidget.windowHandle(); + qApp->sendEvent(window, &e); + log.push_back(msgEventAccepted(e)); + while (true) { + position.ry() += 20; + if (position.y() >= 250) { + QDropEvent e(position, Qt::CopyAction, &mimeData, Qt::LeftButton, Qt::NoModifier); + qApp->sendEvent(window, &e); + log.push_back(msgEventAccepted(e)); + break; + } else { + QDragMoveEvent e(position, Qt::CopyAction, &mimeData, Qt::LeftButton, Qt::NoModifier); + qApp->sendEvent(window, &e); + log.push_back(msgEventAccepted(e)); + } + } + + // Compare logs. + QStringList expectedLog; + const int expectedLogSize = int(sizeof(expectedLogC) / sizeof(expectedLogC[0])); + const QString mimeDataAddress = QString::number(quintptr(&mimeData)); + const QString mimeDataAddressPlaceHolder = QLatin1String("MIME_DATA_ADDRESS"); + for (int i= 0; i < expectedLogSize; ++i) + expectedLog.push_back(QString::fromLatin1(expectedLogC[i]).replace(mimeDataAddressPlaceHolder, mimeDataAddress)); + + QCOMPARE(log, expectedLog); +} +#endif + QTEST_MAIN(tst_QWidget_window) #include "tst_qwidget_window.moc" -- cgit v1.2.3