Merge "Merge remote-tracking branch 'origin/5.15' into dev"

This commit is contained in:
Qt Forward Merge Bot 2019-09-18 01:00:20 +02:00
commit 2c5c3fee1c
31 changed files with 944 additions and 40 deletions

View File

@ -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

View File

@ -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
*/

View File

@ -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.
*/

View File

@ -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);

View File

@ -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;

View File

@ -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);

View File

@ -345,6 +345,11 @@ RequiredProperties &QQmlComponentPrivate::requiredProperties()
return state.creator->requiredProperties();
}
bool QQmlComponentPrivate::hadRequiredProperties() const
{
return state.creator->componentHadRequiredProperties();
}
void QQmlComponentPrivate::clear()
{
if (typeData) {

View File

@ -108,6 +108,7 @@ public:
int start;
RequiredProperties& requiredProperties();
bool hadRequiredProperties() const;
QQmlRefPointer<QV4::ExecutableCompilationUnit> compilationUnit;
struct ConstructionState {

View File

@ -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

View File

@ -108,6 +108,7 @@ public:
void forceCompletion(QQmlInstantiationInterrupt &i);
void incubate(QQmlInstantiationInterrupt &i);
RequiredProperties &requiredProperties();
bool hadRequiredProperties() const;
};
QT_END_NAMESPACE

View File

@ -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, {}});
}

View File

@ -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;

View File

@ -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;

View File

@ -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 &);

View File

@ -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);

View File

@ -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

View File

@ -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)

View File

@ -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 &section);
};
//----------------------------------------------------------------------------
@ -992,14 +995,20 @@ QQuickItem * QQuickListViewPrivate::getSectionItem(const QString &section)
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 &section)
{
if (context->contextProperty(QLatin1String("section")).isValid())
context->setContextProperty(QLatin1String("section"), section);
else
sectionItem->setProperty("section", section);
}
//----------------------------------------------------------------------------
/*!

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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"

View File

@ -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"); }
}
}

View File

@ -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 }
}
}
}

View File

@ -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 }
}
}
}

View File

@ -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"

View File

@ -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++}
}
}
}
}

View File

@ -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)}
}
}
}
}

View File

@ -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"

View File

@ -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")}
}
}
}

View File

@ -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")}
}
}
}

View File

@ -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"