Fix updating of drag icons.
authorFriedemann Kleint <Friedemann.Kleint@nokia.com>
Wed, 29 Aug 2012 14:06:05 +0000 (16:06 +0200)
committerQt by Nokia <qt-info@nokia.com>
Fri, 31 Aug 2012 14:10:25 +0000 (16:10 +0200)
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 <lars.knoll@nokia.com>
src/widgets/kernel/qwidgetwindow.cpp
src/widgets/kernel/qwidgetwindow_qpa_p.h
tests/auto/widgets/kernel/qwidget_window/tst_qwidget_window.cpp

index 6631342..81a341c 100644 (file)
@@ -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<QDragMoveEvent *>(event));
+        return true;
+    case QEvent::DragLeave:
+        handleDragLeaveEvent(static_cast<QDragLeaveEvent *>(event));
+        return true;
     case QEvent::Drop:
-        handleDragEvent(event);
+        handleDropEvent(static_cast<QDropEvent *>(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<QDragMoveEvent *>(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<QDropEvent *>(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
index 5750a05..f9cd539 100644 (file)
@@ -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);
index bccd9a5..17a8d79 100644 (file)
 
 #include <QtTest/QtTest>
 #include <QtGui/QtGui>
+#include <QtCore/QTextStream>
+#include <QtCore/QStringList>
+#include <QtCore/QMimeData>
+#include <QtCore/QPoint>
 #include <qeventloop.h>
 #include <qlist.h>
 
@@ -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"