qt3d/tests/auto/render/qobjectpicker/tst_qobjectpicker.cpp

262 lines
8.1 KiB
C++
Raw Normal View History

/****************************************************************************
**
** Copyright (C) 2015 Klaralvdalens Datakonsult AB (KDAB).
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt3D module 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/QTest>
#include <QtTest/QSignalSpy>
#include <Qt3DCore/private/qnode_p.h>
#include <Qt3DCore/private/qscene_p.h>
#include <Qt3DRender/QObjectPicker>
#include <Qt3DRender/private/qobjectpicker_p.h>
#include <Qt3DRender/QPickEvent>
#include <Qt3DCore/private/qnodecreatedchangegenerator_p.h>
#include <Qt3DCore/qnodecreatedchange.h>
#include "testpostmanarbiter.h"
class MyObjectPicker : public Qt3DRender::QObjectPicker
{
Q_OBJECT
public:
MyObjectPicker(Qt3DCore::QNode *parent = nullptr)
: Qt3DRender::QObjectPicker(parent)
{}
void sceneChangeEvent(const Qt3DCore::QSceneChangePtr &change) final
{
Qt3DRender::QObjectPicker::sceneChangeEvent(change);
}
private:
friend class tst_ObjectPicker;
};
// We need to call QNode::clone which is protected
// So we sublcass QNode instead of QObject
class tst_QObjectPicker : public Qt3DCore::QNode
{
Q_OBJECT
public:
tst_QObjectPicker()
{
qRegisterMetaType<Qt3DRender::QPickEvent*>("Qt3DRender::QPickEvent*");
}
~tst_QObjectPicker()
{
QMetaObject::invokeMethod(this, "_q_cleanup", Qt::DirectConnection);
}
private Q_SLOTS:
void checkInitialState()
{
// GIVEN
Qt3DRender::QObjectPicker picker;
// THEN
QCOMPARE(picker.priority(), 0);
QCOMPARE(picker.isDragEnabled(), false);
QCOMPARE(picker.isHoverEnabled(), false);
}
void checkCreationData()
{
// GIVEN
Qt3DRender::QObjectPicker picker;
picker.setPriority(1584);
picker.setDragEnabled(true);
picker.setHoverEnabled(true);
// WHEN
QVector<Qt3DCore::QNodeCreatedChangeBasePtr> creationChanges;
{
Qt3DCore::QNodeCreatedChangeGenerator creationChangeGenerator(&picker);
creationChanges = creationChangeGenerator.creationChanges();
}
// THEN
{
QCOMPARE(creationChanges.size(), 1);
const auto creationChangeData = qSharedPointerCast<Qt3DCore::QNodeCreatedChange<Qt3DRender::QObjectPickerData>>(creationChanges.first());
const Qt3DRender::QObjectPickerData cloneData = creationChangeData->data;
QCOMPARE(cloneData.priority, 1584);
QCOMPARE(cloneData.hoverEnabled, true);
QCOMPARE(cloneData.dragEnabled, true);
QCOMPARE(picker.id(), creationChangeData->subjectId());
QCOMPARE(picker.isEnabled(), true);
QCOMPARE(picker.isEnabled(), creationChangeData->isNodeEnabled());
QCOMPARE(picker.metaObject(), creationChangeData->metaObject());
}
// WHEN
picker.setEnabled(false);
{
Qt3DCore::QNodeCreatedChangeGenerator creationChangeGenerator(&picker);
creationChanges = creationChangeGenerator.creationChanges();
}
// THEN
{
QCOMPARE(creationChanges.size(), 1);
const auto creationChangeData = qSharedPointerCast<Qt3DCore::QNodeCreatedChange<Qt3DRender::QObjectPickerData>>(creationChanges.first());
const Qt3DRender::QObjectPickerData cloneData = creationChangeData->data;
QCOMPARE(cloneData.priority, 1584);
QCOMPARE(cloneData.hoverEnabled, true);
QCOMPARE(cloneData.dragEnabled, true);
QCOMPARE(picker.id(), creationChangeData->subjectId());
QCOMPARE(picker.isEnabled(), false);
QCOMPARE(picker.isEnabled(), creationChangeData->isNodeEnabled());
QCOMPARE(picker.metaObject(), creationChangeData->metaObject());
}
}
void checkPropertyUpdate()
{
// GIVEN
TestArbiter arbiter;
Qt3DRender::QObjectPicker picker;
arbiter.setArbiterOnNode(&picker);
{
{
// WHEN
picker.setPriority(883);
QCoreApplication::processEvents();
// THEN
QCOMPARE(arbiter.events.size(), 0);
QCOMPARE(arbiter.dirtyNodes.size(), 1);
QCOMPARE(arbiter.dirtyNodes.front(), &picker);
arbiter.dirtyNodes.clear();
}
{
// WHEN
picker.setPriority(883);
QCoreApplication::processEvents();
// THEN
QCOMPARE(arbiter.events.size(), 0);
QCOMPARE(arbiter.dirtyNodes.size(), 0);
}
}
{
{
// WHEN
picker.setDragEnabled(true);
QCoreApplication::processEvents();
// THEN
QCOMPARE(arbiter.events.size(), 0);
QCOMPARE(arbiter.dirtyNodes.size(), 1);
QCOMPARE(arbiter.dirtyNodes.front(), &picker);
arbiter.dirtyNodes.clear();
}
{
// WHEN
picker.setDragEnabled(true);
QCoreApplication::processEvents();
// THEN
QCOMPARE(arbiter.events.size(), 0);
QCOMPARE(arbiter.dirtyNodes.size(), 0);
}
}
{
{
// WHEN
picker.setHoverEnabled(true);
QCoreApplication::processEvents();
// THEN
QCOMPARE(arbiter.events.size(), 0);
QCOMPARE(arbiter.dirtyNodes.size(), 1);
QCOMPARE(arbiter.dirtyNodes.front(), &picker);
arbiter.dirtyNodes.clear();
}
{
// WHEN
picker.setHoverEnabled(true);
QCoreApplication::processEvents();
// THEN
QCOMPARE(arbiter.events.size(), 0);
QCOMPARE(arbiter.dirtyNodes.size(), 0);
}
}
}
void checkCloning_data()
{
QTest::addColumn<Qt3DRender::QObjectPicker *>("objectPicker");
Qt3DRender::QObjectPicker *objectPicker = new Qt3DRender::QObjectPicker();
QTest::newRow("empty objectPicker") << objectPicker;
objectPicker = new Qt3DRender::QObjectPicker();
objectPicker->setHoverEnabled(true);
QTest::newRow("objectPicker_all_true") << objectPicker;
}
void checkPropertyUpdates()
{
// GIVEN
Avoid crash in QML app shutdown and actually send events in C++ app The logic was such that in a C++ application the QNode::setParent() function would bail out early in a C++ application when called from the destructor of its parent object. This is because by the time the child is being deleted the parent is in QObjectPrivate::deleteChildren and therefore the QNode part of the object has already been destroyed. This led to the cast in the parentNode() == parent to fail, thereby exiting the functio early and never getting into QNodePrivate::_q_setParentHelper(). In the case of a QML application, the parent has a dynamic metaobject set by the QML engine. This resulted in the cast in QNode::setParent() succeeding and we called into _q_setParentHelper(). The logic in here resulted in a crash when called from a destructor because the child had already been removed from its parent's list of children. Thus when we called QObjectPrivate::setParentHelper(), this function ended up with an index of -1 for the child in its child list (i.e. not found) and it then tried to index into the children list with this index and we then crashed. The solution in this change is to not do the full logic in QNode::setParent() and _q_setParentHelper(). Rather, we simply remove the subtree at this node from the scene and we send node destruction changes to the backend. With this we avoid the crash of QML application shutdowns and we also make sure to correctly send the node destruction changes even in the case of a C++ Qt 3D application. The backend does not yet get an opportunity to process these final changes. This will be addressed in a follow up commit. As a result of these changes many unit tests began crashing. This is because the QNode dtor is now actually doing some work, rather than bailing out of that work early when the parent is no longer a QNode. This work involves mutating the QScene object which in the unit tests did not live longer than the QNode's to which it was associated with. The unit tests have been adjusted to ensure that the arbiter and scene objects remain alive longer than the QNodes they are being used to test. Task-number: QTBUG-42353 Change-Id: I197870f48fca30656bd85c4c51346d93403fba08 Reviewed-by: Kevin Ottens <kevin.ottens@kdab.com>
2016-05-16 17:47:54 +00:00
TestArbiter arbiter;
Qt3DCore::QScene scene;
QScopedPointer<Qt3DRender::QObjectPicker> objectPicker(new Qt3DRender::QObjectPicker());
Avoid crash in QML app shutdown and actually send events in C++ app The logic was such that in a C++ application the QNode::setParent() function would bail out early in a C++ application when called from the destructor of its parent object. This is because by the time the child is being deleted the parent is in QObjectPrivate::deleteChildren and therefore the QNode part of the object has already been destroyed. This led to the cast in the parentNode() == parent to fail, thereby exiting the functio early and never getting into QNodePrivate::_q_setParentHelper(). In the case of a QML application, the parent has a dynamic metaobject set by the QML engine. This resulted in the cast in QNode::setParent() succeeding and we called into _q_setParentHelper(). The logic in here resulted in a crash when called from a destructor because the child had already been removed from its parent's list of children. Thus when we called QObjectPrivate::setParentHelper(), this function ended up with an index of -1 for the child in its child list (i.e. not found) and it then tried to index into the children list with this index and we then crashed. The solution in this change is to not do the full logic in QNode::setParent() and _q_setParentHelper(). Rather, we simply remove the subtree at this node from the scene and we send node destruction changes to the backend. With this we avoid the crash of QML application shutdowns and we also make sure to correctly send the node destruction changes even in the case of a C++ Qt 3D application. The backend does not yet get an opportunity to process these final changes. This will be addressed in a follow up commit. As a result of these changes many unit tests began crashing. This is because the QNode dtor is now actually doing some work, rather than bailing out of that work early when the parent is no longer a QNode. This work involves mutating the QScene object which in the unit tests did not live longer than the QNode's to which it was associated with. The unit tests have been adjusted to ensure that the arbiter and scene objects remain alive longer than the QNodes they are being used to test. Task-number: QTBUG-42353 Change-Id: I197870f48fca30656bd85c4c51346d93403fba08 Reviewed-by: Kevin Ottens <kevin.ottens@kdab.com>
2016-05-16 17:47:54 +00:00
arbiter.setArbiterOnNode(objectPicker.data());
Qt3DCore::QNodePrivate::get(objectPicker.data())->setScene(&scene);
// WHEN
objectPicker->setHoverEnabled(true);
QCoreApplication::processEvents();
// THEN
QCOMPARE(arbiter.events.size(), 0);
QCOMPARE(arbiter.dirtyNodes.size(), 1);
QCOMPARE(arbiter.dirtyNodes.front(), objectPicker.data());
arbiter.dirtyNodes.clear();
}
};
QTEST_MAIN(tst_QObjectPicker)
#include "tst_qobjectpicker.moc"