blob: b90c9c3c454e018ca2c1786494050236f01e7f4f [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the test suite of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include <QtTest/QtTest>
#include <QtTest/QSignalSpy>
#include <QtQuick/qquickitem.h>
#include <QtQuick/qquickview.h>
#include <QtQml/qqmlcontext.h>
#include <QtQml/qqmlengine.h>
#include <QtQml/qqmlexpression.h>
template <typename T> static T evaluate(QObject *scope, const QString &expression)
{
QQmlExpression expr(qmlContext(scope), scope, expression);
QVariant result = expr.evaluate();
if (expr.hasError())
qWarning() << expr.error().toString();
return result.value<T>();
}
template <> void evaluate<void>(QObject *scope, const QString &expression)
{
QQmlExpression expr(qmlContext(scope), scope, expression);
expr.evaluate();
if (expr.hasError())
qWarning() << expr.error().toString();
}
Q_DECLARE_METATYPE(Qt::DropActions)
class TestDropTarget : public QQuickItem
{
Q_OBJECT
public:
TestDropTarget(QQuickItem *parent = nullptr)
: QQuickItem(parent)
{
setFlags(ItemAcceptsDrops);
}
void reset()
{
enterEvents = 0;
moveEvents = 0;
leaveEvents = 0;
dropEvents = 0;
defaultAction = Qt::IgnoreAction;
proposedAction = Qt::IgnoreAction;
supportedActions = Qt::IgnoreAction;
}
void dragEnterEvent(QDragEnterEvent *event)
{
++enterEvents;
position = event->pos();
defaultAction = event->dropAction();
proposedAction = event->proposedAction();
supportedActions = event->possibleActions();
event->setAccepted(accept);
}
void dragMoveEvent(QDragMoveEvent *event)
{
++moveEvents;
position = event->pos();
defaultAction = event->dropAction();
proposedAction = event->proposedAction();
supportedActions = event->possibleActions();
event->setAccepted(accept);
}
void dragLeaveEvent(QDragLeaveEvent *event)
{
++leaveEvents;
event->setAccepted(accept);
}
void dropEvent(QDropEvent *event)
{
++dropEvents;
position = event->pos();
defaultAction = event->dropAction();
proposedAction = event->proposedAction();
supportedActions = event->possibleActions();
event->setDropAction(acceptAction);
event->setAccepted(accept);
}
int enterEvents = 0;
int moveEvents = 0;
int leaveEvents = 0;
int dropEvents = 0;
Qt::DropAction acceptAction = Qt::MoveAction;
Qt::DropAction defaultAction = Qt::IgnoreAction;
Qt::DropAction proposedAction = Qt::IgnoreAction;
Qt::DropActions supportedActions;
QPointF position;
bool accept = true;
};
class tst_QQuickDrag: public QObject
{
Q_OBJECT
private slots:
void initTestCase();
void cleanupTestCase();
void active();
void setActive_data();
void setActive();
void drop();
void move();
void parentChange();
void hotSpot();
void supportedActions();
void proposedAction();
void keys();
void source();
void recursion_data();
void recursion();
void noCrashWithImageProvider();
private:
QQmlEngine engine;
};
void tst_QQuickDrag::initTestCase()
{
}
void tst_QQuickDrag::cleanupTestCase()
{
}
void tst_QQuickDrag::active()
{
QQuickWindow window;
TestDropTarget dropTarget(window.contentItem());
dropTarget.setSize(QSizeF(100, 100));
QQmlComponent component(&engine);
component.setData(
"import QtQuick 2.0\n"
"Item {\n"
"property bool dragActive: Drag.active\n"
"property Item dragTarget: Drag.target\n"
"x: 50; y: 50\n"
"width: 10; height: 10\n"
"}", QUrl());
QScopedPointer<QObject> object(component.create());
QQuickItem *item = qobject_cast<QQuickItem *>(object.data());
QVERIFY(item);
item->setParentItem(&dropTarget);
QCOMPARE(evaluate<bool>(item, "Drag.active"), false);
QCOMPARE(evaluate<bool>(item, "dragActive"), false);
evaluate<void>(item, "Drag.active = true");
QCOMPARE(evaluate<bool>(item, "Drag.active"), true);
QCOMPARE(evaluate<bool>(item, "dragActive"), true);
QCOMPARE(evaluate<QObject *>(item, "Drag.target"), static_cast<QObject *>(&dropTarget));
QCOMPARE(evaluate<QObject *>(item, "dragTarget"), static_cast<QObject *>(&dropTarget));
QCOMPARE(dropTarget.enterEvents, 1); QCOMPARE(dropTarget.leaveEvents, 0);
dropTarget.reset();
evaluate<void>(item, "Drag.active = false");
QCOMPARE(evaluate<bool>(item, "Drag.active"), false);
QCOMPARE(evaluate<bool>(item, "dragActive"), false);
QCOMPARE(evaluate<QObject *>(item, "Drag.target"), static_cast<QObject *>(nullptr));
QCOMPARE(evaluate<QObject *>(item, "dragTarget"), static_cast<QObject *>(nullptr));
QCOMPARE(dropTarget.enterEvents, 0); QCOMPARE(dropTarget.leaveEvents, 1);
dropTarget.reset();
evaluate<void>(item, "Drag.cancel()");
QCOMPARE(evaluate<bool>(item, "Drag.active"), false);
QCOMPARE(evaluate<bool>(item, "dragActive"), false);
QCOMPARE(evaluate<QObject *>(item, "Drag.target"), static_cast<QObject *>(nullptr));
QCOMPARE(evaluate<QObject *>(item, "dragTarget"), static_cast<QObject *>(nullptr));
QCOMPARE(dropTarget.enterEvents, 0); QCOMPARE(dropTarget.leaveEvents, 0);
dropTarget.reset();
evaluate<void>(item, "Drag.start()");
QCOMPARE(evaluate<bool>(item, "Drag.active"), true);
QCOMPARE(evaluate<bool>(item, "dragActive"), true);
QCOMPARE(evaluate<QObject *>(item, "Drag.target"), static_cast<QObject *>(&dropTarget));
QCOMPARE(evaluate<QObject *>(item, "dragTarget"), static_cast<QObject *>(&dropTarget));
QCOMPARE(dropTarget.enterEvents, 1); QCOMPARE(dropTarget.leaveEvents, 0);
// Start while a drag is active, cancels the previous drag and starts a new one.
dropTarget.reset();
evaluate<void>(item, "Drag.start()");
QCOMPARE(evaluate<bool>(item, "Drag.active"), true);
QCOMPARE(evaluate<bool>(item, "dragActive"), true);
QCOMPARE(evaluate<QObject *>(item, "Drag.target"), static_cast<QObject *>(&dropTarget));
QCOMPARE(evaluate<QObject *>(item, "dragTarget"), static_cast<QObject *>(&dropTarget));
QCOMPARE(dropTarget.enterEvents, 1); QCOMPARE(dropTarget.leaveEvents, 1);
dropTarget.reset();
evaluate<void>(item, "Drag.cancel()");
QCOMPARE(evaluate<bool>(item, "Drag.active"), false);
QCOMPARE(evaluate<bool>(item, "dragActive"), false);
QCOMPARE(evaluate<QObject *>(item, "Drag.target"), static_cast<QObject *>(nullptr));
QCOMPARE(evaluate<QObject *>(item, "dragTarget"), static_cast<QObject *>(nullptr));
QCOMPARE(dropTarget.enterEvents, 0); QCOMPARE(dropTarget.leaveEvents, 1);
// Enter events aren't sent to items without the QQuickItem::ItemAcceptsDrops flag.
dropTarget.setFlags(QQuickItem::Flags());
dropTarget.reset();
evaluate<void>(item, "Drag.active = true");
QCOMPARE(evaluate<bool>(item, "Drag.active"), true);
QCOMPARE(evaluate<bool>(item, "dragActive"), true);
QCOMPARE(evaluate<QObject *>(item, "Drag.target"), static_cast<QObject *>(nullptr));
QCOMPARE(evaluate<QObject *>(item, "dragTarget"), static_cast<QObject *>(nullptr));
QCOMPARE(dropTarget.enterEvents, 0); QCOMPARE(dropTarget.leaveEvents, 0);
dropTarget.reset();
evaluate<void>(item, "Drag.active = false");
QCOMPARE(evaluate<bool>(item, "Drag.active"), false);
QCOMPARE(evaluate<bool>(item, "dragActive"), false);
QCOMPARE(evaluate<QObject *>(item, "Drag.target"), static_cast<QObject *>(nullptr));
QCOMPARE(evaluate<QObject *>(item, "dragTarget"), static_cast<QObject *>(nullptr));
QCOMPARE(dropTarget.enterEvents, 0); QCOMPARE(dropTarget.leaveEvents, 0);
dropTarget.setFlags(QQuickItem::ItemAcceptsDrops);
dropTarget.reset();
evaluate<void>(item, "Drag.active = true");
QCOMPARE(evaluate<bool>(item, "Drag.active"), true);
QCOMPARE(evaluate<bool>(item, "dragActive"), true);
QCOMPARE(evaluate<QObject *>(item, "Drag.target"), static_cast<QObject *>(&dropTarget));
QCOMPARE(evaluate<QObject *>(item, "dragTarget"), static_cast<QObject *>(&dropTarget));
QCOMPARE(dropTarget.enterEvents, 1); QCOMPARE(dropTarget.leaveEvents, 0);
dropTarget.setFlags(QQuickItem::Flags());
dropTarget.reset();
evaluate<void>(item, "Drag.active = false");
QCOMPARE(evaluate<bool>(item, "Drag.active"), false);
QCOMPARE(evaluate<bool>(item, "dragActive"), false);
QCOMPARE(evaluate<QObject *>(item, "Drag.target"), static_cast<QObject *>(nullptr));
QCOMPARE(evaluate<QObject *>(item, "dragTarget"), static_cast<QObject *>(nullptr));
QCOMPARE(dropTarget.enterEvents, 0); QCOMPARE(dropTarget.leaveEvents, 1);
// Follow up events aren't sent to items if the enter event isn't accepted.
dropTarget.setFlags(QQuickItem::ItemAcceptsDrops);
dropTarget.accept = false;
dropTarget.reset();
evaluate<void>(item, "Drag.active = true");
QCOMPARE(evaluate<bool>(item, "Drag.active"), true);
QCOMPARE(evaluate<bool>(item, "dragActive"), true);
QCOMPARE(evaluate<QObject *>(item, "Drag.target"), static_cast<QObject *>(nullptr));
QCOMPARE(evaluate<QObject *>(item, "dragTarget"), static_cast<QObject *>(nullptr));
QCOMPARE(dropTarget.enterEvents, 1); QCOMPARE(dropTarget.leaveEvents, 0);
dropTarget.reset();
evaluate<void>(item, "Drag.active = false");
QCOMPARE(evaluate<bool>(item, "Drag.active"), false);
QCOMPARE(evaluate<bool>(item, "dragActive"), false);
QCOMPARE(evaluate<QObject *>(item, "Drag.target"), static_cast<QObject *>(nullptr));
QCOMPARE(evaluate<QObject *>(item, "dragTarget"), static_cast<QObject *>(nullptr));
QCOMPARE(dropTarget.enterEvents, 0); QCOMPARE(dropTarget.leaveEvents, 0);
dropTarget.accept = true;
dropTarget.reset();
evaluate<void>(item, "Drag.active = true");
QCOMPARE(evaluate<bool>(item, "Drag.active"), true);
QCOMPARE(evaluate<bool>(item, "dragActive"), true);
QCOMPARE(evaluate<QObject *>(item, "Drag.target"), static_cast<QObject *>(&dropTarget));
QCOMPARE(evaluate<QObject *>(item, "dragTarget"), static_cast<QObject *>(&dropTarget));
QCOMPARE(dropTarget.enterEvents, 1); QCOMPARE(dropTarget.leaveEvents, 0);
dropTarget.accept = false;
dropTarget.reset();
evaluate<void>(item, "Drag.active = false");
QCOMPARE(evaluate<bool>(item, "Drag.active"), false);
QCOMPARE(evaluate<bool>(item, "dragActive"), false);
QCOMPARE(evaluate<QObject *>(item, "Drag.target"), static_cast<QObject *>(nullptr));
QCOMPARE(evaluate<QObject *>(item, "dragTarget"), static_cast<QObject *>(nullptr));
QCOMPARE(dropTarget.enterEvents, 0); QCOMPARE(dropTarget.leaveEvents, 1);
// Events are sent to hidden or disabled items.
dropTarget.accept = true;
dropTarget.setVisible(false);
dropTarget.reset();
evaluate<void>(item, "Drag.active = true");
QCOMPARE(evaluate<bool>(item, "Drag.active"), true);
QCOMPARE(evaluate<bool>(item, "dragActive"), true);
QCOMPARE(evaluate<QObject *>(item, "Drag.target"), static_cast<QObject *>(nullptr));
QCOMPARE(evaluate<QObject *>(item, "dragTarget"), static_cast<QObject *>(nullptr));
QCOMPARE(dropTarget.enterEvents, 0); QCOMPARE(dropTarget.leaveEvents, 0);
evaluate<void>(item, "Drag.active = false");
dropTarget.setVisible(true);
dropTarget.setOpacity(0.0);
dropTarget.reset();
evaluate<void>(item, "Drag.active = true");
QCOMPARE(evaluate<bool>(item, "Drag.active"), true);
QCOMPARE(evaluate<bool>(item, "dragActive"), true);
QCOMPARE(evaluate<QObject *>(item, "Drag.target"), static_cast<QObject *>(&dropTarget));
QCOMPARE(evaluate<QObject *>(item, "dragTarget"), static_cast<QObject *>(&dropTarget));
QCOMPARE(dropTarget.enterEvents, 1); QCOMPARE(dropTarget.leaveEvents, 0);
evaluate<void>(item, "Drag.active = false");
dropTarget.setOpacity(1.0);
dropTarget.setEnabled(false);
dropTarget.reset();
evaluate<void>(item, "Drag.active = true");
QCOMPARE(evaluate<bool>(item, "Drag.active"), true);
QCOMPARE(evaluate<bool>(item, "dragActive"), true);
QCOMPARE(evaluate<QObject *>(item, "Drag.target"), static_cast<QObject *>(nullptr));
QCOMPARE(evaluate<QObject *>(item, "dragTarget"), static_cast<QObject *>(nullptr));
QCOMPARE(dropTarget.enterEvents, 0); QCOMPARE(dropTarget.leaveEvents, 0);
evaluate<void>(item, "Drag.active = false");
dropTarget.setEnabled(true);
dropTarget.reset();
// Queued move events are discarded if the drag is cancelled.
evaluate<void>(item, "Drag.active = true");
QCOMPARE(evaluate<bool>(item, "Drag.active"), true);
QCOMPARE(evaluate<bool>(item, "dragActive"), true);
QCOMPARE(dropTarget.enterEvents, 1); QCOMPARE(dropTarget.leaveEvents, 0); QCOMPARE(dropTarget.moveEvents, 0);
dropTarget.reset();
item->setPosition(QPointF(80, 80));
QCOMPARE(evaluate<bool>(item, "Drag.active"), true);
QCOMPARE(evaluate<bool>(item, "dragActive"), true);
QCOMPARE(dropTarget.enterEvents, 0); QCOMPARE(dropTarget.leaveEvents, 0); QCOMPARE(dropTarget.moveEvents, 0);
evaluate<void>(item, "Drag.active = false");
QCOMPARE(evaluate<bool>(item, "Drag.active"), false);
QCOMPARE(evaluate<bool>(item, "dragActive"), false);
QCOMPARE(dropTarget.enterEvents, 0); QCOMPARE(dropTarget.leaveEvents, 1); QCOMPARE(dropTarget.moveEvents, 0);
dropTarget.reset();
QCoreApplication::processEvents();
QCOMPARE(dropTarget.enterEvents, 0); QCOMPARE(dropTarget.leaveEvents, 0); QCOMPARE(dropTarget.moveEvents, 0);
}
void tst_QQuickDrag::setActive_data()
{
QTest::addColumn<QString>("dragType");
QTest::newRow("default") << "";
QTest::newRow("internal") << "Drag.dragType: Drag.Internal";
QTest::newRow("none") << "Drag.dragType: Drag.None";
/* We don't test Drag.Automatic, because that causes QDrag::exec() to be
* invoked, and on some platforms tha's implemented by running a main loop
* until the drag has finished -- and at that point, the Drag.active will
* be false again. */
}
// QTBUG-52540
void tst_QQuickDrag::setActive()
{
QFETCH(QString, dragType);
QQuickWindow window;
TestDropTarget dropTarget(window.contentItem());
dropTarget.setSize(QSizeF(100, 100));
QQmlComponent component(&engine);
component.setData(
"import QtQuick 2.0\n"
"Item {\n"
"property bool dragActive: Drag.active\n"
"property Item dragTarget: Drag.target\n" +
dragType.toUtf8() + "\n"
"x: 50; y: 50\n"
"width: 10; height: 10\n"
"}", QUrl());
QScopedPointer<QObject> object(component.create());
QQuickItem *item = qobject_cast<QQuickItem *>(object.data());
QVERIFY(item);
item->setParentItem(&dropTarget);
QCOMPARE(evaluate<bool>(item, "Drag.active"), false);
QCOMPARE(evaluate<bool>(item, "dragActive"), false);
evaluate<void>(item, "Drag.active = true");
QCOMPARE(evaluate<bool>(item, "Drag.active"), true);
QCOMPARE(evaluate<bool>(item, "dragActive"), true);
}
void tst_QQuickDrag::drop()
{
QQuickWindow window;
TestDropTarget outerTarget(window.contentItem());
outerTarget.setSize(QSizeF(100, 100));
outerTarget.acceptAction = Qt::CopyAction;
TestDropTarget innerTarget(&outerTarget);
innerTarget.setSize(QSizeF(100, 100));
innerTarget.acceptAction = Qt::MoveAction;
QQmlComponent component(&engine);
component.setData(
"import QtQuick 2.0\n"
"Item {\n"
"property bool dragActive: Drag.active\n"
"property Item dragTarget: Drag.target\n"
"x: 50; y: 50\n"
"width: 10; height: 10\n"
"}", QUrl());
QScopedPointer<QObject> object(component.create());
QQuickItem *item = qobject_cast<QQuickItem *>(object.data());
QVERIFY(item);
item->setParentItem(&outerTarget);
innerTarget.reset(); outerTarget.reset();
evaluate<void>(item, "Drag.active = true");
QCOMPARE(evaluate<bool>(item, "Drag.active"), true);
QCOMPARE(evaluate<bool>(item, "dragActive"), true);
QCOMPARE(evaluate<QObject *>(item, "Drag.target"), static_cast<QObject *>(&innerTarget));
QCOMPARE(evaluate<QObject *>(item, "dragTarget"), static_cast<QObject *>(&innerTarget));
QCOMPARE(outerTarget.enterEvents, 0); QCOMPARE(outerTarget.leaveEvents, 0); QCOMPARE(outerTarget.dropEvents, 0);
QCOMPARE(innerTarget.enterEvents, 1); QCOMPARE(innerTarget.leaveEvents, 0); QCOMPARE(innerTarget.dropEvents, 0);
innerTarget.reset(); outerTarget.reset();
QCOMPARE(evaluate<bool>(item, "Drag.drop() == Qt.MoveAction"), true);
QCOMPARE(evaluate<bool>(item, "Drag.active"), false);
QCOMPARE(evaluate<bool>(item, "dragActive"), false);
QCOMPARE(evaluate<QObject *>(item, "Drag.target"), static_cast<QObject *>(&innerTarget));
QCOMPARE(evaluate<QObject *>(item, "dragTarget"), static_cast<QObject *>(&innerTarget));
QCOMPARE(outerTarget.enterEvents, 0); QCOMPARE(outerTarget.leaveEvents, 0); QCOMPARE(outerTarget.dropEvents, 0);
QCOMPARE(innerTarget.enterEvents, 0); QCOMPARE(innerTarget.leaveEvents, 0); QCOMPARE(innerTarget.dropEvents, 1);
innerTarget.reset(); outerTarget.reset();
evaluate<void>(item, "Drag.active = true");
QCOMPARE(evaluate<bool>(item, "Drag.active"), true);
QCOMPARE(evaluate<bool>(item, "dragActive"), true);
QCOMPARE(evaluate<QObject *>(item, "Drag.target"), static_cast<QObject *>(&innerTarget));
QCOMPARE(evaluate<QObject *>(item, "dragTarget"), static_cast<QObject *>(&innerTarget));
QCOMPARE(outerTarget.enterEvents, 0); QCOMPARE(outerTarget.leaveEvents, 0); QCOMPARE(outerTarget.dropEvents, 0);
QCOMPARE(innerTarget.enterEvents, 1); QCOMPARE(innerTarget.leaveEvents, 0); QCOMPARE(innerTarget.dropEvents, 0);
evaluate<void>(item, "Drag.active = false");
// Inner target doesn't accept enter so drop goes directly to outer.
innerTarget.accept = false;
innerTarget.setFlags(QQuickItem::Flags());
innerTarget.reset(); outerTarget.reset();
evaluate<void>(item, "Drag.active = true");
QCOMPARE(evaluate<bool>(item, "Drag.active"), true);
QCOMPARE(evaluate<bool>(item, "dragActive"), true);
QCOMPARE(evaluate<QObject *>(item, "Drag.target"), static_cast<QObject *>(&outerTarget));
QCOMPARE(evaluate<QObject *>(item, "dragTarget"), static_cast<QObject *>(&outerTarget));
QCOMPARE(outerTarget.enterEvents, 1); QCOMPARE(outerTarget.leaveEvents, 0); QCOMPARE(outerTarget.dropEvents, 0);
QCOMPARE(innerTarget.enterEvents, 0); QCOMPARE(innerTarget.leaveEvents, 0); QCOMPARE(innerTarget.dropEvents, 0);
innerTarget.reset(); outerTarget.reset();
QCOMPARE(evaluate<bool>(item, "Drag.drop() == Qt.CopyAction"), true);
QCOMPARE(evaluate<bool>(item, "Drag.active"), false);
QCOMPARE(evaluate<bool>(item, "dragActive"), false);
QCOMPARE(evaluate<QObject *>(item, "Drag.target"), static_cast<QObject *>(&outerTarget));
QCOMPARE(evaluate<QObject *>(item, "dragTarget"), static_cast<QObject *>(&outerTarget));
QCOMPARE(outerTarget.enterEvents, 0); QCOMPARE(outerTarget.leaveEvents, 0); QCOMPARE(outerTarget.dropEvents, 1);
QCOMPARE(innerTarget.enterEvents, 0); QCOMPARE(innerTarget.leaveEvents, 0); QCOMPARE(innerTarget.dropEvents, 0);
// Neither target accepts drop so Qt::IgnoreAction is returned.
innerTarget.reset(); outerTarget.reset();
evaluate<void>(item, "Drag.active = true");
QCOMPARE(evaluate<bool>(item, "Drag.active"), true);
QCOMPARE(evaluate<bool>(item, "dragActive"), true);
QCOMPARE(evaluate<QObject *>(item, "Drag.target"), static_cast<QObject *>(&outerTarget));
QCOMPARE(evaluate<QObject *>(item, "dragTarget"), static_cast<QObject *>(&outerTarget));
QCOMPARE(outerTarget.enterEvents, 1); QCOMPARE(outerTarget.leaveEvents, 0); QCOMPARE(outerTarget.dropEvents, 0);
QCOMPARE(innerTarget.enterEvents, 0); QCOMPARE(innerTarget.leaveEvents, 0); QCOMPARE(innerTarget.dropEvents, 0);
outerTarget.accept = false;
innerTarget.reset(); outerTarget.reset();
QCOMPARE(evaluate<bool>(item, "Drag.drop() == Qt.IgnoreAction"), true);
QCOMPARE(evaluate<bool>(item, "Drag.active"), false);
QCOMPARE(evaluate<bool>(item, "dragActive"), false);
QCOMPARE(evaluate<QObject *>(item, "Drag.target"), static_cast<QObject *>(nullptr));
QCOMPARE(evaluate<QObject *>(item, "dragTarget"), static_cast<QObject *>(nullptr));
QCOMPARE(outerTarget.enterEvents, 0); QCOMPARE(outerTarget.leaveEvents, 0); QCOMPARE(outerTarget.dropEvents, 1);
QCOMPARE(innerTarget.enterEvents, 0); QCOMPARE(innerTarget.leaveEvents, 0); QCOMPARE(innerTarget.dropEvents, 0);
// drop doesn't send an event and returns Qt.IgnoreAction if not active.
innerTarget.accept = true;
outerTarget.accept = true;
innerTarget.reset(); outerTarget.reset();
QCOMPARE(evaluate<bool>(item, "Drag.drop() == Qt.IgnoreAction"), true);
QCOMPARE(evaluate<bool>(item, "Drag.active"), false);
QCOMPARE(evaluate<bool>(item, "dragActive"), false);
QCOMPARE(evaluate<QObject *>(item, "Drag.target"), static_cast<QObject *>(nullptr));
QCOMPARE(evaluate<QObject *>(item, "dragTarget"), static_cast<QObject *>(nullptr));
QCOMPARE(outerTarget.enterEvents, 0); QCOMPARE(outerTarget.leaveEvents, 0); QCOMPARE(outerTarget.dropEvents, 0);
QCOMPARE(innerTarget.enterEvents, 0); QCOMPARE(innerTarget.leaveEvents, 0); QCOMPARE(innerTarget.dropEvents, 0);
// Queued move event is delivered before a drop event.
innerTarget.reset(); outerTarget.reset();
evaluate<void>(item, "Drag.active = true");
item->setPosition(QPointF(80, 80));
evaluate<void>(item, "Drag.drop()");
QCOMPARE(evaluate<bool>(item, "Drag.active"), false);
QCOMPARE(evaluate<bool>(item, "dragActive"), false);
QCOMPARE(evaluate<QObject *>(item, "Drag.target"), static_cast<QObject *>(&outerTarget));
QCOMPARE(evaluate<QObject *>(item, "dragTarget"), static_cast<QObject *>(&outerTarget));
QCOMPARE(outerTarget.enterEvents, 1); QCOMPARE(outerTarget.leaveEvents, 0); QCOMPARE(outerTarget.dropEvents, 1); QCOMPARE(outerTarget.moveEvents, 1);
QCOMPARE(innerTarget.enterEvents, 0); QCOMPARE(innerTarget.leaveEvents, 0); QCOMPARE(innerTarget.dropEvents, 0); QCOMPARE(innerTarget.moveEvents, 0);
innerTarget.reset(); outerTarget.reset();
QCoreApplication::processEvents();
QCOMPARE(outerTarget.enterEvents, 0); QCOMPARE(outerTarget.leaveEvents, 0); QCOMPARE(outerTarget.dropEvents, 0); QCOMPARE(outerTarget.moveEvents, 0);
QCOMPARE(innerTarget.enterEvents, 0); QCOMPARE(innerTarget.leaveEvents, 0); QCOMPARE(innerTarget.dropEvents, 0); QCOMPARE(innerTarget.moveEvents, 0);
}
void tst_QQuickDrag::move()
{
QQuickWindow window;
TestDropTarget outerTarget(window.contentItem());
outerTarget.setSize(QSizeF(100, 100));
TestDropTarget leftTarget(&outerTarget);
leftTarget.setPosition(QPointF(0, 35));
leftTarget.setSize(QSizeF(30, 30));
TestDropTarget rightTarget(&outerTarget);
rightTarget.setPosition(QPointF(70, 35));
rightTarget.setSize(QSizeF(30, 30));
QQmlComponent component(&engine);
component.setData(
"import QtQuick 2.0\n"
"Item {\n"
"property bool dragActive: Drag.active\n"
"property Item dragTarget: Drag.target\n"
"x: 50; y: 50\n"
"width: 10; height: 10\n"
"}", QUrl());
QScopedPointer<QObject> object(component.create());
QQuickItem *item = qobject_cast<QQuickItem *>(object.data());
QVERIFY(item);
item->setParentItem(&outerTarget);
evaluate<void>(item, "Drag.active = true");
QCOMPARE(evaluate<bool>(item, "Drag.active"), true);
QCOMPARE(evaluate<bool>(item, "dragActive"), true);
QCOMPARE(evaluate<QObject *>(item, "Drag.target"), static_cast<QObject *>(&outerTarget));
QCOMPARE(evaluate<QObject *>(item, "dragTarget"), static_cast<QObject *>(&outerTarget));
QCOMPARE(outerTarget.enterEvents, 1); QCOMPARE(outerTarget.leaveEvents, 0); QCOMPARE(outerTarget.moveEvents, 0);
QCOMPARE(leftTarget .enterEvents, 0); QCOMPARE(leftTarget .leaveEvents, 0); QCOMPARE(leftTarget .moveEvents, 0);
QCOMPARE(rightTarget.enterEvents, 0); QCOMPARE(rightTarget.leaveEvents, 0); QCOMPARE(rightTarget.moveEvents, 0);
QCOMPARE(outerTarget.position.x(), qreal(50)); QCOMPARE(outerTarget.position.y(), qreal(50));
// Move within the outer target.
outerTarget.reset(); leftTarget.reset(); rightTarget.reset();
item->setPosition(QPointF(60, 50));
// Move event is delivered in the event loop.
QCOMPARE(outerTarget.enterEvents, 0); QCOMPARE(outerTarget.leaveEvents, 0); QCOMPARE(outerTarget.moveEvents, 0);
QCOMPARE(leftTarget .enterEvents, 0); QCOMPARE(leftTarget .leaveEvents, 0); QCOMPARE(leftTarget .moveEvents, 0);
QCOMPARE(rightTarget.enterEvents, 0); QCOMPARE(rightTarget.leaveEvents, 0); QCOMPARE(rightTarget.moveEvents, 0);
QCoreApplication::processEvents();
QCOMPARE(evaluate<QObject *>(item, "Drag.target"), static_cast<QObject *>(&outerTarget));
QCOMPARE(evaluate<QObject *>(item, "dragTarget"), static_cast<QObject *>(&outerTarget));
QCOMPARE(outerTarget.enterEvents, 0); QCOMPARE(outerTarget.leaveEvents, 0); QCOMPARE(outerTarget.moveEvents, 1);
QCOMPARE(leftTarget .enterEvents, 0); QCOMPARE(leftTarget .leaveEvents, 0); QCOMPARE(leftTarget .moveEvents, 0);
QCOMPARE(rightTarget.enterEvents, 0); QCOMPARE(rightTarget.leaveEvents, 0); QCOMPARE(rightTarget.moveEvents, 0);
QCOMPARE(outerTarget.position.x(), qreal(60)); QCOMPARE(outerTarget.position.y(), qreal(50));
// Move into the right target.
outerTarget.reset(); leftTarget.reset(); rightTarget.reset();
// Setting X and Y individually should still only generate on move.
item->setX(75);
item->setY(50);
QCOMPARE(outerTarget.enterEvents, 0); QCOMPARE(outerTarget.leaveEvents, 0); QCOMPARE(outerTarget.moveEvents, 0);
QCOMPARE(leftTarget .enterEvents, 0); QCOMPARE(leftTarget .leaveEvents, 0); QCOMPARE(leftTarget .moveEvents, 0);
QCOMPARE(rightTarget.enterEvents, 0); QCOMPARE(rightTarget.leaveEvents, 0); QCOMPARE(rightTarget.moveEvents, 0);
QCoreApplication::processEvents();
QCOMPARE(evaluate<QObject *>(item, "Drag.target"), static_cast<QObject *>(&rightTarget));
QCOMPARE(evaluate<QObject *>(item, "dragTarget"), static_cast<QObject *>(&rightTarget));
QCOMPARE(outerTarget.enterEvents, 0); QCOMPARE(outerTarget.leaveEvents, 1); QCOMPARE(outerTarget.moveEvents, 0);
QCOMPARE(leftTarget .enterEvents, 0); QCOMPARE(leftTarget .leaveEvents, 0); QCOMPARE(leftTarget .moveEvents, 0);
QCOMPARE(rightTarget.enterEvents, 1); QCOMPARE(rightTarget.leaveEvents, 0); QCOMPARE(rightTarget.moveEvents, 0);
QCOMPARE(rightTarget.position.x(), qreal(5)); QCOMPARE(rightTarget.position.y(), qreal(15));
// Move into the left target.
outerTarget.reset(); leftTarget.reset(); rightTarget.reset();
item->setPosition(QPointF(25, 50));
QCoreApplication::processEvents();
QCOMPARE(evaluate<QObject *>(item, "Drag.target"), static_cast<QObject *>(&leftTarget));
QCOMPARE(evaluate<QObject *>(item, "dragTarget"), static_cast<QObject *>(&leftTarget));
QCOMPARE(outerTarget.enterEvents, 0); QCOMPARE(outerTarget.leaveEvents, 0); QCOMPARE(outerTarget.moveEvents, 0);
QCOMPARE(leftTarget .enterEvents, 1); QCOMPARE(leftTarget .leaveEvents, 0); QCOMPARE(leftTarget .moveEvents, 0);
QCOMPARE(rightTarget.enterEvents, 0); QCOMPARE(rightTarget.leaveEvents, 1); QCOMPARE(rightTarget.moveEvents, 0);
QCOMPARE(leftTarget.position.x(), qreal(25)); QCOMPARE(leftTarget.position.y(), qreal(15));
// Move within the left target.
outerTarget.reset(); leftTarget.reset(); rightTarget.reset();
item->setPosition(QPointF(25, 40));
QCoreApplication::processEvents();
QCOMPARE(evaluate<QObject *>(item, "Drag.target"), static_cast<QObject *>(&leftTarget));
QCOMPARE(evaluate<QObject *>(item, "dragTarget"), static_cast<QObject *>(&leftTarget));
QCOMPARE(outerTarget.enterEvents, 0); QCOMPARE(outerTarget.leaveEvents, 0); QCOMPARE(outerTarget.moveEvents, 0);
QCOMPARE(leftTarget .enterEvents, 0); QCOMPARE(leftTarget .leaveEvents, 0); QCOMPARE(leftTarget .moveEvents, 1);
QCOMPARE(rightTarget.enterEvents, 0); QCOMPARE(rightTarget.leaveEvents, 0); QCOMPARE(rightTarget.moveEvents, 0);
QCOMPARE(outerTarget.position.x(), qreal(60)); QCOMPARE(outerTarget.position.y(), qreal(50));
QCOMPARE(leftTarget.position.x(), qreal(25)); QCOMPARE(leftTarget.position.y(), qreal(5));
// Move out of all targets.
outerTarget.reset(); leftTarget.reset(); rightTarget.reset();
item->setPosition(QPointF(110, 50));
QCoreApplication::processEvents();
QCOMPARE(evaluate<QObject *>(item, "Drag.target"), static_cast<QObject *>(nullptr));
QCOMPARE(evaluate<QObject *>(item, "dragTarget"), static_cast<QObject *>(nullptr));
QCOMPARE(outerTarget.enterEvents, 0); QCOMPARE(outerTarget.leaveEvents, 0); QCOMPARE(outerTarget.moveEvents, 0);
QCOMPARE(leftTarget .enterEvents, 0); QCOMPARE(leftTarget .leaveEvents, 1); QCOMPARE(leftTarget .moveEvents, 0);
QCOMPARE(rightTarget.enterEvents, 0); QCOMPARE(rightTarget.leaveEvents, 0); QCOMPARE(rightTarget.moveEvents, 0);
// Stop the right target accepting drag events and move into it.
rightTarget.accept = false;
outerTarget.reset(); leftTarget.reset(); rightTarget.reset();
item->setPosition(QPointF(80, 50));
QCoreApplication::processEvents();
QCOMPARE(evaluate<QObject *>(item, "Drag.target"), static_cast<QObject *>(&outerTarget));
QCOMPARE(evaluate<QObject *>(item, "dragTarget"), static_cast<QObject *>(&outerTarget));
QCOMPARE(outerTarget.enterEvents, 1); QCOMPARE(outerTarget.leaveEvents, 0); QCOMPARE(outerTarget.moveEvents, 0);
QCOMPARE(leftTarget .enterEvents, 0); QCOMPARE(leftTarget .leaveEvents, 0); QCOMPARE(leftTarget .moveEvents, 0);
QCOMPARE(rightTarget.enterEvents, 1); QCOMPARE(rightTarget.leaveEvents, 0); QCOMPARE(rightTarget.moveEvents, 0);
QCOMPARE(outerTarget.position.x(), qreal(80)); QCOMPARE(outerTarget.position.y(), qreal(50));
// Stop the outer target accepting drag events after it has accepted an enter event.
outerTarget.accept = false;
outerTarget.reset(); leftTarget.reset(); rightTarget.reset();
item->setPosition(QPointF(60, 50));
QCoreApplication::processEvents();
QCOMPARE(evaluate<QObject *>(item, "Drag.target"), static_cast<QObject *>(&outerTarget));
QCOMPARE(evaluate<QObject *>(item, "dragTarget"), static_cast<QObject *>(&outerTarget));
QCOMPARE(outerTarget.enterEvents, 0); QCOMPARE(outerTarget.leaveEvents, 0); QCOMPARE(outerTarget.moveEvents, 1);
QCOMPARE(leftTarget .enterEvents, 0); QCOMPARE(leftTarget .leaveEvents, 0); QCOMPARE(leftTarget .moveEvents, 0);
QCOMPARE(rightTarget.enterEvents, 0); QCOMPARE(rightTarget.leaveEvents, 0); QCOMPARE(rightTarget.moveEvents, 0);
QCOMPARE(outerTarget.position.x(), qreal(60)); QCOMPARE(outerTarget.position.y(), qreal(50));
// Clear the QQuickItem::ItemAcceptsDrops flag from the outer target after it accepted an enter event.
outerTarget.setFlags(QQuickItem::Flags());
outerTarget.reset(); leftTarget.reset(); rightTarget.reset();
item->setPosition(QPointF(40, 50));
QCoreApplication::processEvents();
QCOMPARE(evaluate<QObject *>(item, "Drag.target"), static_cast<QObject *>(&outerTarget));
QCOMPARE(evaluate<QObject *>(item, "dragTarget"), static_cast<QObject *>(&outerTarget));
QCOMPARE(outerTarget.enterEvents, 0); QCOMPARE(outerTarget.leaveEvents, 0); QCOMPARE(outerTarget.moveEvents, 1);
QCOMPARE(leftTarget .enterEvents, 0); QCOMPARE(leftTarget .leaveEvents, 0); QCOMPARE(leftTarget .moveEvents, 0);
QCOMPARE(rightTarget.enterEvents, 0); QCOMPARE(rightTarget.leaveEvents, 0); QCOMPARE(rightTarget.moveEvents, 0);
QCOMPARE(outerTarget.position.x(), qreal(40)); QCOMPARE(outerTarget.position.y(), qreal(50));
// Clear the QQuickItem::ItemAcceptsDrops flag from the left target before it accepts an enter event.
leftTarget.setFlags(QQuickItem::Flags());
outerTarget.reset(); leftTarget.reset(); rightTarget.reset();
item->setPosition(QPointF(25, 50));
QCoreApplication::processEvents();
QCOMPARE(evaluate<QObject *>(item, "Drag.target"), static_cast<QObject *>(&outerTarget));
QCOMPARE(evaluate<QObject *>(item, "dragTarget"), static_cast<QObject *>(&outerTarget));
QCOMPARE(outerTarget.enterEvents, 0); QCOMPARE(outerTarget.leaveEvents, 0); QCOMPARE(outerTarget.moveEvents, 1);
QCOMPARE(leftTarget .enterEvents, 0); QCOMPARE(leftTarget .leaveEvents, 0); QCOMPARE(leftTarget .moveEvents, 0);
QCOMPARE(rightTarget.enterEvents, 0); QCOMPARE(rightTarget.leaveEvents, 0); QCOMPARE(rightTarget.moveEvents, 0);
QCOMPARE(outerTarget.position.x(), qreal(25)); QCOMPARE(outerTarget.position.y(), qreal(50));
}
void tst_QQuickDrag::parentChange()
{
QQuickWindow window1;
TestDropTarget dropTarget1(window1.contentItem());
dropTarget1.setSize(QSizeF(100, 100));
QQuickWindow window2;
TestDropTarget dropTarget2(window2.contentItem());
dropTarget2.setSize(QSizeF(100, 100));
QQmlComponent component(&engine);
component.setData(
"import QtQuick 2.0\n"
"Item {\n"
"property real hotSpotX: Drag.hotSpot.x\n"
"property real hotSpotY: Drag.hotSpot.y\n"
"x: 50; y: 50\n"
"width: 10; height: 10\n"
"Drag.active: true\n"
"}", QUrl());
QScopedPointer<QObject> object(component.create());
QQuickItem *item = qobject_cast<QQuickItem *>(object.data());
QVERIFY(item);
QCOMPARE(evaluate<bool>(item, "Drag.active"), true);
// Verify setting a parent item for an item with an active drag sends an enter event.
item->setParentItem(window1.contentItem());
QCOMPARE(dropTarget1.enterEvents, 0);
QCoreApplication::processEvents();
QCOMPARE(dropTarget1.enterEvents, 1);
// Changing the parent within the same window should send a move event.
item->setParentItem(&dropTarget1);
QCOMPARE(dropTarget1.enterEvents, 1);
QCOMPARE(dropTarget1.moveEvents, 0);
QCoreApplication::processEvents();
QCOMPARE(dropTarget1.enterEvents, 1);
QCOMPARE(dropTarget1.moveEvents, 1);
// Changing the parent to an item in another window sends a leave event in the old window
// and an enter on the new window.
item->setParentItem(window2.contentItem());
QCOMPARE(dropTarget1.enterEvents, 1);
QCOMPARE(dropTarget1.moveEvents, 1);
QCOMPARE(dropTarget1.leaveEvents, 0);
QCOMPARE(dropTarget2.enterEvents, 0);
QCoreApplication::processEvents();
QCOMPARE(dropTarget1.enterEvents, 1);
QCOMPARE(dropTarget1.moveEvents, 1);
QCOMPARE(dropTarget1.leaveEvents, 1);
QCOMPARE(dropTarget2.enterEvents, 1);
// Removing then parent item sends a leave event.
item->setParentItem(nullptr);
QCOMPARE(dropTarget1.enterEvents, 1);
QCOMPARE(dropTarget1.moveEvents, 1);
QCOMPARE(dropTarget1.leaveEvents, 1);
QCOMPARE(dropTarget2.enterEvents, 1);
QCOMPARE(dropTarget2.leaveEvents, 0);
QCoreApplication::processEvents();
QCOMPARE(dropTarget1.enterEvents, 1);
QCOMPARE(dropTarget1.moveEvents, 1);
QCOMPARE(dropTarget1.leaveEvents, 1);
QCOMPARE(dropTarget2.enterEvents, 1);
QCOMPARE(dropTarget2.leaveEvents, 1);
// Go around again and verify no events if active is false.
evaluate<void>(item, "Drag.active = false");
item->setParentItem(window1.contentItem());
QCoreApplication::processEvents();
item->setParentItem(&dropTarget1);
QCoreApplication::processEvents();
item->setParentItem(window2.contentItem());
QCoreApplication::processEvents();
item->setParentItem(nullptr);
QCoreApplication::processEvents();
QCOMPARE(dropTarget1.enterEvents, 1);
QCOMPARE(dropTarget1.moveEvents, 1);
QCOMPARE(dropTarget1.leaveEvents, 1);
QCOMPARE(dropTarget2.enterEvents, 1);
QCOMPARE(dropTarget2.leaveEvents, 1);
}
void tst_QQuickDrag::hotSpot()
{
QQuickWindow window;
TestDropTarget dropTarget(window.contentItem());
dropTarget.setSize(QSizeF(100, 100));
QQmlComponent component(&engine);
component.setData(
"import QtQuick 2.0\n"
"Item {\n"
"property real hotSpotX: Drag.hotSpot.x\n"
"property real hotSpotY: Drag.hotSpot.y\n"
"x: 50; y: 50\n"
"width: 10; height: 10\n"
"}", QUrl());
QScopedPointer<QObject> object(component.create());
QQuickItem *item = qobject_cast<QQuickItem *>(object.data());
QVERIFY(item);
item->setParentItem(&dropTarget);
QCOMPARE(evaluate<qreal>(item, "Drag.hotSpot.x"), qreal(0));
QCOMPARE(evaluate<qreal>(item, "Drag.hotSpot.y"), qreal(0));
QCOMPARE(evaluate<qreal>(item, "hotSpotX"), qreal(0));
QCOMPARE(evaluate<qreal>(item, "hotSpotY"), qreal(0));
evaluate<void>(item, "{ Drag.start(); Drag.cancel() }");
QCOMPARE(dropTarget.position.x(), qreal(50));
QCOMPARE(dropTarget.position.y(), qreal(50));
evaluate<void>(item, "{ Drag.hotSpot.x = 5, Drag.hotSpot.y = 5 }");
QCOMPARE(evaluate<qreal>(item, "Drag.hotSpot.x"), qreal(5));
QCOMPARE(evaluate<qreal>(item, "Drag.hotSpot.y"), qreal(5));
QCOMPARE(evaluate<qreal>(item, "hotSpotX"), qreal(5));
QCOMPARE(evaluate<qreal>(item, "hotSpotY"), qreal(5));
evaluate<void>(item, "Drag.start()");
QCOMPARE(dropTarget.position.x(), qreal(55));
QCOMPARE(dropTarget.position.y(), qreal(55));
item->setPosition(QPointF(30, 20));
QCoreApplication::processEvents();
QCOMPARE(dropTarget.position.x(), qreal(35));
QCOMPARE(dropTarget.position.y(), qreal(25));
evaluate<void>(item, "{ Drag.hotSpot.x = 10; Drag.hotSpot.y = 10 }");
QCOMPARE(evaluate<qreal>(item, "Drag.hotSpot.x"), qreal(10));
QCOMPARE(evaluate<qreal>(item, "Drag.hotSpot.y"), qreal(10));
QCOMPARE(evaluate<qreal>(item, "hotSpotX"), qreal(10));
QCOMPARE(evaluate<qreal>(item, "hotSpotY"), qreal(10));
// Setting the hotSpot will deliver a move event in the event loop.
QCOMPARE(dropTarget.position.x(), qreal(35));
QCOMPARE(dropTarget.position.y(), qreal(25));
QCoreApplication::processEvents();
QCOMPARE(dropTarget.position.x(), qreal(40));
QCOMPARE(dropTarget.position.y(), qreal(30));
item->setPosition(QPointF(10, 20));
QCoreApplication::processEvents();
QCOMPARE(dropTarget.position.x(), qreal(20));
QCOMPARE(dropTarget.position.y(), qreal(30));
evaluate<void>(item, "{ Drag.hotSpot.x = 10; Drag.hotSpot.y = 10 }");
}
void tst_QQuickDrag::supportedActions()
{
QQuickWindow window;
TestDropTarget dropTarget(window.contentItem());
dropTarget.setSize(QSizeF(100, 100));
QQmlComponent component(&engine);
component.setData(
"import QtQuick 2.0\n"
"Item {\n"
"property int supportedActions: Drag.supportedActions\n"
"x: 50; y: 50\n"
"width: 10; height: 10\n"
"}", QUrl());
QScopedPointer<QObject> object(component.create());
QQuickItem *item = qobject_cast<QQuickItem *>(object.data());
QVERIFY(item);
item->setParentItem(&dropTarget);
QCOMPARE(evaluate<bool>(item, "Drag.supportedActions == Qt.CopyAction | Qt.MoveAction | Qt.LinkAction"), true);
QCOMPARE(evaluate<bool>(item, "supportedActions == Qt.CopyAction | Qt.MoveAction | Qt.LinkAction"), true);
evaluate<void>(item, "{ Drag.start(); Drag.cancel() }");
QCOMPARE(dropTarget.supportedActions, Qt::CopyAction | Qt::MoveAction | Qt::LinkAction);
dropTarget.reset();
evaluate<void>(item, "Drag.supportedActions = Qt.CopyAction | Qt.MoveAction");
QCOMPARE(evaluate<bool>(item, "Drag.supportedActions == Qt.CopyAction | Qt.MoveAction"), true);
QCOMPARE(evaluate<bool>(item, "supportedActions == Qt.CopyAction | Qt.MoveAction"), true);
evaluate<void>(item, "Drag.start()");
QCOMPARE(dropTarget.supportedActions, Qt::CopyAction | Qt::MoveAction);
QCOMPARE(dropTarget.leaveEvents, 0);
QCOMPARE(dropTarget.enterEvents, 1);
// Changing the supported actions will restart the drag, after a delay to avoid any
// recursion.
evaluate<void>(item, "Drag.supportedActions = Qt.MoveAction");
QCOMPARE(evaluate<bool>(item, "Drag.supportedActions == Qt.MoveAction"), true);
QCOMPARE(evaluate<bool>(item, "supportedActions == Qt.MoveAction"), true);
item->setPosition(QPointF(60, 60));
QCOMPARE(dropTarget.supportedActions, Qt::CopyAction | Qt::MoveAction);
QCOMPARE(dropTarget.leaveEvents, 0);
QCOMPARE(dropTarget.enterEvents, 1);
QCoreApplication::processEvents();
QCOMPARE(dropTarget.supportedActions, Qt::MoveAction);
QCOMPARE(dropTarget.leaveEvents, 1);
QCOMPARE(dropTarget.enterEvents, 2);
// Calling start with proposed actions will override the current actions for the next sequence.
evaluate<void>(item, "Drag.start(Qt.CopyAction)");
QCOMPARE(evaluate<bool>(item, "Drag.supportedActions == Qt.MoveAction"), true);
QCOMPARE(evaluate<bool>(item, "supportedActions == Qt.MoveAction"), true);
QCOMPARE(dropTarget.supportedActions, Qt::CopyAction);
evaluate<void>(item, "Drag.start()");
QCOMPARE(evaluate<bool>(item, "Drag.supportedActions == Qt.MoveAction"), true);
QCOMPARE(evaluate<bool>(item, "supportedActions == Qt.MoveAction"), true);
QCOMPARE(dropTarget.supportedActions, Qt::MoveAction);
}
void tst_QQuickDrag::proposedAction()
{
QQuickWindow window;
TestDropTarget dropTarget(window.contentItem());
dropTarget.setSize(QSizeF(100, 100));
QQmlComponent component(&engine);
component.setData(
"import QtQuick 2.0\n"
"Item {\n"
"property int proposedAction: Drag.proposedAction\n"
"x: 50; y: 50\n"
"width: 10; height: 10\n"
"}", QUrl());
QScopedPointer<QObject> object(component.create());
QQuickItem *item = qobject_cast<QQuickItem *>(object.data());
QVERIFY(item);
item->setParentItem(&dropTarget);
QCOMPARE(evaluate<bool>(item, "Drag.proposedAction == Qt.MoveAction"), true);
QCOMPARE(evaluate<bool>(item, "proposedAction == Qt.MoveAction"), true);
evaluate<void>(item, "{ Drag.start(); Drag.cancel() }");
QCOMPARE(dropTarget.defaultAction, Qt::MoveAction);
QCOMPARE(dropTarget.proposedAction, Qt::MoveAction);
evaluate<void>(item, "Drag.proposedAction = Qt.CopyAction");
QCOMPARE(evaluate<bool>(item, "Drag.proposedAction == Qt.CopyAction"), true);
QCOMPARE(evaluate<bool>(item, "proposedAction == Qt.CopyAction"), true);
evaluate<void>(item, "Drag.start()");
QCOMPARE(dropTarget.defaultAction, Qt::CopyAction);
QCOMPARE(dropTarget.proposedAction, Qt::CopyAction);
// The proposed action can change during a drag.
evaluate<void>(item, "Drag.proposedAction = Qt.MoveAction");
QCOMPARE(evaluate<bool>(item, "Drag.proposedAction == Qt.MoveAction"), true);
QCOMPARE(evaluate<bool>(item, "proposedAction == Qt.MoveAction"), true);
QCoreApplication::processEvents();
QCOMPARE(dropTarget.defaultAction, Qt::MoveAction);
QCOMPARE(dropTarget.proposedAction, Qt::MoveAction);
evaluate<void>(item, "Drag.proposedAction = Qt.LinkAction");
QCOMPARE(evaluate<bool>(item, "Drag.proposedAction == Qt.LinkAction"), true);
QCOMPARE(evaluate<bool>(item, "proposedAction == Qt.LinkAction"), true);
evaluate<void>(item, "Drag.drop()");
QCOMPARE(dropTarget.defaultAction, Qt::LinkAction);
QCOMPARE(dropTarget.proposedAction, Qt::LinkAction);
}
void tst_QQuickDrag::keys()
{
QQmlComponent component(&engine);
component.setData(
"import QtQuick 2.0\n"
"Item {\n"
"property variant keys: Drag.keys\n"
"x: 50; y: 50\n"
"width: 10; height: 10\n"
"}", QUrl());
QScopedPointer<QObject> object(component.create());
QQuickItem *item = qobject_cast<QQuickItem *>(object.data());
QVERIFY(item);
QCOMPARE(evaluate<QStringList>(item, "Drag.keys"), QStringList());
QCOMPARE(evaluate<QStringList>(item, "keys"), QStringList());
QCOMPARE(item->property("keys").toStringList(), QStringList());
evaluate<void>(item, "Drag.keys = [\"red\", \"blue\"]");
QCOMPARE(evaluate<QStringList>(item, "Drag.keys"), QStringList() << "red" << "blue");
QCOMPARE(evaluate<QStringList>(item, "keys"), QStringList() << "red" << "blue");
QCOMPARE(item->property("keys").toStringList(), QStringList() << "red" << "blue");
// Test changing the keys restarts a drag.
QQuickWindow window;
item->setParentItem(window.contentItem());
TestDropTarget dropTarget(window.contentItem());
dropTarget.setSize(QSizeF(100, 100));
evaluate<void>(item, "Drag.start()");
QCOMPARE(dropTarget.leaveEvents, 0);
QCOMPARE(dropTarget.enterEvents, 1);
evaluate<void>(item, "Drag.keys = [\"green\"]");
QCOMPARE(dropTarget.leaveEvents, 0);
QCOMPARE(dropTarget.enterEvents, 1);
QCoreApplication::processEvents();
QCOMPARE(dropTarget.leaveEvents, 1);
QCOMPARE(dropTarget.enterEvents, 2);
}
void tst_QQuickDrag::source()
{
QQmlComponent component(&engine);
component.setData(
"import QtQuick 2.0\n"
"Item {\n"
"property Item source: Drag.source\n"
"x: 50; y: 50\n"
"width: 10; height: 10\n"
"Item { id: proxySource; objectName: \"proxySource\" }\n"
"}", QUrl());
QScopedPointer<QObject> object(component.create());
QQuickItem *item = qobject_cast<QQuickItem *>(object.data());
QVERIFY(item);
QCOMPARE(evaluate<QObject *>(item, "Drag.source"), static_cast<QObject *>(item));
QCOMPARE(evaluate<QObject *>(item, "source"), static_cast<QObject *>(item));
QQuickItem *proxySource = item->findChild<QQuickItem *>("proxySource");
QVERIFY(proxySource);
evaluate<void>(item, "Drag.source = proxySource");
QCOMPARE(evaluate<QObject *>(item, "Drag.source"), static_cast<QObject *>(proxySource));
QCOMPARE(evaluate<QObject *>(item, "source"), static_cast<QObject *>(proxySource));
evaluate<void>(item, "Drag.source = undefined");
QCOMPARE(evaluate<QObject *>(item, "Drag.source"), static_cast<QObject *>(item));
QCOMPARE(evaluate<QObject *>(item, "source"), static_cast<QObject *>(item));
// Test changing the source restarts a drag.
QQuickWindow window;
item->setParentItem(window.contentItem());
TestDropTarget dropTarget(window.contentItem());
dropTarget.setSize(QSizeF(100, 100));
evaluate<void>(item, "Drag.start()");
QCOMPARE(dropTarget.leaveEvents, 0);
QCOMPARE(dropTarget.enterEvents, 1);
evaluate<void>(item, "Drag.source = proxySource");
QCOMPARE(dropTarget.leaveEvents, 0);
QCOMPARE(dropTarget.enterEvents, 1);
QCoreApplication::processEvents();
QCOMPARE(dropTarget.leaveEvents, 1);
QCOMPARE(dropTarget.enterEvents, 2);
}
class RecursingDropTarget : public TestDropTarget
{
public:
RecursingDropTarget(const QString &script, int type, QQuickItem *parent)
: TestDropTarget(parent), script(script), type(type), item(nullptr) {}
void setItem(QQuickItem *i) { item = i; }
protected:
void dragEnterEvent(QDragEnterEvent *event)
{
TestDropTarget::dragEnterEvent(event);
if (type == QEvent::DragEnter && enterEvents < 2)
evaluate<void>(item, script);
}
void dragMoveEvent(QDragMoveEvent *event)
{
TestDropTarget::dragMoveEvent(event);
if (type == QEvent::DragMove && moveEvents < 2)
evaluate<void>(item, script);
}
void dragLeaveEvent(QDragLeaveEvent *event)
{
TestDropTarget::dragLeaveEvent(event);
if (type == QEvent::DragLeave && leaveEvents < 2)
evaluate<void>(item, script);
}
void dropEvent(QDropEvent *event)
{
TestDropTarget::dropEvent(event);
if (type == QEvent::Drop && dropEvents < 2)
evaluate<void>(item, script);
}
private:
QString script;
int type;
QQuickItem *item;
};
void tst_QQuickDrag::recursion_data()
{
QTest::addColumn<QString>("script");
QTest::addColumn<int>("type");
QTest::addColumn<int>("moveEvents");
QTest::addColumn<QRegularExpression>("warning");
QTest::newRow("Drag.start() in Enter")
<< QString("Drag.start()")
<< int(QEvent::DragEnter)
<< 1
<< QRegularExpression(".*start\\(\\) cannot be called from within a drag event handler");
QTest::newRow("Drag.cancel() in Enter")
<< QString("Drag.cancel()")
<< int(QEvent::DragEnter)
<< 1
<< QRegularExpression(".*cancel\\(\\) cannot be called from within a drag event handler");
QTest::newRow("Drag.drop() in Enter")
<< QString("Drag.drop()")
<< int(QEvent::DragEnter)
<< 1
<< QRegularExpression(".*drop\\(\\) cannot be called from within a drag event handler");
QTest::newRow("Drag.active = true in Enter")
<< QString("Drag.active = true")
<< int(QEvent::DragEnter)
<< 1
<< QRegularExpression();
QTest::newRow("Drag.active = false in Enter")
<< QString("Drag.active = false")
<< int(QEvent::DragEnter)
<< 1
<< QRegularExpression(".*active cannot be changed from within a drag event handler");
QTest::newRow("move in Enter")
<< QString("x = 23")
<< int(QEvent::DragEnter)
<< 1
<< QRegularExpression();
QTest::newRow("Drag.start() in Move")
<< QString("Drag.start()")
<< int(QEvent::DragMove)
<< 1
<< QRegularExpression(".*start\\(\\) cannot be called from within a drag event handler");
QTest::newRow("Drag.cancel() in Move")
<< QString("Drag.cancel()")
<< int(QEvent::DragMove)
<< 1
<< QRegularExpression(".*cancel\\(\\) cannot be called from within a drag event handler");
QTest::newRow("Drag.drop() in Move")
<< QString("Drag.drop()")
<< int(QEvent::DragMove)
<< 1
<< QRegularExpression(".*drop\\(\\) cannot be called from within a drag event handler");
QTest::newRow("Drag.active = true in Move")
<< QString("Drag.active = true")
<< int(QEvent::DragMove)
<< 1
<< QRegularExpression();
QTest::newRow("Drag.active = false in Move")
<< QString("Drag.active = false")
<< int(QEvent::DragMove)
<< 1
<< QRegularExpression(".*active cannot be changed from within a drag event handler");
QTest::newRow("move in Move")
<< QString("x = 23")
<< int(QEvent::DragMove)
<< 2
<< QRegularExpression();
QTest::newRow("Drag.start() in Leave")
<< QString("Drag.start()")
<< int(QEvent::DragLeave)
<< 1
<< QRegularExpression(".*start\\(\\) cannot be called from within a drag event handler");
QTest::newRow("Drag.cancel() in Leave")
<< QString("Drag.cancel()")
<< int(QEvent::DragLeave)
<< 1
<< QRegularExpression(".*cancel\\(\\) cannot be called from within a drag event handler");
QTest::newRow("Drag.drop() in Leave")
<< QString("Drag.drop()")
<< int(QEvent::DragLeave)
<< 1
<< QRegularExpression(".*drop\\(\\) cannot be called from within a drag event handler");
QTest::newRow("Drag.active = true in Leave")
<< QString("Drag.active = true")
<< int(QEvent::DragLeave)
<< 1
<< QRegularExpression(".*active cannot be changed from within a drag event handler");
QTest::newRow("Drag.active = false in Leave")
<< QString("Drag.active = false")
<< int(QEvent::DragLeave)
<< 1
<< QRegularExpression();
QTest::newRow("move in Leave")
<< QString("x = 23")
<< int(QEvent::DragLeave)
<< 1
<< QRegularExpression();
QTest::newRow("Drag.start() in Drop")
<< QString("Drag.start()")
<< int(QEvent::Drop)
<< 1
<< QRegularExpression(".*start\\(\\) cannot be called from within a drag event handler");
QTest::newRow("Drag.cancel() in Drop")
<< QString("Drag.cancel()")
<< int(QEvent::Drop)
<< 1
<< QRegularExpression(".*cancel\\(\\) cannot be called from within a drag event handler");
QTest::newRow("Drag.drop() in Drop")
<< QString("Drag.drop()")
<< int(QEvent::Drop)
<< 1
<< QRegularExpression(".*drop\\(\\) cannot be called from within a drag event handler");
QTest::newRow("Drag.active = true in Drop")
<< QString("Drag.active = true")
<< int(QEvent::Drop)
<< 1
<< QRegularExpression(".*active cannot be changed from within a drag event handler");
QTest::newRow("Drag.active = false in Drop")
<< QString("Drag.active = false")
<< int(QEvent::Drop)
<< 1
<< QRegularExpression();
QTest::newRow("move in Drop")
<< QString("x = 23")
<< int(QEvent::Drop)
<< 1
<< QRegularExpression();
}
void tst_QQuickDrag::recursion()
{
QFETCH(QString, script);
QFETCH(int, type);
QFETCH(int, moveEvents);
QFETCH(QRegularExpression, warning);
if (!warning.pattern().isEmpty())
QTest::ignoreMessage(QtWarningMsg, warning);
QQuickWindow window;
RecursingDropTarget dropTarget(script, type, window.contentItem());
dropTarget.setSize(QSizeF(100, 100));
QQmlComponent component(&engine);
component.setData(
"import QtQuick 2.0\n"
"Item {\n"
"x: 50; y: 50\n"
"width: 10; height: 10\n"
"}", QUrl());
QScopedPointer<QObject> object(component.create());
QQuickItem *item = qobject_cast<QQuickItem *>(object.data());
QVERIFY(item);
item->setParentItem(window.contentItem());
dropTarget.setItem(item);
evaluate<void>(item, "Drag.start()");
QCOMPARE(dropTarget.enterEvents, 1);
QCOMPARE(dropTarget.moveEvents, 0);
QCOMPARE(dropTarget.dropEvents, 0);
QCOMPARE(dropTarget.leaveEvents, 0);
evaluate<void>(item, "y = 15");
// the evaluate statement above, y = 15, will cause
// QQuickItem::setY(15) to be called, leading to an
// event being posted that will be delivered
// to RecursingDropTarget::dragMoveEvent(), hence
// the following call to QCoreApplication::processEvents()
QCoreApplication::processEvents();
// Regarding 'move in Move' when
// RecursingDropTarget::dragMoveEvent() runs,
// its call 'evaluate' triggers a second
// move event (x = 23) that needs to be delivered.
QCoreApplication::processEvents();
QCOMPARE(dropTarget.enterEvents, 1);
QCOMPARE(dropTarget.moveEvents, moveEvents);
QCOMPARE(dropTarget.dropEvents, 0);
QCOMPARE(dropTarget.leaveEvents, 0);
if (type == QEvent::Drop) {
QCOMPARE(evaluate<bool>(item, "Drag.drop() == Qt.MoveAction"), true);
QCOMPARE(dropTarget.enterEvents, 1);
QCOMPARE(dropTarget.moveEvents, moveEvents);
QCOMPARE(dropTarget.dropEvents, 1);
QCOMPARE(dropTarget.leaveEvents, 0);
} else {
evaluate<void>(item, "Drag.cancel()");
QCOMPARE(dropTarget.enterEvents, 1);
QCOMPARE(dropTarget.moveEvents, moveEvents);
QCOMPARE(dropTarget.dropEvents, 0);
QCOMPARE(dropTarget.leaveEvents, 1);
}
}
void tst_QQuickDrag::noCrashWithImageProvider()
{
// QTBUG-72045
QQmlComponent component(&engine);
component.setData(
R"(
import QtQuick 2.9
Item {
Rectangle {
id: item
width: 50
height: 50
anchors.centerIn: parent
color: "orange"
Component.onCompleted: {
item.Drag.imageSource = "image://kill/me"
}
}
})", QUrl());
QScopedPointer<QObject> object(component.create());
QQuickItem *item = qobject_cast<QQuickItem *>(object.data());
QVERIFY(item);
}
QTEST_MAIN(tst_QQuickDrag)
#include "tst_qquickdrag.moc"