/**************************************************************************** ** ** Copyright (C) 2018 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtQml 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 #include #include #include class tst_QWidgetsInQml : public QObject { Q_OBJECT public: tst_QWidgetsInQml(); private slots: void instantiateWidget(); void instantiateWidgetWithoutParentWidget(); void widgetAsDefaultPropertyCollected(); void widgetAsDefaultPropertyKept(); void widgetAsDefaultPropertyKeptDuringCreation(); }; static void gc(QQmlEngine &engine) { engine.collectGarbage(); QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete); QCoreApplication::processEvents(); } // Like QtObject, but with default property class QObjectContainer : public QObject { Q_OBJECT Q_CLASSINFO("DefaultProperty", "data"); Q_PROPERTY(QQmlListProperty data READ data DESIGNABLE false); public: QObjectContainer() : widgetParent(0) , gcOnAppend(false) {} QQmlListProperty data() { return QQmlListProperty(this, 0, children_append, children_count, children_at, children_clear); } static void children_append(QQmlListProperty *prop, QObject *o) { QObjectContainer *that = static_cast(prop->object); that->dataChildren.append(o); QObject::connect(o, SIGNAL(destroyed(QObject*)), prop->object, SLOT(childDestroyed(QObject*))); QWidget *widget = qobject_cast(o); if (widget && that->widgetParent) widget->setParent(that->widgetParent); if (that->gcOnAppend) { QQmlEngine *engine = qmlEngine(that); gc(*engine); } } static int children_count(QQmlListProperty *prop) { return static_cast(prop->object)->dataChildren.count(); } static QObject *children_at(QQmlListProperty *prop, int index) { return static_cast(prop->object)->dataChildren.at(index); } static void children_clear(QQmlListProperty *prop) { QObjectContainer *that = static_cast(prop->object); foreach (QObject *c, that->dataChildren) QObject::disconnect(c, SIGNAL(destroyed(QObject*)), that, SLOT(childDestroyed(QObject*))); that->dataChildren.clear(); } QList dataChildren; QWidget *widgetParent; bool gcOnAppend; protected slots: void childDestroyed(QObject *child) { dataChildren.removeAll(child); } }; class QWidgetContainer : public QObjectContainer { Q_OBJECT public: QWidgetContainer() { widgetParent = new QWidget; QQmlEngine::setObjectOwnership(widgetParent, QQmlEngine::CppOwnership); } virtual ~QWidgetContainer() { delete widgetParent; widgetParent = 0; } }; class QObjectContainerWithGCOnAppend : public QObjectContainer { Q_OBJECT public: QObjectContainerWithGCOnAppend() { gcOnAppend = true; } }; tst_QWidgetsInQml::tst_QWidgetsInQml() { qmlRegisterType("Qt.Widgets", 1, 0, "QWidget"); qmlRegisterType("Qt.Widgets", 1, 0, "QObjectContainer"); qmlRegisterType("Qt.Widgets", 1, 0, "QWidgetContainer"); qmlRegisterType("Qt.Widgets", 1, 0, "QObjectContainerWithGCOnAppend"); } void tst_QWidgetsInQml::instantiateWidget() { QQmlEngine engine; QQmlComponent component(&engine); component.setData("import Qt.Widgets 1.0;\nQWidget { property QWidget child: QWidget { objectName: 'child' } }", QUrl()); QScopedPointer object(component.create()); QVERIFY(!object.isNull()); QWidget *rootWidget = qobject_cast(object.data()); QVERIFY(rootWidget != 0); QCOMPARE(rootWidget->children().count(), 1); QWidget *firstChildWidget = qobject_cast(rootWidget->children().first()); QVERIFY(firstChildWidget != 0); QWidget *widgetProperty = qvariant_cast(object->property("child")); QVERIFY(widgetProperty != 0); QCOMPARE(firstChildWidget, widgetProperty); QCOMPARE(firstChildWidget->objectName(), QString("child")); } void tst_QWidgetsInQml::instantiateWidgetWithoutParentWidget() { QQmlEngine engine; QQmlComponent component(&engine); component.setData("import Qt.Widgets 1.0;\n" "import QtQml 2.0;\n" "QtObject { property QtObject child: QWidget { objectName: 'child' } }", QUrl()); QScopedPointer object(component.create()); QVERIFY(!object.isNull()); QPointer widgetProperty = qvariant_cast(object->property("child")); QVERIFY(!widgetProperty.isNull()); QCOMPARE(widgetProperty->objectName(), QString("child")); QVERIFY(!widgetProperty->parent()); gc(engine); // Don't collect, the property reference should keep it alive QVERIFY(!widgetProperty.isNull()); } void tst_QWidgetsInQml::widgetAsDefaultPropertyCollected() { QQmlEngine engine; QQmlComponent component(&engine); component.setData("import Qt.Widgets 1.0;\n" "import QtQml 2.0;\n" "QObjectContainer {\n" " QWidget {\n" " id: parentLessChild;\n" " objectName: 'child'\n" " }\n" " property var widgetHolder;\n" " Component.onCompleted: {\n" " widgetHolder = parentLessChild;\n" " }\n" "}", QUrl()); QScopedPointer object(component.create()); QVERIFY(!object.isNull()); QObjectContainer *container = qobject_cast(object.data()); QCOMPARE(container->dataChildren.count(), 1); QJSValue holder = qvariant_cast(object->property("widgetHolder")); QVERIFY(!holder.isNull()); gc(engine); QCOMPARE(container->dataChildren.count(), 1); holder = QJSValue(); object->setProperty("widgetHolder", QVariant::fromValue(holder)); gc(engine); // The QWidget is without a parent and nobody is referencing it anymore (the children // list in QObjectContainer is weak!), so it should get collected. QCOMPARE(container->dataChildren.count(), 0); } void tst_QWidgetsInQml::widgetAsDefaultPropertyKept() { QQmlEngine engine; QQmlComponent component(&engine); component.setData("import Qt.Widgets 1.0;\n" "import QtQml 2.0;\n" "QWidgetContainer {\n" " QWidget {\n" " id: parentLessChild;\n" " objectName: 'child'\n" " }\n" " property var widgetHolder;\n" " Component.onCompleted: {\n" " widgetHolder = parentLessChild;\n" " }\n" "}", QUrl()); QScopedPointer object(component.create()); QVERIFY(!object.isNull()); QWidgetContainer *container = qobject_cast(object.data()); QCOMPARE(container->dataChildren.count(), 1); QJSValue holder = qvariant_cast(object->property("widgetHolder")); QVERIFY(!holder.isNull()); gc(engine); QCOMPARE(container->dataChildren.count(), 1); holder = QJSValue(); object->setProperty("widgetHolder", QVariant::fromValue(holder)); gc(engine); QCOMPARE(container->dataChildren.count(), 1); } void tst_QWidgetsInQml::widgetAsDefaultPropertyKeptDuringCreation() { QQmlEngine engine; QQmlComponent component(&engine); component.setData("import Qt.Widgets 1.0;\n" "import QtQml 2.0;\n" "QObjectContainerWithGCOnAppend {\n" " QWidget {\n" " id: parentLessChild;\n" " objectName: 'child'\n" " property var blah;\n" // Ensures that we have a JS wrapper " }\n" "}", QUrl()); QScopedPointer object(component.create()); QVERIFY(!object.isNull()); QObjectContainer *container = qobject_cast(object.data()); QCOMPARE(container->dataChildren.count(), 1); gc(engine); QCOMPARE(container->dataChildren.count(), 0); } QTEST_MAIN(tst_QWidgetsInQml) #include "tst_qwidgetsinqml.moc"