From 4e012093462f07e7ffd42d4061539c54b4f43ace Mon Sep 17 00:00:00 2001 From: Simon Hausmann Date: Mon, 31 Mar 2014 16:49:14 +0200 Subject: Avoid recompiling of signal handlers defined in QtQuick state changes and Connection objects We can re-use the expression we've compiled at QML type compilation time, as long as we "inject" the signal parameters in the dynamic qml lookup chain. Change-Id: Icc417531c41dea06ff5d033011179af49b03f542 Reviewed-by: Lars Knoll --- src/qml/jsruntime/qv4script.cpp | 25 ++++++++++++++++++++- src/qml/jsruntime/qv4script_p.h | 2 +- src/qml/qml/qqmlbinding.cpp | 4 ++-- src/qml/qml/qqmlboundsignal.cpp | 24 ++++++++++++++++++++ src/qml/qml/qqmlboundsignal_p.h | 2 ++ src/qml/qml/qqmlcompiler_p.h | 3 +++ src/qml/qml/qqmlcustomparser_p.h | 4 +++- src/qml/qml/qqmlexpression.cpp | 2 +- src/qml/qml/qqmlobjectcreator.cpp | 2 +- src/qml/types/qqmlconnections.cpp | 26 +++++++--------------- src/qml/types/qqmlconnections_p.h | 2 +- src/qml/types/qqmllistmodel.cpp | 2 +- src/qml/types/qqmllistmodel_p.h | 2 +- src/quick/util/qquickpropertychanges.cpp | 21 +++++------------ src/quick/util/qquickpropertychanges_p.h | 2 +- .../qml/qqmlconnections/tst_qqmlconnections.cpp | 5 ++--- tests/auto/qml/qqmllanguage/testtypes.cpp | 4 ++-- tests/auto/qml/qqmllanguage/testtypes.h | 8 +++---- 18 files changed, 86 insertions(+), 54 deletions(-) diff --git a/src/qml/jsruntime/qv4script.cpp b/src/qml/jsruntime/qv4script.cpp index e489b99ec5..a25d969059 100644 --- a/src/qml/jsruntime/qv4script.cpp +++ b/src/qml/jsruntime/qv4script.cpp @@ -132,11 +132,34 @@ void QmlBindingWrapper::markObjects(Managed *m, ExecutionEngine *e) wrapper->qmlContext->mark(e); } -Returned *QmlBindingWrapper::createQmlCallableForFunction(ExecutionEngine *engine, QQmlContextData *qmlContext, QObject *scopeObject, Function *runtimeFunction) +static ReturnedValue signalParameterGetter(QV4::CallContext *ctx, uint parameterIndex) { + QV4::CallContext *signalEmittingContext = ctx->parent->asCallContext(); + Q_ASSERT(signalEmittingContext); + return signalEmittingContext->argument(parameterIndex); +} + +Returned *QmlBindingWrapper::createQmlCallableForFunction(QQmlContextData *qmlContext, QObject *scopeObject, Function *runtimeFunction, const QList &signalParameters, QString *error) +{ + ExecutionEngine *engine = QQmlEnginePrivate::getV4Engine(qmlContext->engine); QV4::Scope valueScope(engine); QV4::ScopedObject qmlScopeObject(valueScope, QV4::QmlContextWrapper::qmlScope(engine->v8Engine, qmlContext, scopeObject)); QV4::Scoped wrapper(valueScope, new (engine->memoryManager) QV4::QmlBindingWrapper(engine->rootContext, qmlScopeObject)); + + if (!signalParameters.isEmpty()) { + if (error) + QQmlPropertyCache::signalParameterStringForJS(qmlContext->engine, signalParameters, error); + QV4::ScopedProperty p(valueScope); + QV4::ScopedString s(valueScope); + int index = 0; + foreach (const QByteArray ¶m, signalParameters) { + p->setGetter(new (engine->memoryManager) QV4::IndexedBuiltinFunction(wrapper->context(), index++, signalParameterGetter)); + p->setSetter(0); + s = engine->newString(QString::fromUtf8(param)); + qmlScopeObject->insertMember(s, p, QV4::Attr_Accessor|QV4::Attr_NotEnumerable|QV4::Attr_NotConfigurable); + } + } + QV4::ScopedFunctionObject function(valueScope, QV4::FunctionObject::createScriptFunction(wrapper->context(), runtimeFunction)); return function->asReturned(); } diff --git a/src/qml/jsruntime/qv4script_p.h b/src/qml/jsruntime/qv4script_p.h index 0165778f4b..de582f9674 100644 --- a/src/qml/jsruntime/qv4script_p.h +++ b/src/qml/jsruntime/qv4script_p.h @@ -67,7 +67,7 @@ struct QmlBindingWrapper : FunctionObject { CallContext *context() const { return qmlContext; } - static Returned *createQmlCallableForFunction(QV4::ExecutionEngine *engine, QQmlContextData *qmlContext, QObject *scopeObject, QV4::Function *runtimeFunction); + static Returned *createQmlCallableForFunction(QQmlContextData *qmlContext, QObject *scopeObject, QV4::Function *runtimeFunction, const QList &signalParameters = QList(), QString *error = 0); private: Object *qml; diff --git a/src/qml/qml/qqmlbinding.cpp b/src/qml/qml/qqmlbinding.cpp index e958b97f46..571d78312e 100644 --- a/src/qml/qml/qqmlbinding.cpp +++ b/src/qml/qml/qqmlbinding.cpp @@ -89,7 +89,7 @@ QQmlBinding::createBinding(Identifier id, QObject *obj, QQmlContext *ctxt) QV4::ExecutionEngine *v4 = engine->v4engine(); QV4::Scope valueScope(v4); QV4::Function *runtimeFunction = cdata->compilationUnit->runtimeFunctions[cdata->customParserBindings[id]]; - QV4::ScopedValue function(valueScope, QV4::QmlBindingWrapper::createQmlCallableForFunction(v4, ctxtdata, obj, runtimeFunction)); + QV4::ScopedValue function(valueScope, QV4::QmlBindingWrapper::createQmlCallableForFunction(ctxtdata, obj, runtimeFunction)); rv = new QQmlBinding(function, obj, ctxtdata); } @@ -147,7 +147,7 @@ QQmlBinding::QQmlBinding(const QQmlScriptString &script, QObject *obj, QQmlConte setScopeObject(obj ? obj : scriptPrivate->scope); if (runtimeFunction) { - v4function = QV4::QmlBindingWrapper::createQmlCallableForFunction(engine->v4engine(), ctxtdata, scopeObject(), runtimeFunction); + v4function = QV4::QmlBindingWrapper::createQmlCallableForFunction(ctxtdata, scopeObject(), runtimeFunction); } else { QString code = scriptPrivate->script; v4function = qmlBinding(context(), scopeObject(), code, url, scriptPrivate->lineNumber); diff --git a/src/qml/qml/qqmlboundsignal.cpp b/src/qml/qml/qqmlboundsignal.cpp index b0834d7f21..876f367097 100644 --- a/src/qml/qml/qqmlboundsignal.cpp +++ b/src/qml/qml/qqmlboundsignal.cpp @@ -52,6 +52,7 @@ #include "qqmlglobal_p.h" #include #include +#include #include "qqmlinfo.h" #include @@ -59,6 +60,7 @@ #include #include + QT_BEGIN_NAMESPACE static QQmlJavaScriptExpression::VTable QQmlBoundSignalExpression_jsvtable = { @@ -105,6 +107,28 @@ QQmlBoundSignalExpression::QQmlBoundSignalExpression(QObject *target, int index, init(ctxt, scope); } +QQmlBoundSignalExpression::QQmlBoundSignalExpression(QObject *target, int index, QQmlContextData *ctxt, QObject *scope, QV4::Function *runtimeFunction) + : QQmlJavaScriptExpression(&QQmlBoundSignalExpression_jsvtable), + m_target(target), + m_index(index), + m_extra(0) +{ + setExpressionFunctionValid(true); + setInvalidParameterName(false); + + // It's important to call init first, because m_index gets remapped in case of cloned signals. + init(ctxt, scope); + + QMetaMethod signal = QMetaObjectPrivate::signal(m_target->metaObject(), m_index); + QString error; + m_v8function = QV4::QmlBindingWrapper::createQmlCallableForFunction(ctxt, scope, runtimeFunction, signal.parameterNames(), &error); + if (!error.isEmpty()) { + qmlInfo(scopeObject()) << error; + setInvalidParameterName(true); + } else + setInvalidParameterName(false); +} + void QQmlBoundSignalExpression::init(QQmlContextData *ctxt, QObject *scope) { setNotifyOnValueChanged(false); diff --git a/src/qml/qml/qqmlboundsignal_p.h b/src/qml/qml/qqmlboundsignal_p.h index 054c6d2f80..43ff71f682 100644 --- a/src/qml/qml/qqmlboundsignal_p.h +++ b/src/qml/qml/qqmlboundsignal_p.h @@ -78,6 +78,8 @@ public: QQmlBoundSignalExpression(QObject *target, int index, QQmlContextData *ctxt, QObject *scope, const QV4::ValueRef &function); + QQmlBoundSignalExpression(QObject *target, int index, + QQmlContextData *ctxt, QObject *scope, QV4::Function *runtimeFunction); // "inherited" from QQmlJavaScriptExpression. static QString expressionIdentifier(QQmlJavaScriptExpression *); diff --git a/src/qml/qml/qqmlcompiler_p.h b/src/qml/qml/qqmlcompiler_p.h index 9432811f5b..f3b6f621ce 100644 --- a/src/qml/qml/qqmlcompiler_p.h +++ b/src/qml/qml/qqmlcompiler_p.h @@ -154,6 +154,9 @@ public: bool isInitialized() const { return hasEngine(); } void initialize(QQmlEngine *); + QV4::Function *functionForBindingId(int bindingId) const + { return compilationUnit->runtimeFunctions[customParserBindings[bindingId]]; } + protected: virtual void destroy(); // From QQmlRefCount virtual void clear(); // From QQmlCleanup diff --git a/src/qml/qml/qqmlcustomparser_p.h b/src/qml/qml/qqmlcustomparser_p.h index 4fbe235e21..2ce6375870 100644 --- a/src/qml/qml/qqmlcustomparser_p.h +++ b/src/qml/qml/qqmlcustomparser_p.h @@ -62,6 +62,8 @@ QT_BEGIN_NAMESPACE +class QQmlCompiledData; + struct QQmlCustomParserCompilerBackend { virtual ~QQmlCustomParserCompilerBackend() {} @@ -91,7 +93,7 @@ public: Flags flags() const { return m_flags; } virtual QByteArray compile(const QV4::CompiledData::QmlUnit *qmlUnit, const QList &bindings) = 0; - virtual void setCustomData(QObject *, const QByteArray &)=0; + virtual void setCustomData(QObject *, const QByteArray &, QQmlCompiledData *cdata) = 0; QList errors() const { return exceptions; } diff --git a/src/qml/qml/qqmlexpression.cpp b/src/qml/qml/qqmlexpression.cpp index e3e5dff4d8..4dc3704bbb 100644 --- a/src/qml/qml/qqmlexpression.cpp +++ b/src/qml/qml/qqmlexpression.cpp @@ -81,7 +81,7 @@ void QQmlExpressionPrivate::init(QQmlContextData *ctxt, const QString &expr, QOb void QQmlExpressionPrivate::init(QQmlContextData *ctxt, QV4::Function *runtimeFunction, QObject *me) { expressionFunctionValid = true; - function = QV4::QmlBindingWrapper::createQmlCallableForFunction(QQmlEnginePrivate::getV4Engine(ctxt->engine), ctxt, me, runtimeFunction); + function = QV4::QmlBindingWrapper::createQmlCallableForFunction(ctxt, me, runtimeFunction); QQmlAbstractExpression::setContext(ctxt); setScopeObject(me); diff --git a/src/qml/qml/qqmlobjectcreator.cpp b/src/qml/qml/qqmlobjectcreator.cpp index ff32266588..d0c635b007 100644 --- a/src/qml/qml/qqmlobjectcreator.cpp +++ b/src/qml/qml/qqmlobjectcreator.cpp @@ -1117,7 +1117,7 @@ QObject *QQmlObjectCreator::createInstance(int index, QObject *parent, bool isCo if (customParser) { QHash::ConstIterator entry = compiledData->customParserData.find(index); if (entry != compiledData->customParserData.constEnd()) { - customParser->setCustomData(instance, entry->compilationArtifact); + customParser->setCustomData(instance, entry->compilationArtifact, compiledData); bindingsToSkip = entry->bindings; } } diff --git a/src/qml/types/qqmlconnections.cpp b/src/qml/types/qqmlconnections.cpp index 25ef5db394..99ec0b55de 100644 --- a/src/qml/types/qqmlconnections.cpp +++ b/src/qml/types/qqmlconnections.cpp @@ -46,6 +46,7 @@ #include #include #include +#include #include #include @@ -68,6 +69,7 @@ public: bool componentcomplete; QByteArray data; + QQmlRefPointer cdata; }; /*! @@ -211,8 +213,6 @@ QByteArray QQmlConnectionsParser::compile(const QV4::CompiledData::QmlUnit *qmlU for (int ii = 0; ii < props.count(); ++ii) { const QV4::CompiledData::Binding *binding = props.at(ii); QString propName = qmlUnit->header.stringAt(binding->propertyNameIndex); - int propLine = binding->location.line; - int propColumn = binding->location.column; if (!propName.startsWith(QLatin1String("on")) || !propName.at(2).isUpper()) { error(props.at(ii), QQmlConnections::tr("Cannot assign to non-existent property \"%1\"").arg(propName)); @@ -232,21 +232,19 @@ QByteArray QQmlConnectionsParser::compile(const QV4::CompiledData::QmlUnit *qmlU return QByteArray(); } else { ds << propName; - ds << binding->valueAsString(&qmlUnit->header); - ds << propLine; - ds << propColumn; + ds << bindingIdentifier(binding); } } return rv; } -void QQmlConnectionsParser::setCustomData(QObject *object, - const QByteArray &data) +void QQmlConnectionsParser::setCustomData(QObject *object, const QByteArray &data, QQmlCompiledData *cdata) { QQmlConnectionsPrivate *p = static_cast(QObjectPrivate::get(object)); p->data = data; + p->cdata = cdata; } @@ -260,12 +258,8 @@ void QQmlConnections::connectSignals() while (!ds.atEnd()) { QString propName; ds >> propName; - QString script; - ds >> script; - int line; - ds >> line; - int column; - ds >> column; + int bindingId; + ds >> bindingId; QQmlProperty prop(target(), propName); if (prop.isValid() && (prop.type() & QQmlProperty::SignalProperty)) { @@ -273,19 +267,15 @@ void QQmlConnections::connectSignals() QQmlBoundSignal *signal = new QQmlBoundSignal(target(), signalIndex, this, qmlEngine(this)); - QString location; QQmlContextData *ctxtdata = 0; QQmlData *ddata = QQmlData::get(this); if (ddata) { ctxtdata = ddata->outerContext; - if (ctxtdata && !ctxtdata->url.isEmpty()) - location = ddata->outerContext->urlString; } QQmlBoundSignalExpression *expression = ctxtdata ? new QQmlBoundSignalExpression(target(), signalIndex, - ctxtdata, this, script, - location, line, column) : 0; + ctxtdata, this, d->cdata->functionForBindingId(bindingId)) : 0; signal->takeExpression(expression); d->boundsignals += signal; } else { diff --git a/src/qml/types/qqmlconnections_p.h b/src/qml/types/qqmlconnections_p.h index efd535a44f..f169eeb53f 100644 --- a/src/qml/types/qqmlconnections_p.h +++ b/src/qml/types/qqmlconnections_p.h @@ -85,7 +85,7 @@ class QQmlConnectionsParser : public QQmlCustomParser { public: virtual QByteArray compile(const QV4::CompiledData::QmlUnit *qmlUnit, const QList &props); - virtual void setCustomData(QObject *, const QByteArray &); + virtual void setCustomData(QObject *, const QByteArray &, QQmlCompiledData *cdata); }; diff --git a/src/qml/types/qqmllistmodel.cpp b/src/qml/types/qqmllistmodel.cpp index 301fd50095..8eab51a99f 100644 --- a/src/qml/types/qqmllistmodel.cpp +++ b/src/qml/types/qqmllistmodel.cpp @@ -2391,7 +2391,7 @@ QByteArray QQmlListModelParser::compile(const QV4::CompiledData::QmlUnit *qmlUni return rv; } -void QQmlListModelParser::setCustomData(QObject *obj, const QByteArray &d) +void QQmlListModelParser::setCustomData(QObject *obj, const QByteArray &d, QQmlCompiledData *) { QQmlListModel *rv = static_cast(obj); diff --git a/src/qml/types/qqmllistmodel_p.h b/src/qml/types/qqmllistmodel_p.h index ece653b11a..54ed18865f 100644 --- a/src/qml/types/qqmllistmodel_p.h +++ b/src/qml/types/qqmllistmodel_p.h @@ -169,7 +169,7 @@ public: QQmlListModelParser() : QQmlCustomParser(QQmlCustomParser::AcceptsSignalHandlers) {} QByteArray compile(const QV4::CompiledData::QmlUnit *qmlUnit, const QList &bindings); - void setCustomData(QObject *, const QByteArray &); + void setCustomData(QObject *, const QByteArray &, QQmlCompiledData *); private: struct ListInstruction diff --git a/src/quick/util/qquickpropertychanges.cpp b/src/quick/util/qquickpropertychanges.cpp index f73210a7a8..188b91b15e 100644 --- a/src/quick/util/qquickpropertychanges.cpp +++ b/src/quick/util/qquickpropertychanges.cpp @@ -48,6 +48,7 @@ #include #include #include +#include #include #include #include @@ -204,6 +205,7 @@ public: QPointer object; QByteArray data; + QQmlRefPointer cdata; bool decoded : 1; bool restore : 1; @@ -324,23 +326,10 @@ void QQuickPropertyChangesPrivate::decode() QQmlProperty prop = property(name); //### better way to check for signal property? if (prop.type() & QQmlProperty::SignalProperty) { - QString expression = data.toString(); - QUrl url = QUrl(); - int line = -1; - int column = -1; - - QQmlData *ddata = QQmlData::get(q); - if (ddata && ddata->outerContext && !ddata->outerContext->url.isEmpty()) { - url = ddata->outerContext->url; - line = ddata->lineNumber; - column = ddata->columnNumber; - } - QQuickReplaceSignalHandler *handler = new QQuickReplaceSignalHandler; handler->property = prop; handler->expression.take(new QQmlBoundSignalExpression(object, QQmlPropertyPrivate::get(prop)->signalIndex(), - QQmlContextData::get(qmlContext(q)), object, expression, - url.toString(), line, column)); + QQmlContextData::get(qmlContext(q)), object, cdata->functionForBindingId(id))); signalReplacements << handler; } else if (isScript) { // binding QString expression = data.toString(); @@ -364,12 +353,12 @@ void QQuickPropertyChangesPrivate::decode() data.clear(); } -void QQuickPropertyChangesParser::setCustomData(QObject *object, - const QByteArray &data) +void QQuickPropertyChangesParser::setCustomData(QObject *object, const QByteArray &data, QQmlCompiledData *cdata) { QQuickPropertyChangesPrivate *p = static_cast(QObjectPrivate::get(object)); p->data = data; + p->cdata = cdata; p->decoded = false; } diff --git a/src/quick/util/qquickpropertychanges_p.h b/src/quick/util/qquickpropertychanges_p.h index 9e9aaef8a1..3eed151d11 100644 --- a/src/quick/util/qquickpropertychanges_p.h +++ b/src/quick/util/qquickpropertychanges_p.h @@ -95,7 +95,7 @@ public: void compileList(QList > &list, const QString &pre, const QV4::CompiledData::QmlUnit *qmlUnit, const QV4::CompiledData::Binding *binding); virtual QByteArray compile(const QV4::CompiledData::QmlUnit *qmlUnit, const QList &props); - virtual void setCustomData(QObject *, const QByteArray &); + virtual void setCustomData(QObject *, const QByteArray &, QQmlCompiledData *); }; diff --git a/tests/auto/qml/qqmlconnections/tst_qqmlconnections.cpp b/tests/auto/qml/qqmlconnections/tst_qqmlconnections.cpp index dbf48779d6..1cd1583f22 100644 --- a/tests/auto/qml/qqmlconnections/tst_qqmlconnections.cpp +++ b/tests/auto/qml/qqmlconnections/tst_qqmlconnections.cpp @@ -251,10 +251,9 @@ void tst_qqmlconnections::rewriteErrors() { QQmlEngine engine; QQmlComponent c(&engine, testFileUrl("rewriteError-unnamed.qml")); + QTest::ignoreMessage(QtWarningMsg, (c.url().toString() + ":5:35: QML Connections: Signal uses unnamed parameter followed by named parameter.").toLatin1()); TestObject *obj = qobject_cast(c.create()); QVERIFY(obj != 0); - - QTest::ignoreMessage(QtWarningMsg, (c.url().toString() + ":5:35: QML Connections: Signal uses unnamed parameter followed by named parameter.").toLatin1()); obj->unnamedArgumentSignal(1, .5, "hello"); QCOMPARE(obj->ran(), false); @@ -264,10 +263,10 @@ void tst_qqmlconnections::rewriteErrors() { QQmlEngine engine; QQmlComponent c(&engine, testFileUrl("rewriteError-global.qml")); + QTest::ignoreMessage(QtWarningMsg, (c.url().toString() + ":5:35: QML Connections: Signal parameter \"parseInt\" hides global variable.").toLatin1()); TestObject *obj = qobject_cast(c.create()); QVERIFY(obj != 0); - QTest::ignoreMessage(QtWarningMsg, (c.url().toString() + ":5:35: QML Connections: Signal parameter \"parseInt\" hides global variable.").toLatin1()); obj->signalWithGlobalName(10); QCOMPARE(obj->ran(), false); diff --git a/tests/auto/qml/qqmllanguage/testtypes.cpp b/tests/auto/qml/qqmllanguage/testtypes.cpp index 226206edfd..8ffa327cf2 100644 --- a/tests/auto/qml/qqmllanguage/testtypes.cpp +++ b/tests/auto/qml/qqmllanguage/testtypes.cpp @@ -125,7 +125,7 @@ QByteArray CustomBindingParser::compile(const QV4::CompiledData::QmlUnit *qmlUni return result; } -void CustomBindingParser::setCustomData(QObject *object, const QByteArray &data) +void CustomBindingParser::setCustomData(QObject *object, const QByteArray &data, QQmlCompiledData*) { CustomBinding *customBinding = qobject_cast(object); Q_ASSERT(customBinding); @@ -197,7 +197,7 @@ QByteArray SimpleObjectCustomParser::compile(const QV4::CompiledData::QmlUnit *, return QByteArray::number(bindings.count()); } -void SimpleObjectCustomParser::setCustomData(QObject *object, const QByteArray &data) +void SimpleObjectCustomParser::setCustomData(QObject *object, const QByteArray &data, QQmlCompiledData*) { SimpleObjectWithCustomParser *o = qobject_cast(object); Q_ASSERT(o); diff --git a/tests/auto/qml/qqmllanguage/testtypes.h b/tests/auto/qml/qqmllanguage/testtypes.h index 1f34309a5e..6a6b2eba45 100644 --- a/tests/auto/qml/qqmllanguage/testtypes.h +++ b/tests/auto/qml/qqmllanguage/testtypes.h @@ -729,14 +729,14 @@ class MyCustomParserTypeParser : public QQmlCustomParser { public: QByteArray compile(const QV4::CompiledData::QmlUnit *, const QList &) { return QByteArray(); } - void setCustomData(QObject *, const QByteArray &) {} + void setCustomData(QObject *, const QByteArray &, QQmlCompiledData*) {} }; class EnumSupportingCustomParser : public QQmlCustomParser { public: QByteArray compile(const QV4::CompiledData::QmlUnit *qmlUnit, const QList &bindings); - void setCustomData(QObject *, const QByteArray &) {} + void setCustomData(QObject *, const QByteArray &, QQmlCompiledData*) {} }; class MyParserStatus : public QObject, public QQmlParserStatus @@ -1107,7 +1107,7 @@ public: class CustomBindingParser : public QQmlCustomParser { virtual QByteArray compile(const QV4::CompiledData::QmlUnit *qmlUnit, const QList &bindings); - virtual void setCustomData(QObject *object, const QByteArray &data); + virtual void setCustomData(QObject *object, const QByteArray &data, QQmlCompiledData *); }; class SimpleObjectWithCustomParser : public QObject @@ -1134,7 +1134,7 @@ private: class SimpleObjectCustomParser : public QQmlCustomParser { virtual QByteArray compile(const QV4::CompiledData::QmlUnit *, const QList &bindings); - virtual void setCustomData(QObject *object, const QByteArray &data); + virtual void setCustomData(QObject *object, const QByteArray &data, QQmlCompiledData *); }; class RootObjectInCreationTester : public QObject -- cgit v1.2.3