Merge "Merge remote-tracking branch 'origin/5.15' into dev"
This commit is contained in:
commit
2c5c3fee1c
|
@ -51,6 +51,6 @@
|
|||
// We mean it.
|
||||
//
|
||||
|
||||
#define Q_QML_PRIVATE_API_VERSION 5
|
||||
#define Q_QML_PRIVATE_API_VERSION 6
|
||||
|
||||
#endif // QQMLAPIVERSION_P_H
|
||||
|
|
|
@ -0,0 +1,104 @@
|
|||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2019 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of the documentation of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:FDL$
|
||||
** 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 Free Documentation License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU Free
|
||||
** Documentation License version 1.3 as published by the Free Software
|
||||
** Foundation and appearing in the file included in the packaging of
|
||||
** this file. Please review the following information to ensure
|
||||
** the GNU Free Documentation License version 1.3 requirements
|
||||
** will be met: https://www.gnu.org/licenses/fdl-1.3.html.
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
/*!
|
||||
|
||||
\page qtqml-javascript-finetuning.html
|
||||
\title Fine-tuning the JavaScript Engine
|
||||
\brief Describes the environment variables available, to control how Javascript is run.
|
||||
|
||||
Running JavaScript code can be influenced by a few environment variables, particularly:
|
||||
|
||||
\table
|
||||
\header
|
||||
\li Environment Variable
|
||||
\li Description
|
||||
\row
|
||||
\li \c{QV4_JIT_CALL_THRESHOLD}
|
||||
\li The JavaScript engine contains a Just-In-Time compiler (JIT). The JIT will compile
|
||||
frequently run JavaScript functions into machine code to run faster. This
|
||||
environment variable determines how often a function needs to be run to be
|
||||
considered for JIT compilation. The default value is 3 times.
|
||||
\row
|
||||
\li \c{QV4_FORCE_INTERPRETER}
|
||||
\li Setting this environment variable disables the JIT and runs all
|
||||
functions through the interpreter, no matter how often they are called.
|
||||
\row
|
||||
\li \c{QV4_JS_MAX_STACK_SIZE}
|
||||
\li The JavaScript engine reserves a special memory area as a stack to run JavaScript.
|
||||
This stack is separate from the C++ stack. Usually this area is 4MB in size. If this
|
||||
environment variable contains a number, the JavaScript engine interprets it as the
|
||||
size of the memory area, in bytes, to be allocated as the JavaScript stack.
|
||||
\row
|
||||
\li \c{QV4_GC_MAX_STACK_SIZE}
|
||||
\li In addition to the regular JavaScript stack, the JavaScript engine keeps another stack
|
||||
for the garbage collector, usually 2MB of memory. If the garbage collector needs to
|
||||
handle an excessive number of objects at the same time, this stack might overrun.
|
||||
If it contains a number, this environment variable is interpreted as the size in bytes
|
||||
of the memory area that will be allocated as the stack for the garbage collector.
|
||||
\row
|
||||
\li \c{QV4_CRASH_ON_STACKOVERFLOW}
|
||||
\li Usually the JavaScript engine tries to catch C++ stack overflows caused by
|
||||
excessively recursive JavaScript code, and generates a non-fatal error condition.
|
||||
There are separate recursion checks for compiling JavaScript and running JavaScript. A
|
||||
stack overflow when compiling JavaScript indicates that the code contains deeply nested
|
||||
objects and functions. A stack overflow at run-time indicates that the code results in
|
||||
a deeply recursive program. The check for this is only indirectly related to the
|
||||
JavaScript stack size mentioned above, as each JavaScript function call consumes stack
|
||||
space on both, the C++ and the JavaScript stack. The code that checks for excessive
|
||||
recursion is necessarily conservative, as the available stack size depends on many
|
||||
factors and can often be customized by the user. With this environment variable set, the
|
||||
JavaScript engine does not check for stack overflows when compiling or running
|
||||
JavaScript and will not generate exceptions for them. Instead, when the stack overflows
|
||||
the program attempts an invalid memory access. This most likely terminates the
|
||||
program. In turn, the program gets to use up all the stack space the operating system
|
||||
can provide.
|
||||
\warning malicious code may be able to evade the termination and access unexpected
|
||||
memory locations this way.
|
||||
\row
|
||||
\li \c{QV4_MAX_CALL_DEPTH}
|
||||
\li Stack overflows when running (as opposed to compiling) JavaScript are prevented by
|
||||
controlling the call depth: the number of nested function invocations. By
|
||||
default, an exception is generated if the call depth exceeds 1234. If it contains a
|
||||
number, this environment variable overrides the maximum call depth. Beware that the
|
||||
recursion limit when compiling JavaScript is not affected.
|
||||
\row
|
||||
\li \c{QV4_MM_AGGRESSIVE_GC}
|
||||
\li Setting this environment variable runs the garbage collector before each memory
|
||||
allocation. This is very expensive at run-time, but it quickly uncovers many memory
|
||||
management errors, for example the manual deletion of an object belonging to the QML
|
||||
engine from C++.
|
||||
\row
|
||||
\li \c{QV4_PROFILE_WRITE_PERF_MAP}
|
||||
\li On Linux, the \c perf utility can be used to profile programs. To analyze JIT-compiled
|
||||
JavaScript functions, it needs to know about their names and locations in memory. To
|
||||
provide this information, there's a convention to create a special file called
|
||||
\c{perf-<pid>.map} in \e{/tmp} which perf then reads. This environment variable, if
|
||||
set, causes the JIT to generate this file.
|
||||
\endtable
|
||||
|
||||
*/
|
||||
|
|
@ -81,4 +81,11 @@ These limitations and extensions are documented in the description of the
|
|||
\l{qtqml-javascript-hostenvironment.html}{JavaScript Host Environment} provided
|
||||
by the QML engine.
|
||||
|
||||
\section1 Fine Tuning the JavaScript engine
|
||||
|
||||
For specific use cases you may want to override some of the parameters the
|
||||
JavaScript engine uses for handling memory and compiling JavaScript. See
|
||||
\l{qtqml-javascript-finetuning.html}{Fine Tuning the JavaScript engine} for
|
||||
more information on these parameters.
|
||||
|
||||
*/
|
||||
|
|
|
@ -216,15 +216,19 @@ ExecutionEngine::ExecutionEngine(QJSEngine *jsEngine)
|
|||
memoryManager = new QV4::MemoryManager(this);
|
||||
|
||||
if (maxCallDepth == -1) {
|
||||
ok = false;
|
||||
maxCallDepth = qEnvironmentVariableIntValue("QV4_MAX_CALL_DEPTH", &ok);
|
||||
if (!ok || maxCallDepth <= 0) {
|
||||
if (qEnvironmentVariableIsSet("QV4_CRASH_ON_STACKOVERFLOW")) {
|
||||
maxCallDepth = std::numeric_limits<qint32>::max();
|
||||
} else {
|
||||
ok = false;
|
||||
maxCallDepth = qEnvironmentVariableIntValue("QV4_MAX_CALL_DEPTH", &ok);
|
||||
if (!ok || maxCallDepth <= 0) {
|
||||
#if defined(QT_NO_DEBUG) && !defined(__SANITIZE_ADDRESS__) && !QT_HAS_FEATURE(address_sanitizer)
|
||||
maxCallDepth = 1234;
|
||||
maxCallDepth = 1234;
|
||||
#else
|
||||
// no (tail call) optimization is done, so there'll be a lot mare stack frames active
|
||||
maxCallDepth = 200;
|
||||
// no (tail call) optimization is done, so there'll be a lot mare stack frames active
|
||||
maxCallDepth = 200;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
Q_ASSERT(maxCallDepth > 0);
|
||||
|
|
|
@ -105,6 +105,12 @@ ClassExpression *Node::asClassDefinition()
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
bool Node::ignoreRecursionDepth() const
|
||||
{
|
||||
static const bool doIgnore = qEnvironmentVariableIsSet("QV4_CRASH_ON_STACKOVERFLOW");
|
||||
return doIgnore;
|
||||
}
|
||||
|
||||
ExpressionNode *ExpressionNode::expressionCast()
|
||||
{
|
||||
return this;
|
||||
|
|
|
@ -275,10 +275,16 @@ public:
|
|||
virtual FunctionExpression *asFunctionDefinition();
|
||||
virtual ClassExpression *asClassDefinition();
|
||||
|
||||
bool ignoreRecursionDepth() const;
|
||||
|
||||
inline void accept(Visitor *visitor)
|
||||
{
|
||||
Visitor::RecursionDepthCheck recursionCheck(visitor);
|
||||
if (recursionCheck()) {
|
||||
|
||||
// Stack overflow is uncommon, ignoreRecursionDepth() only returns true if
|
||||
// QV4_CRASH_ON_STACKOVERFLOW is set, and ignoreRecursionDepth() needs to be out of line.
|
||||
// Therefore, check for ignoreRecursionDepth() _after_ calling the inline recursionCheck().
|
||||
if (recursionCheck() || ignoreRecursionDepth()) {
|
||||
if (visitor->preVisit(this))
|
||||
accept0(visitor);
|
||||
visitor->postVisit(this);
|
||||
|
|
|
@ -345,6 +345,11 @@ RequiredProperties &QQmlComponentPrivate::requiredProperties()
|
|||
return state.creator->requiredProperties();
|
||||
}
|
||||
|
||||
bool QQmlComponentPrivate::hadRequiredProperties() const
|
||||
{
|
||||
return state.creator->componentHadRequiredProperties();
|
||||
}
|
||||
|
||||
void QQmlComponentPrivate::clear()
|
||||
{
|
||||
if (typeData) {
|
||||
|
|
|
@ -108,6 +108,7 @@ public:
|
|||
|
||||
int start;
|
||||
RequiredProperties& requiredProperties();
|
||||
bool hadRequiredProperties() const;
|
||||
QQmlRefPointer<QV4::ExecutableCompilationUnit> compilationUnit;
|
||||
|
||||
struct ConstructionState {
|
||||
|
|
|
@ -690,6 +690,11 @@ RequiredProperties &QQmlIncubatorPrivate::requiredProperties()
|
|||
return creator->requiredProperties();
|
||||
}
|
||||
|
||||
bool QQmlIncubatorPrivate::hadRequiredProperties() const
|
||||
{
|
||||
return creator->componentHadRequiredProperties();
|
||||
}
|
||||
|
||||
/*!
|
||||
Stores a mapping from property names to initial values with which the incubated
|
||||
component will be initialized
|
||||
|
|
|
@ -108,6 +108,7 @@ public:
|
|||
void forceCompletion(QQmlInstantiationInterrupt &i);
|
||||
void incubate(QQmlInstantiationInterrupt &i);
|
||||
RequiredProperties &requiredProperties();
|
||||
bool hadRequiredProperties() const;
|
||||
};
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
|
|
@ -82,6 +82,7 @@ QQmlObjectCreator::QQmlObjectCreator(QQmlContextData *parentContext, const QQmlR
|
|||
, propertyCaches(&compilationUnit->propertyCaches)
|
||||
, sharedState(new QQmlObjectCreatorSharedState)
|
||||
, topLevelCreator(true)
|
||||
, hadRequiredProperties(false)
|
||||
, incubator(incubator)
|
||||
{
|
||||
init(parentContext);
|
||||
|
@ -1523,6 +1524,7 @@ bool QQmlObjectCreator::populateInstance(int index, QObject *instance, QObject *
|
|||
const QV4::CompiledData::Property* property = _compiledObject->propertiesBegin() + propertyIndex;
|
||||
QQmlPropertyData *propertyData = _propertyCache->property(_propertyCache->propertyOffset() + propertyIndex);
|
||||
if (property->isRequired) {
|
||||
hadRequiredProperties = true;
|
||||
sharedState->requiredProperties.insert(propertyData,
|
||||
RequiredPropertyInfo {compilationUnit->stringAt(property->nameIndex), compilationUnit->finalUrl(), property->location, {}});
|
||||
}
|
||||
|
|
|
@ -126,6 +126,7 @@ public:
|
|||
QFiniteStack<QPointer<QObject> > &allCreatedObjects() { return sharedState->allCreatedObjects; }
|
||||
|
||||
RequiredProperties &requiredProperties() {return sharedState->requiredProperties;}
|
||||
bool componentHadRequiredProperties() const {return hadRequiredProperties;}
|
||||
|
||||
private:
|
||||
QQmlObjectCreator(QQmlContextData *contextData, const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit, QQmlObjectCreatorSharedState *inheritedSharedState);
|
||||
|
@ -172,6 +173,7 @@ private:
|
|||
const QQmlPropertyCacheVector *propertyCaches;
|
||||
QExplicitlySharedDataPointer<QQmlObjectCreatorSharedState> sharedState;
|
||||
bool topLevelCreator;
|
||||
bool hadRequiredProperties;
|
||||
QQmlIncubatorPrivate *incubator;
|
||||
|
||||
QObject *_qobject;
|
||||
|
|
|
@ -73,14 +73,6 @@ static bool parseVersion(const QString &str, int *major, int *minor)
|
|||
return false;
|
||||
}
|
||||
|
||||
QQmlDirParser::QQmlDirParser() : _designerSupported(false)
|
||||
{
|
||||
}
|
||||
|
||||
QQmlDirParser::~QQmlDirParser()
|
||||
{
|
||||
}
|
||||
|
||||
inline static void scanSpace(const QChar *&ch) {
|
||||
while (ch->isSpace() && !ch->isNull() && *ch != QLatin1Char('\n'))
|
||||
++ch;
|
||||
|
|
|
@ -64,9 +64,6 @@ class QQmlEngine;
|
|||
class Q_QMLCOMPILER_PRIVATE_EXPORT QQmlDirParser
|
||||
{
|
||||
public:
|
||||
QQmlDirParser();
|
||||
~QQmlDirParser();
|
||||
|
||||
bool parse(const QString &source);
|
||||
|
||||
bool hasError() const;
|
||||
|
@ -87,7 +84,7 @@ public:
|
|||
|
||||
struct Plugin
|
||||
{
|
||||
Plugin() {}
|
||||
Plugin() = default;
|
||||
|
||||
Plugin(const QString &name, const QString &path)
|
||||
: name(name), path(path)
|
||||
|
@ -101,7 +98,7 @@ public:
|
|||
|
||||
struct Component
|
||||
{
|
||||
Component() {}
|
||||
Component() = default;
|
||||
|
||||
Component(const QString &typeName, const QString &fileName, int majorVersion, int minorVersion)
|
||||
: typeName(typeName), fileName(fileName), majorVersion(majorVersion), minorVersion(minorVersion),
|
||||
|
@ -120,7 +117,7 @@ public:
|
|||
|
||||
struct Script
|
||||
{
|
||||
Script() {}
|
||||
Script() = default;
|
||||
|
||||
Script(const QString &nameSpace, const QString &fileName, int majorVersion, int minorVersion)
|
||||
: nameSpace(nameSpace), fileName(fileName), majorVersion(majorVersion), minorVersion(minorVersion)
|
||||
|
@ -143,7 +140,7 @@ public:
|
|||
|
||||
struct TypeInfo
|
||||
{
|
||||
TypeInfo() {}
|
||||
TypeInfo() = default;
|
||||
TypeInfo(const QString &fileName)
|
||||
: fileName(fileName) {}
|
||||
|
||||
|
@ -166,14 +163,14 @@ private:
|
|||
QStringList _imports;
|
||||
QList<Script> _scripts;
|
||||
QList<Plugin> _plugins;
|
||||
bool _designerSupported;
|
||||
bool _designerSupported = false;
|
||||
QList<TypeInfo> _typeInfos;
|
||||
QString _className;
|
||||
};
|
||||
|
||||
typedef QHash<QString,QQmlDirParser::Component> QQmlDirComponents;
|
||||
typedef QList<QQmlDirParser::Script> QQmlDirScripts;
|
||||
typedef QList<QQmlDirParser::Plugin> QQmlDirPlugins;
|
||||
using QQmlDirComponents = QHash<QString,QQmlDirParser::Component>;
|
||||
using QQmlDirScripts = QList<QQmlDirParser::Script>;
|
||||
using QQmlDirPlugins = QList<QQmlDirParser::Plugin>;
|
||||
|
||||
QDebug &operator<< (QDebug &, const QQmlDirParser::Component &);
|
||||
QDebug &operator<< (QDebug &, const QQmlDirParser::Script &);
|
||||
|
|
|
@ -48,7 +48,6 @@
|
|||
#include <private/qqmlchangeset_p.h>
|
||||
#include <private/qqmlengine_p.h>
|
||||
#include <private/qqmlcomponent_p.h>
|
||||
#include <private/qqmlincubator_p.h>
|
||||
|
||||
#include <private/qv4value_p.h>
|
||||
#include <private/qv4functionobject_p.h>
|
||||
|
@ -887,6 +886,81 @@ static bool isDoneIncubating(QQmlIncubator::Status status)
|
|||
return status == QQmlIncubator::Ready || status == QQmlIncubator::Error;
|
||||
}
|
||||
|
||||
PropertyUpdater::PropertyUpdater(QObject *parent) :
|
||||
QObject(parent) {}
|
||||
|
||||
void PropertyUpdater::doUpdate()
|
||||
{
|
||||
auto sender = QObject::sender();
|
||||
auto mo = sender->metaObject();
|
||||
auto signalIndex = QObject::senderSignalIndex();
|
||||
// start at 0 instead of propertyOffset to handle properties from parent hierarchy
|
||||
for (auto i = 0; i < mo->propertyCount() + mo->propertyOffset(); ++i) {
|
||||
auto property = mo->property(i);
|
||||
if (property.notifySignal().methodIndex() == signalIndex) {
|
||||
// we synchronize between required properties and model rolenames by name
|
||||
// that's why the QQmlProperty and the metaobject property must have the same name
|
||||
QQmlProperty qmlProp(parent(), QString::fromLatin1(property.name()));
|
||||
qmlProp.write(property.read(QObject::sender()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void QQDMIncubationTask::initializeRequiredProperties(QQmlDelegateModelItem *modelItemToIncubate, QObject *object)
|
||||
{
|
||||
auto incubatorPriv = QQmlIncubatorPrivate::get(this);
|
||||
if (incubatorPriv->hadRequiredProperties()) {
|
||||
if (incubatorPriv->requiredProperties().empty())
|
||||
return;
|
||||
RequiredProperties &requiredProperties = incubatorPriv->requiredProperties();
|
||||
|
||||
auto qmlMetaObject = modelItemToIncubate->metaObject();
|
||||
// if a required property was not in the model, it might still be a static property of the
|
||||
// QQmlDelegateModelItem or one of its derived classes this is the case for index, row,
|
||||
// column, model and more
|
||||
// the most derived subclass of QQmlDelegateModelItem is QQmlDMAbstractModelData at depth 2,
|
||||
// so 4 should be plenty
|
||||
QVarLengthArray<const QMetaObject *, 4> mos;
|
||||
// we first check the dynamic meta object for properties originating from the model
|
||||
mos.push_back(qmlMetaObject); // contains abstractitemmodelproperties
|
||||
auto delegateModelItemSubclassMO = qmlMetaObject->superClass();
|
||||
mos.push_back(delegateModelItemSubclassMO);
|
||||
|
||||
while (strcmp(delegateModelItemSubclassMO->className(), modelItemToIncubate->staticMetaObject.className())) {
|
||||
delegateModelItemSubclassMO = delegateModelItemSubclassMO->superClass();
|
||||
mos.push_back(delegateModelItemSubclassMO);
|
||||
}
|
||||
if (proxiedObject)
|
||||
mos.push_back(proxiedObject->metaObject());
|
||||
|
||||
auto updater = new PropertyUpdater(object);
|
||||
for (const QMetaObject *mo : mos) {
|
||||
for (int i = mo->propertyOffset(); i < mo->propertyCount() + mo->propertyOffset(); ++i) {
|
||||
auto prop = mo->property(i);
|
||||
if (!prop.name())
|
||||
continue;
|
||||
auto propName = QString::fromUtf8(prop.name());
|
||||
bool wasInRequired = false;
|
||||
QQmlProperty componentProp = QQmlComponentPrivate::removePropertyFromRequired(
|
||||
object, propName, requiredProperties, &wasInRequired);
|
||||
// only write to property if it was actually requested by the component
|
||||
if (wasInRequired && prop.hasNotifySignal()) {
|
||||
QMetaMethod changeSignal = prop.notifySignal();
|
||||
QMetaMethod updateSlot = PropertyUpdater::staticMetaObject.method(PropertyUpdater::staticMetaObject.indexOfSlot("doUpdate()"));
|
||||
QObject::connect(modelItemToIncubate, changeSignal, updater, updateSlot);
|
||||
}
|
||||
if (wasInRequired)
|
||||
componentProp.write(prop.read(modelItemToIncubate));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
modelItemToIncubate->contextData->contextObject = modelItemToIncubate;
|
||||
if (proxiedObject)
|
||||
proxyContext->contextObject = proxiedObject;
|
||||
}
|
||||
}
|
||||
|
||||
void QQDMIncubationTask::statusChanged(Status status)
|
||||
{
|
||||
if (vdm) {
|
||||
|
@ -987,6 +1061,7 @@ void QQDMIncubationTask::setInitialState(QObject *o)
|
|||
void QQmlDelegateModelPrivate::setInitialState(QQDMIncubationTask *incubationTask, QObject *o)
|
||||
{
|
||||
QQmlDelegateModelItem *cacheItem = incubationTask->incubating;
|
||||
incubationTask->initializeRequiredProperties(incubationTask->incubating, o);
|
||||
cacheItem->object = o;
|
||||
|
||||
if (QQuickPackage *package = qmlobject_cast<QQuickPackage *>(cacheItem->object))
|
||||
|
@ -1053,7 +1128,6 @@ QObject *QQmlDelegateModelPrivate::object(Compositor::Group group, int index, QQ
|
|||
|
||||
QQmlContextData *ctxt = new QQmlContextData;
|
||||
ctxt->setParent(QQmlContextData::get(creationContext ? creationContext : m_context.data()));
|
||||
ctxt->contextObject = cacheItem;
|
||||
cacheItem->contextData = ctxt;
|
||||
|
||||
if (m_adaptorModel.hasProxyObject()) {
|
||||
|
@ -1062,7 +1136,8 @@ QObject *QQmlDelegateModelPrivate::object(Compositor::Group group, int index, QQ
|
|||
ctxt = new QQmlContextData;
|
||||
ctxt->setParent(cacheItem->contextData, /*stronglyReferencedByParent*/true);
|
||||
QObject *proxied = proxy->proxiedObject();
|
||||
ctxt->contextObject = proxied;
|
||||
cacheItem->incubationTask->proxiedObject = proxied;
|
||||
cacheItem->incubationTask->proxyContext = ctxt;
|
||||
// We don't own the proxied object. We need to clear it if it goes away.
|
||||
QObject::connect(proxied, &QObject::destroyed,
|
||||
cacheItem, &QQmlDelegateModelItem::childContextObjectDestroyed);
|
||||
|
|
|
@ -201,11 +201,14 @@ public:
|
|||
, incubating(nullptr)
|
||||
, vdm(l) {}
|
||||
|
||||
void initializeRequiredProperties(QQmlDelegateModelItem *modelItemToIncubate, QObject* object);
|
||||
void statusChanged(Status) override;
|
||||
void setInitialState(QObject *) override;
|
||||
|
||||
QQmlDelegateModelItem *incubating = nullptr;
|
||||
QQmlDelegateModelPrivate *vdm = nullptr;
|
||||
QQmlContextData *proxyContext = nullptr;
|
||||
QPointer<QObject> proxiedObject = nullptr; // the proxied object might disapear, so we use a QPointer instead of a raw one
|
||||
int index[QQmlListCompositor::MaximumGroupCount];
|
||||
};
|
||||
|
||||
|
@ -445,6 +448,16 @@ private:
|
|||
const int indexPropertyOffset;
|
||||
};
|
||||
|
||||
class PropertyUpdater : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
PropertyUpdater(QObject *parent);
|
||||
public Q_SLOTS:
|
||||
void doUpdate();
|
||||
};
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
||||
#endif
|
||||
|
|
|
@ -525,8 +525,13 @@ const QAbstractItemModel *QQmlTableInstanceModel::abstractItemModel() const
|
|||
|
||||
void QQmlTableInstanceModelIncubationTask::setInitialState(QObject *object)
|
||||
{
|
||||
modelItemToIncubate->object = object;
|
||||
emit tableInstanceModel->initItem(modelItemToIncubate->index, object);
|
||||
initializeRequiredProperties(modelItemToIncubate, object);
|
||||
if (QQmlIncubatorPrivate::get(this)->requiredProperties().empty()) {
|
||||
modelItemToIncubate->object = object;
|
||||
emit tableInstanceModel->initItem(modelItemToIncubate->index, object);
|
||||
} else {
|
||||
object->deleteLater();
|
||||
}
|
||||
}
|
||||
|
||||
void QQmlTableInstanceModelIncubationTask::statusChanged(QQmlIncubator::Status status)
|
||||
|
|
|
@ -49,6 +49,7 @@
|
|||
#include <QtCore/qmath.h>
|
||||
|
||||
#include <private/qquicksmoothedanimation_p_p.h>
|
||||
#include <private/qqmlcomponent_p.h>
|
||||
#include "qplatformdefs.h"
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
@ -190,6 +191,8 @@ public:
|
|||
}
|
||||
|
||||
friend class QQuickViewSection;
|
||||
|
||||
static void setSectionHelper(QQmlContext *context, QQuickItem *sectionItem, const QString §ion);
|
||||
};
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
|
@ -992,14 +995,20 @@ QQuickItem * QQuickListViewPrivate::getSectionItem(const QString §ion)
|
|||
sectionCache[i] = nullptr;
|
||||
sectionItem->setVisible(true);
|
||||
QQmlContext *context = QQmlEngine::contextForObject(sectionItem)->parentContext();
|
||||
context->setContextProperty(QLatin1String("section"), section);
|
||||
setSectionHelper(context, sectionItem, section);
|
||||
} else {
|
||||
QQmlContext *creationContext = sectionCriteria->delegate()->creationContext();
|
||||
QQmlContext *context = new QQmlContext(
|
||||
creationContext ? creationContext : qmlContext(q));
|
||||
context->setContextProperty(QLatin1String("section"), section);
|
||||
QObject *nobj = sectionCriteria->delegate()->beginCreate(context);
|
||||
QQmlComponent* delegate = sectionCriteria->delegate();
|
||||
QQmlComponentPrivate* delegatePriv = QQmlComponentPrivate::get(delegate);
|
||||
QObject *nobj = delegate->beginCreate(context);
|
||||
if (nobj) {
|
||||
if (delegatePriv->hadRequiredProperties()) {
|
||||
delegate->setInitialProperties(nobj, {{"section", section}});
|
||||
} else {
|
||||
context->setContextProperty(QLatin1String("section"), section);
|
||||
}
|
||||
QQml_setParent_noEvent(context, nobj);
|
||||
sectionItem = qobject_cast<QQuickItem *>(nobj);
|
||||
if (!sectionItem) {
|
||||
|
@ -1069,7 +1078,7 @@ void QQuickListViewPrivate::updateInlineSection(FxListItemSG *listItem)
|
|||
listItem->setPosition(pos);
|
||||
} else {
|
||||
QQmlContext *context = QQmlEngine::contextForObject(listItem->section())->parentContext();
|
||||
context->setContextProperty(QLatin1String("section"), listItem->attached->m_section);
|
||||
setSectionHelper(context, listItem->section(), listItem->attached->m_section);
|
||||
}
|
||||
} else if (listItem->section()) {
|
||||
qreal pos = listItem->position();
|
||||
|
@ -1125,7 +1134,7 @@ void QQuickListViewPrivate::updateStickySections()
|
|||
currentSectionItem = getSectionItem(currentSection);
|
||||
} else if (QString::compare(currentStickySection, currentSection, Qt::CaseInsensitive)) {
|
||||
QQmlContext *context = QQmlEngine::contextForObject(currentSectionItem)->parentContext();
|
||||
context->setContextProperty(QLatin1String("section"), currentSection);
|
||||
setSectionHelper(context, currentSectionItem, currentSection);
|
||||
}
|
||||
currentStickySection = currentSection;
|
||||
if (!currentSectionItem)
|
||||
|
@ -1159,7 +1168,7 @@ void QQuickListViewPrivate::updateStickySections()
|
|||
nextSectionItem = getSectionItem(nextSection);
|
||||
} else if (QString::compare(nextStickySection, nextSection, Qt::CaseInsensitive)) {
|
||||
QQmlContext *context = QQmlEngine::contextForObject(nextSectionItem)->parentContext();
|
||||
context->setContextProperty(QLatin1String("section"), nextSection);
|
||||
setSectionHelper(context, nextSectionItem, nextSection);
|
||||
}
|
||||
nextStickySection = nextSection;
|
||||
if (!nextSectionItem)
|
||||
|
@ -1754,6 +1763,14 @@ bool QQuickListViewPrivate::flick(AxisData &data, qreal minExtent, qreal maxExte
|
|||
}
|
||||
}
|
||||
|
||||
void QQuickListViewPrivate::setSectionHelper(QQmlContext *context, QQuickItem *sectionItem, const QString §ion)
|
||||
{
|
||||
if (context->contextProperty(QLatin1String("section")).isValid())
|
||||
context->setContextProperty(QLatin1String("section"), section);
|
||||
else
|
||||
sectionItem->setProperty("section", section);
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
/*!
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
import QtQuick 2.12
|
||||
import Qt.fruit 1.0
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
required property bool useCpp
|
||||
width: 200; height: 200
|
||||
|
||||
|
||||
ListModel {
|
||||
id: fruitModel
|
||||
|
||||
ListElement {
|
||||
name: "Apple"
|
||||
cost: 2
|
||||
}
|
||||
ListElement {
|
||||
name: "Orange"
|
||||
cost: 3
|
||||
}
|
||||
ListElement {
|
||||
name: "Banana"
|
||||
cost: 1
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Component {
|
||||
id: fruitDelegate
|
||||
Row {
|
||||
id: row
|
||||
spacing: 10
|
||||
required property string name
|
||||
required property int cost
|
||||
Text { text: row.name }
|
||||
Text { text: '$' + row.cost }
|
||||
Component.onCompleted: () => { console.debug(row.name+row.cost) };
|
||||
}
|
||||
}
|
||||
|
||||
ListView {
|
||||
anchors.fill: parent
|
||||
model: root.useCpp ? FruitModelCpp : fruitModel
|
||||
delegate: fruitDelegate
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
import QtQuick 2.0
|
||||
|
||||
Rectangle {
|
||||
property string sectionProperty: "number"
|
||||
property int sectionPositioning: ViewSection.InlineLabels
|
||||
width: 240
|
||||
height: 320
|
||||
color: "#ffffff"
|
||||
resources: [
|
||||
Component {
|
||||
id: myDelegate
|
||||
Item {
|
||||
id: wrapper
|
||||
objectName: "wrapper"
|
||||
property string section: ListView.section
|
||||
property string nextSection: ListView.nextSection
|
||||
property string prevSection: ListView.previousSection
|
||||
height: 20;
|
||||
width: 240
|
||||
Rectangle {
|
||||
height: 20
|
||||
width: parent.width
|
||||
color: wrapper.ListView.isCurrentItem ? "lightsteelblue" : "white"
|
||||
Text {
|
||||
text: index
|
||||
}
|
||||
Text {
|
||||
x: 30
|
||||
id: textName
|
||||
objectName: "textName"
|
||||
text: name
|
||||
}
|
||||
Text {
|
||||
x: 100
|
||||
id: textNumber
|
||||
objectName: "textNumber"
|
||||
text: number
|
||||
}
|
||||
Text {
|
||||
objectName: "nextSection"
|
||||
x: 150
|
||||
text: wrapper.ListView.nextSection
|
||||
}
|
||||
Text {
|
||||
x: 200
|
||||
text: wrapper.y
|
||||
}
|
||||
}
|
||||
ListView.onRemove: SequentialAnimation {
|
||||
PropertyAction { target: wrapper; property: "ListView.delayRemove"; value: true }
|
||||
NumberAnimation { target: wrapper; property: "height"; to: 0; duration: 100; easing.type: Easing.InOutQuad }
|
||||
PropertyAction { target: wrapper; property: "ListView.delayRemove"; value: false }
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
ListView {
|
||||
id: list
|
||||
objectName: "list"
|
||||
width: 240
|
||||
height: 320
|
||||
cacheBuffer: 60
|
||||
model: testModel
|
||||
delegate: myDelegate
|
||||
section.property: sectionProperty
|
||||
section.delegate: Rectangle {
|
||||
id: myDelegate
|
||||
required property string section
|
||||
objectName: "sect_" + section
|
||||
color: "#99bb99"
|
||||
height: 20
|
||||
width: list.width
|
||||
Text { text: myDelegate.section + ", " + parent.y + ", " + parent.objectName }
|
||||
}
|
||||
section.labelPositioning: sectionPositioning
|
||||
}
|
||||
}
|
|
@ -127,6 +127,7 @@ private slots:
|
|||
void qAbstractItemModel_package_sections();
|
||||
void qAbstractItemModel_sections();
|
||||
void sectionsPositioning();
|
||||
void sectionsDelegate_data();
|
||||
void sectionsDelegate();
|
||||
void sectionsDragOutsideBounds_data();
|
||||
void sectionsDragOutsideBounds();
|
||||
|
@ -280,6 +281,8 @@ private slots:
|
|||
void touchCancel();
|
||||
void resizeAfterComponentComplete();
|
||||
|
||||
void delegateWithRequiredProperties();
|
||||
|
||||
private:
|
||||
template <class T> void items(const QUrl &source);
|
||||
template <class T> void changed(const QUrl &source);
|
||||
|
@ -2158,8 +2161,17 @@ void tst_QQuickListView::sections(const QUrl &source)
|
|||
QTRY_COMPARE(item->height(), 40.0);
|
||||
}
|
||||
|
||||
void tst_QQuickListView::sectionsDelegate_data()
|
||||
{
|
||||
QTest::addColumn<QUrl>("path");
|
||||
QTest::addRow("implicit") << testFileUrl("listview-sections_delegate.qml");
|
||||
QTest::addRow("required") << testFileUrl("listview-sections_delegate_required.qml");
|
||||
}
|
||||
|
||||
void tst_QQuickListView::sectionsDelegate()
|
||||
{
|
||||
QFETCH(QUrl, path);
|
||||
|
||||
QScopedPointer<QQuickView> window(createView());
|
||||
|
||||
QaimModel model;
|
||||
|
@ -2169,7 +2181,7 @@ void tst_QQuickListView::sectionsDelegate()
|
|||
QQmlContext *ctxt = window->rootContext();
|
||||
ctxt->setContextProperty("testModel", &model);
|
||||
|
||||
window->setSource(testFileUrl("listview-sections_delegate.qml"));
|
||||
window->setSource(path);
|
||||
window->show();
|
||||
QVERIFY(QTest::qWaitForWindowExposed(window.data()));
|
||||
|
||||
|
@ -9037,6 +9049,90 @@ void tst_QQuickListView::resizeAfterComponentComplete() // QTBUG-76487
|
|||
QTRY_COMPARE(lastItem->property("y").toInt(), 9 * lastItem->property("height").toInt());
|
||||
}
|
||||
|
||||
class Animal
|
||||
{
|
||||
public:
|
||||
Animal(const int cost, const QString &name) {m_name = name; m_cost = cost;}
|
||||
|
||||
int cost() const {return m_cost;}
|
||||
QString name() const {return m_name;}
|
||||
|
||||
QString m_name;
|
||||
int m_cost;
|
||||
};
|
||||
|
||||
class FruitModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum AnimalRoles {
|
||||
NameRole = Qt::UserRole + 1,
|
||||
CostRole
|
||||
};
|
||||
|
||||
FruitModel(QObject* = nullptr) {
|
||||
m_animals.push_back(Animal {4, QLatin1String("Melon")});
|
||||
m_animals.push_back(Animal {5, QLatin1String("Cherry")});
|
||||
}
|
||||
|
||||
int rowCount(const QModelIndex & = QModelIndex()) const override {return m_animals.count();}
|
||||
|
||||
QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const override {
|
||||
if (!checkIndex(index))
|
||||
return {};
|
||||
const Animal &animal = m_animals[index.row()];
|
||||
if (role == CostRole)
|
||||
return animal.cost();
|
||||
else if (role == NameRole)
|
||||
return animal.name();
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
protected:
|
||||
QHash<int, QByteArray> roleNames() const override {
|
||||
QHash<int, QByteArray> roles;
|
||||
roles[CostRole] = "cost";
|
||||
roles[NameRole] = "name";
|
||||
return roles;
|
||||
}
|
||||
private:
|
||||
QList<Animal> m_animals;
|
||||
};
|
||||
|
||||
void tst_QQuickListView::delegateWithRequiredProperties()
|
||||
{
|
||||
FruitModel myModel;
|
||||
qmlRegisterSingletonInstance("Qt.fruit", 1, 0, "FruitModelCpp", &myModel);
|
||||
{
|
||||
// ListModel
|
||||
QTest::ignoreMessage(QtMsgType::QtDebugMsg, "Apple2");
|
||||
QTest::ignoreMessage(QtMsgType::QtDebugMsg, "Orange3");
|
||||
QTest::ignoreMessage(QtMsgType::QtDebugMsg, "Banana1");
|
||||
QScopedPointer<QQuickView> window(createView());
|
||||
window->setInitialProperties({{QLatin1String("useCpp"), false}});
|
||||
window->setSource(testFileUrl("delegatesWithRequiredProperties.qml"));
|
||||
window->show();
|
||||
QVERIFY(QTest::qWaitForWindowExposed(window.data()));
|
||||
|
||||
QObject *listView = window->rootObject();
|
||||
QVERIFY(listView);
|
||||
}
|
||||
{
|
||||
// C++ model
|
||||
QTest::ignoreMessage(QtMsgType::QtDebugMsg, "Melon4");
|
||||
QTest::ignoreMessage(QtMsgType::QtDebugMsg, "Cherry5");
|
||||
QScopedPointer<QQuickView> window(createView());
|
||||
window->setInitialProperties({{QLatin1String("useCpp"), true}});
|
||||
window->setSource(testFileUrl("delegatesWithRequiredProperties.qml"));
|
||||
|
||||
window->show();
|
||||
QVERIFY(QTest::qWaitForWindowExposed(window.data()));
|
||||
|
||||
QObject *listView = window->rootObject();
|
||||
QVERIFY(listView);
|
||||
}
|
||||
}
|
||||
|
||||
QTEST_MAIN(tst_QQuickListView)
|
||||
|
||||
#include "tst_qquicklistview.moc"
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
import QtQuick 2.14
|
||||
|
||||
Item {
|
||||
id: root
|
||||
width: 800
|
||||
height: 600
|
||||
property bool working: false
|
||||
|
||||
ListModel {
|
||||
id: myModel
|
||||
ListElement {
|
||||
name: "Bill Jones"
|
||||
place: "Berlin"
|
||||
}
|
||||
ListElement {
|
||||
name: "Jane Doe"
|
||||
place: "Oslo"
|
||||
}
|
||||
ListElement {
|
||||
name: "John Smith"
|
||||
place: "Oulo"
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: delegateComponent
|
||||
Rectangle {
|
||||
id: myDelegate
|
||||
height: 50
|
||||
width: 50
|
||||
required property string name
|
||||
required property int index
|
||||
onNameChanged: () => {if (myDelegate.name === "You-know-who") root.working = true}
|
||||
Text {
|
||||
text: myDelegate.name
|
||||
font.pointSize: 10
|
||||
anchors.fill: myDelegate
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PathView {
|
||||
anchors.fill: parent
|
||||
model: myModel
|
||||
delegate: delegateComponent
|
||||
path: Path {
|
||||
startX: 80; startY: 100
|
||||
PathQuad { x: 120; y: 25; controlX: 260; controlY: 75 }
|
||||
PathQuad { x: 140; y: 100; controlX: -20; controlY: 75 }
|
||||
}
|
||||
}
|
||||
Timer {
|
||||
interval: 1
|
||||
running: true
|
||||
repeat: false
|
||||
onTriggered: () => { myModel.setProperty(1, "name", "You-know-who"); }
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
import QtQuick 2.14
|
||||
|
||||
Item {
|
||||
width: 400
|
||||
height: 200
|
||||
|
||||
ListModel {
|
||||
id: myModel
|
||||
ListElement {
|
||||
name: "Bill Jones"
|
||||
place: "Berlin"
|
||||
}
|
||||
ListElement {
|
||||
name: "Jane Doe"
|
||||
place: "Oslo"
|
||||
}
|
||||
ListElement {
|
||||
name: "John Smith"
|
||||
place: "Oulo"
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: delegateComponent
|
||||
Rectangle {
|
||||
id: myDelegate
|
||||
required property int index
|
||||
required property string name
|
||||
required property string place
|
||||
height: 100
|
||||
width: 100
|
||||
Text {
|
||||
text: myDelegate.name + " lives in " + myDelegate.place + myDelegate.index
|
||||
font.pointSize: 16
|
||||
anchors.fill: myDelegate
|
||||
|
||||
Component.onCompleted: () => {console.info(myDelegate.name+myDelegate.place+myDelegate.index)}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PathView {
|
||||
anchors.fill: parent
|
||||
model: myModel
|
||||
delegate: delegateComponent
|
||||
path: Path {
|
||||
startX: 120; startY: 100
|
||||
PathQuad { x: 120; y: 25; controlX: 260; controlY: 75 }
|
||||
PathQuad { x: 120; y: 100; controlX: -20; controlY: 75 }
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
import QtQuick 2.14
|
||||
|
||||
Item {
|
||||
width: 400
|
||||
height: 200
|
||||
|
||||
ListModel {
|
||||
id: myModel
|
||||
ListElement {
|
||||
name: "Bill Jones"
|
||||
place: "Berlin"
|
||||
}
|
||||
ListElement {
|
||||
name: "Jane Doe"
|
||||
place: "Oslo"
|
||||
}
|
||||
ListElement {
|
||||
name: "John Smith"
|
||||
place: "Oulo"
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: delegateComponent
|
||||
Rectangle {
|
||||
id: myDelegate
|
||||
required property int set
|
||||
set: 42
|
||||
height: 100
|
||||
width: 100
|
||||
Text {
|
||||
text: "Test"
|
||||
font.pointSize: 16
|
||||
anchors.fill: myDelegate
|
||||
|
||||
Component.onCompleted: () => { try {index; console.log(index); console.log(name)} catch(ex) {console.info(ex.name)} }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PathView {
|
||||
anchors.fill: parent
|
||||
model: myModel
|
||||
delegate: delegateComponent
|
||||
path: Path {
|
||||
startX: 120; startY: 100
|
||||
PathQuad { x: 120; y: 25; controlX: 260; controlY: 75 }
|
||||
PathQuad { x: 120; y: 100; controlX: -20; controlY: 75 }
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -149,6 +149,8 @@ private slots:
|
|||
void movementDirection();
|
||||
void removePath();
|
||||
void objectModelMove();
|
||||
void requiredPropertiesInDelegate();
|
||||
void requiredPropertiesInDelegatePreventUnrelated();
|
||||
};
|
||||
|
||||
class TestObject : public QObject
|
||||
|
@ -2658,6 +2660,34 @@ void tst_QQuickPathView::objectModelMove()
|
|||
}
|
||||
}
|
||||
|
||||
void tst_QQuickPathView::requiredPropertiesInDelegate()
|
||||
{
|
||||
{
|
||||
QTest::ignoreMessage(QtMsgType::QtInfoMsg, "Bill JonesBerlin0");
|
||||
QTest::ignoreMessage(QtMsgType::QtInfoMsg, "Jane DoeOslo1");
|
||||
QTest::ignoreMessage(QtMsgType::QtInfoMsg, "John SmithOulo2");
|
||||
QScopedPointer<QQuickView> window(createView());
|
||||
window->setSource(testFileUrl("delegateWithRequiredProperties.qml"));
|
||||
window->show();
|
||||
}
|
||||
{
|
||||
QScopedPointer<QQuickView> window(createView());
|
||||
window->setSource(testFileUrl("delegateWithRequiredProperties.2.qml"));
|
||||
window->show();
|
||||
QTRY_VERIFY(window->rootObject()->property("working").toBool());
|
||||
}
|
||||
}
|
||||
|
||||
void tst_QQuickPathView::requiredPropertiesInDelegatePreventUnrelated()
|
||||
{
|
||||
QTest::ignoreMessage(QtMsgType::QtInfoMsg, "ReferenceError");
|
||||
QTest::ignoreMessage(QtMsgType::QtInfoMsg, "ReferenceError");
|
||||
QTest::ignoreMessage(QtMsgType::QtInfoMsg, "ReferenceError");
|
||||
QScopedPointer<QQuickView> window(createView());
|
||||
window->setSource(testFileUrl("delegatewithUnrelatedRequiredPreventsAccessToModel.qml"));
|
||||
window->show();
|
||||
}
|
||||
|
||||
QTEST_MAIN(tst_QQuickPathView)
|
||||
|
||||
#include "tst_qquickpathview.moc"
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
import QtQuick 2.0
|
||||
|
||||
Rectangle {
|
||||
id: container
|
||||
objectName: "container"
|
||||
width: 240
|
||||
height: 320
|
||||
color: "white"
|
||||
Repeater {
|
||||
id: repeater
|
||||
objectName: "repeater"
|
||||
model: testData
|
||||
property int errors: 0
|
||||
property int instantiated: 0
|
||||
Component {
|
||||
Item{
|
||||
required property int index
|
||||
required property int idx
|
||||
Component.onCompleted: {if (index != idx) repeater.errors += 1; repeater.instantiated++}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
import QtQuick 2.14
|
||||
|
||||
Item {
|
||||
Column {
|
||||
Repeater {
|
||||
model: ["apples", "oranges", "pears"]
|
||||
Text {
|
||||
id: txt
|
||||
required property string modelData
|
||||
required property int index
|
||||
text: modelData + index
|
||||
Component.onCompleted: () => {console.info(txt.text)}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -55,6 +55,7 @@ public:
|
|||
|
||||
private slots:
|
||||
void numberModel();
|
||||
void objectList_data();
|
||||
void objectList();
|
||||
void stringList();
|
||||
void dataModel_adding();
|
||||
|
@ -79,6 +80,7 @@ private slots:
|
|||
void QTBUG54859_asynchronousMove();
|
||||
void package();
|
||||
void ownership();
|
||||
void requiredProperties();
|
||||
};
|
||||
|
||||
class TestObject : public QObject
|
||||
|
@ -143,6 +145,14 @@ void tst_QQuickRepeater::numberModel()
|
|||
delete window;
|
||||
}
|
||||
|
||||
void tst_QQuickRepeater::objectList_data()
|
||||
{
|
||||
QTest::addColumn<QUrl>("filename");
|
||||
|
||||
QTest::newRow("normal") << testFileUrl("objlist.qml");
|
||||
QTest::newRow("required") << testFileUrl("objlist_required.qml");
|
||||
}
|
||||
|
||||
class MyObject : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
@ -157,6 +167,7 @@ public:
|
|||
|
||||
void tst_QQuickRepeater::objectList()
|
||||
{
|
||||
QFETCH(QUrl, filename);
|
||||
QQuickView *window = createView();
|
||||
QObjectList data;
|
||||
for (int i=0; i<100; i++)
|
||||
|
@ -165,7 +176,7 @@ void tst_QQuickRepeater::objectList()
|
|||
QQmlContext *ctxt = window->rootContext();
|
||||
ctxt->setContextProperty("testData", QVariant::fromValue(data));
|
||||
|
||||
window->setSource(testFileUrl("objlist.qml"));
|
||||
window->setSource(filename);
|
||||
qApp->processEvents();
|
||||
|
||||
QQuickRepeater *repeater = findItem<QQuickRepeater>(window->rootObject(), "repeater");
|
||||
|
@ -1108,6 +1119,18 @@ void tst_QQuickRepeater::ownership()
|
|||
QVERIFY(!modelGuard);
|
||||
}
|
||||
|
||||
void tst_QQuickRepeater::requiredProperties()
|
||||
{
|
||||
QTest::ignoreMessage(QtMsgType::QtInfoMsg, "apples0");
|
||||
QTest::ignoreMessage(QtMsgType::QtInfoMsg, "oranges1");
|
||||
QTest::ignoreMessage(QtMsgType::QtInfoMsg, "pears2");
|
||||
QQmlEngine engine;
|
||||
|
||||
QQmlComponent component(&engine, testFileUrl("requiredProperty.qml"));
|
||||
QScopedPointer<QObject> o {component.create()};
|
||||
QVERIFY(o);
|
||||
}
|
||||
|
||||
QTEST_MAIN(tst_QQuickRepeater)
|
||||
|
||||
#include "tst_qquickrepeater.moc"
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2018 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of the QtQuick module of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:LGPL$
|
||||
** 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 Lesser General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU Lesser
|
||||
** General Public License version 3 as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.LGPL3 included in the
|
||||
** packaging of this file. Please review the following information to
|
||||
** ensure the GNU Lesser General Public License version 3 requirements
|
||||
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 2.0 or (at your option) the GNU General
|
||||
** Public license version 3 or any later version approved by the KDE Free
|
||||
** Qt Foundation. The licenses are as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
|
||||
** 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-2.0.html and
|
||||
** https://www.gnu.org/licenses/gpl-3.0.html.
|
||||
**
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Window 2.3
|
||||
|
||||
Item {
|
||||
width: 640
|
||||
height: 450
|
||||
|
||||
property alias tableView: tableView
|
||||
|
||||
TableView {
|
||||
id: tableView
|
||||
width: 600
|
||||
height: 400
|
||||
delegate: tableViewDelegate
|
||||
}
|
||||
|
||||
Component {
|
||||
id: tableViewDelegate
|
||||
Rectangle {
|
||||
id: rect
|
||||
required property string position
|
||||
required property bool hasModelChildren
|
||||
required property QtObject model
|
||||
Text {text: rect.position}
|
||||
implicitWidth: 100
|
||||
implicitHeight: 100
|
||||
Component.onCompleted: () => {if (rect.position === "R1:C1" && rect.model.hasModelChildren == rect.hasModelChildren) console.info("success")}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2018 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of the QtQuick module of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:LGPL$
|
||||
** 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 Lesser General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU Lesser
|
||||
** General Public License version 3 as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.LGPL3 included in the
|
||||
** packaging of this file. Please review the following information to
|
||||
** ensure the GNU Lesser General Public License version 3 requirements
|
||||
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 2.0 or (at your option) the GNU General
|
||||
** Public license version 3 or any later version approved by the KDE Free
|
||||
** Qt Foundation. The licenses are as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
|
||||
** 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-2.0.html and
|
||||
** https://www.gnu.org/licenses/gpl-3.0.html.
|
||||
**
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Window 2.3
|
||||
|
||||
Item {
|
||||
width: 640
|
||||
height: 450
|
||||
|
||||
property alias tableView: tableView
|
||||
|
||||
TableView {
|
||||
id: tableView
|
||||
width: 600
|
||||
height: 400
|
||||
delegate: tableViewDelegate
|
||||
}
|
||||
|
||||
Component {
|
||||
id: tableViewDelegate
|
||||
Rectangle {
|
||||
id: rect
|
||||
required property string position
|
||||
required property bool unset
|
||||
required property bool hasModelChildren
|
||||
required property QtObject model
|
||||
Text {text: rect.position}
|
||||
implicitWidth: 100
|
||||
implicitHeight: 100
|
||||
Component.onCompleted: () => {if (rect.position === "R1:C1" && rect.model.hasModelChildren == rect.hasModelChildren) console.info("success")}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -172,6 +172,7 @@ private slots:
|
|||
void checkSyncView_differentSizedModels();
|
||||
void checkSyncView_connect_late_data();
|
||||
void checkSyncView_connect_late();
|
||||
void delegateWithRequiredProperties();
|
||||
};
|
||||
|
||||
tst_QQuickTableView::tst_QQuickTableView()
|
||||
|
@ -2592,6 +2593,50 @@ void tst_QQuickTableView::checkSyncView_connect_late()
|
|||
|
||||
}
|
||||
|
||||
void tst_QQuickTableView::delegateWithRequiredProperties()
|
||||
{
|
||||
constexpr static int PositionRole = Qt::UserRole+1;
|
||||
struct MyTable : QAbstractTableModel {
|
||||
|
||||
|
||||
using QAbstractTableModel::QAbstractTableModel;
|
||||
|
||||
int rowCount(const QModelIndex& = QModelIndex()) const override {
|
||||
return 3;
|
||||
}
|
||||
|
||||
int columnCount(const QModelIndex& = QModelIndex()) const override {
|
||||
return 3;
|
||||
}
|
||||
|
||||
QVariant data(const QModelIndex &index, int = Qt::DisplayRole) const override {
|
||||
return QVariant::fromValue(QString::asprintf("R%d:C%d", index.row(), index.column()));
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> roleNames() const override {
|
||||
return QHash<int, QByteArray> { {PositionRole, "position"} };
|
||||
}
|
||||
};
|
||||
|
||||
auto model = QVariant::fromValue(QSharedPointer<MyTable>(new MyTable));
|
||||
{
|
||||
QTest::ignoreMessage(QtMsgType::QtInfoMsg, "success");
|
||||
LOAD_TABLEVIEW("delegateWithRequired.qml")
|
||||
QVERIFY(tableView);
|
||||
tableView->setModel(model);
|
||||
WAIT_UNTIL_POLISHED;
|
||||
QVERIFY(view->errors().empty());
|
||||
}
|
||||
{
|
||||
QTest::ignoreMessage(QtMsgType::QtWarningMsg, QRegularExpression(R"|(TableView: failed loading index: \d)|"));
|
||||
LOAD_TABLEVIEW("delegatewithRequiredUnset.qml")
|
||||
QVERIFY(tableView);
|
||||
tableView->setModel(model);
|
||||
WAIT_UNTIL_POLISHED;
|
||||
QTRY_VERIFY(view->errors().empty());
|
||||
}
|
||||
}
|
||||
|
||||
QTEST_MAIN(tst_QQuickTableView)
|
||||
|
||||
#include "tst_qquicktableview.moc"
|
||||
|
|
Loading…
Reference in New Issue