aboutsummaryrefslogtreecommitdiffstats
path: root/src/qml/types
diff options
context:
space:
mode:
authorLaszlo Agocs <laszlo.agocs@qt.io>2019-03-21 20:42:38 +0100
committerLaszlo Agocs <laszlo.agocs@qt.io>2019-03-21 20:42:47 +0100
commit6767114285db9d0e16dc278d08f231e8561546b4 (patch)
tree0945902a2242fd7ec0a1f7fd3e6acbb769e723bd /src/qml/types
parentee076afedccbe1d37306a7972051f84eb036d655 (diff)
parentc32b109e9dea44c6775c2dbf8f164870c1dc8971 (diff)
Merge remote-tracking branch 'origin/dev' into wip/scenegraphng
Diffstat (limited to 'src/qml/types')
-rw-r--r--src/qml/types/qqmlbind.cpp8
-rw-r--r--src/qml/types/qqmlbind_p.h2
-rw-r--r--src/qml/types/qqmlconnections.cpp26
-rw-r--r--src/qml/types/qqmlconnections_p.h8
-rw-r--r--src/qml/types/qqmldelegatecomponent.cpp300
-rw-r--r--src/qml/types/qqmldelegatecomponent_p.h155
-rw-r--r--src/qml/types/qqmldelegatemodel.cpp824
-rw-r--r--src/qml/types/qqmldelegatemodel_p.h18
-rw-r--r--src/qml/types/qqmldelegatemodel_p_p.h65
-rw-r--r--src/qml/types/qqmlinstantiator.cpp47
-rw-r--r--src/qml/types/qqmlinstantiator_p.h5
-rw-r--r--src/qml/types/qqmlinstantiator_p_p.h9
-rw-r--r--src/qml/types/qqmlitemmodels.qdoc10
-rw-r--r--src/qml/types/qqmlitemselectionmodel.qdoc116
-rw-r--r--src/qml/types/qqmllistmodel.cpp900
-rw-r--r--src/qml/types/qqmllistmodel_p.h38
-rw-r--r--src/qml/types/qqmllistmodel_p_p.h78
-rw-r--r--src/qml/types/qqmllistmodelworkeragent.cpp91
-rw-r--r--src/qml/types/qqmllistmodelworkeragent_p.h31
-rw-r--r--src/qml/types/qqmlmodelsmodule.cpp20
-rw-r--r--src/qml/types/qqmlmodelsmodule_p.h1
-rw-r--r--src/qml/types/qqmlobjectmodel.cpp38
-rw-r--r--src/qml/types/qqmlobjectmodel_p.h13
-rw-r--r--src/qml/types/qqmltableinstancemodel.cpp547
-rw-r--r--src/qml/types/qqmltableinstancemodel_p.h162
-rw-r--r--src/qml/types/qqmltablemodel.cpp950
-rw-r--r--src/qml/types/qqmltablemodel_p.h158
-rw-r--r--src/qml/types/qqmltimer.cpp6
-rw-r--r--src/qml/types/qqmltimer_p.h4
-rw-r--r--src/qml/types/qquickpackage.cpp4
-rw-r--r--src/qml/types/qquickpackage_p.h2
-rw-r--r--src/qml/types/qquickworkerscript.cpp326
-rw-r--r--src/qml/types/qquickworkerscript_p.h6
-rw-r--r--src/qml/types/types.pri49
34 files changed, 3902 insertions, 1115 deletions
diff --git a/src/qml/types/qqmlbind.cpp b/src/qml/types/qqmlbind.cpp
index da644becc2..513f7f2997 100644
--- a/src/qml/types/qqmlbind.cpp
+++ b/src/qml/types/qqmlbind.cpp
@@ -60,7 +60,7 @@ QT_BEGIN_NAMESPACE
class QQmlBindPrivate : public QObjectPrivate
{
public:
- QQmlBindPrivate() : obj(0), componentComplete(true), delayed(false), pendingEval(false) {}
+ QQmlBindPrivate() : obj(nullptr), componentComplete(true), delayed(false), pendingEval(false) {}
~QQmlBindPrivate() { }
QQmlNullableValue<bool> when;
@@ -102,7 +102,7 @@ void QQmlBindPrivate::validate(QObject *binding) const
In QML, property bindings result in a dependency between the properties of
different objects.
- \section1 Binding to an inaccessible property
+ \section1 Binding to an Inaccessible Property
Sometimes it is necessary to bind an object's property to
that of another object that isn't directly instantiated by QML, such as a
@@ -120,7 +120,7 @@ void QQmlBindPrivate::validate(QObject *binding) const
When \c{text} changes, the C++ property \c{enteredText} will update
automatically.
- \section1 Conditional bindings
+ \section1 Conditional Bindings
In some cases you may want to modify the value of a property when a certain
condition is met but leave it unmodified otherwise. Often, it's not possible
@@ -370,7 +370,7 @@ void QQmlBind::eval()
//restore any previous binding
if (d->prevBind) {
QQmlAbstractBinding::Ptr p = d->prevBind;
- d->prevBind = 0;
+ d->prevBind = nullptr;
QQmlPropertyPrivate::setBinding(p.data());
}
return;
diff --git a/src/qml/types/qqmlbind_p.h b/src/qml/types/qqmlbind_p.h
index c9dd14b58a..5bf9ef85c6 100644
--- a/src/qml/types/qqmlbind_p.h
+++ b/src/qml/types/qqmlbind_p.h
@@ -71,7 +71,7 @@ class Q_AUTOTEST_EXPORT QQmlBind : public QObject, public QQmlPropertyValueSourc
Q_PROPERTY(bool delayed READ delayed WRITE setDelayed REVISION 8)
public:
- QQmlBind(QObject *parent=0);
+ QQmlBind(QObject *parent=nullptr);
~QQmlBind();
bool when() const;
diff --git a/src/qml/types/qqmlconnections.cpp b/src/qml/types/qqmlconnections.cpp
index e218cdcfe4..f601087690 100644
--- a/src/qml/types/qqmlconnections.cpp
+++ b/src/qml/types/qqmlconnections.cpp
@@ -56,7 +56,7 @@ QT_BEGIN_NAMESPACE
class QQmlConnectionsPrivate : public QObjectPrivate
{
public:
- QQmlConnectionsPrivate() : target(0), enabled(true), targetSet(false), ignoreUnknownSignals(false), componentcomplete(true) {}
+ QQmlConnectionsPrivate() : target(nullptr), enabled(true), targetSet(false), ignoreUnknownSignals(false), componentcomplete(true) {}
QList<QQmlBoundSignal*> boundsignals;
QObject *target;
@@ -75,7 +75,7 @@ public:
\instantiates QQmlConnections
\inqmlmodule QtQml
\ingroup qtquick-interceptors
- \brief Describes generalized connections to signals
+ \brief Describes generalized connections to signals.
A Connections object creates a connection to a QML signal.
@@ -231,11 +231,11 @@ void QQmlConnections::setIgnoreUnknownSignals(bool ignore)
d->ignoreUnknownSignals = ignore;
}
-void QQmlConnectionsParser::verifyBindings(const QV4::CompiledData::Unit *qmlUnit, const QList<const QV4::CompiledData::Binding *> &props)
+void QQmlConnectionsParser::verifyBindings(const QQmlRefPointer<QV4::CompiledData::CompilationUnit> &compilationUnit, const QList<const QV4::CompiledData::Binding *> &props)
{
for (int ii = 0; ii < props.count(); ++ii) {
const QV4::CompiledData::Binding *binding = props.at(ii);
- const QString &propName = qmlUnit->stringAt(binding->propertyNameIndex);
+ const QString &propName = compilationUnit->stringAt(binding->propertyNameIndex);
if (!propName.startsWith(QLatin1String("on")) || (propName.length() < 3 || !propName.at(2).isUpper())) {
error(props.at(ii), QQmlConnections::tr("Cannot assign to non-existent property \"%1\"").arg(propName));
@@ -243,8 +243,8 @@ void QQmlConnectionsParser::verifyBindings(const QV4::CompiledData::Unit *qmlUni
}
if (binding->type >= QV4::CompiledData::Binding::Type_Object) {
- const QV4::CompiledData::Object *target = qmlUnit->objectAt(binding->value.objectIndex);
- if (!qmlUnit->stringAt(target->inheritedTypeNameIndex).isEmpty())
+ const QV4::CompiledData::Object *target = compilationUnit->objectAt(binding->value.objectIndex);
+ if (!compilationUnit->stringAt(target->inheritedTypeNameIndex).isEmpty())
error(binding, QQmlConnections::tr("Connections: nested objects not allowed"));
else
error(binding, QQmlConnections::tr("Connections: syntax error"));
@@ -256,7 +256,7 @@ void QQmlConnectionsParser::verifyBindings(const QV4::CompiledData::Unit *qmlUni
}
}
-void QQmlConnectionsParser::applyBindings(QObject *object, QV4::CompiledData::CompilationUnit *compilationUnit, const QList<const QV4::CompiledData::Binding *> &bindings)
+void QQmlConnectionsParser::applyBindings(QObject *object, const QQmlRefPointer<QV4::CompiledData::CompilationUnit> &compilationUnit, const QList<const QV4::CompiledData::Binding *> &bindings)
{
QQmlConnectionsPrivate *p =
static_cast<QQmlConnectionsPrivate *>(QObjectPrivate::get(object));
@@ -274,12 +274,11 @@ void QQmlConnections::connectSignals()
return;
QObject *target = this->target();
QQmlData *ddata = QQmlData::get(this);
- QQmlContextData *ctxtdata = ddata ? ddata->outerContext : 0;
+ QQmlContextData *ctxtdata = ddata ? ddata->outerContext : nullptr;
- const QV4::CompiledData::Unit *qmlUnit = d->compilationUnit->data;
for (const QV4::CompiledData::Binding *binding : qAsConst(d->bindings)) {
Q_ASSERT(binding->type == QV4::CompiledData::Binding::Type_Script);
- QString propName = qmlUnit->stringAt(binding->propertyNameIndex);
+ QString propName = d->compilationUnit->stringAt(binding->propertyNameIndex);
QQmlProperty prop(target, propName);
if (prop.isValid() && (prop.type() & QQmlProperty::SignalProperty)) {
@@ -288,9 +287,10 @@ void QQmlConnections::connectSignals()
new QQmlBoundSignal(target, signalIndex, this, qmlEngine(this));
signal->setEnabled(d->enabled);
- QQmlBoundSignalExpression *expression = ctxtdata ?
- new QQmlBoundSignalExpression(target, signalIndex,
- ctxtdata, this, d->compilationUnit->runtimeFunctions[binding->value.compiledScriptIndex]) : 0;
+ auto f = d->compilationUnit->runtimeFunctions[binding->value.compiledScriptIndex];
+ QQmlBoundSignalExpression *expression =
+ ctxtdata ? new QQmlBoundSignalExpression(target, signalIndex, ctxtdata, this, f)
+ : nullptr;
signal->takeExpression(expression);
d->boundsignals += signal;
} else {
diff --git a/src/qml/types/qqmlconnections_p.h b/src/qml/types/qqmlconnections_p.h
index 580b6522de..bd03d7e152 100644
--- a/src/qml/types/qqmlconnections_p.h
+++ b/src/qml/types/qqmlconnections_p.h
@@ -69,11 +69,11 @@ class Q_AUTOTEST_EXPORT QQmlConnections : public QObject, public QQmlParserStatu
Q_INTERFACES(QQmlParserStatus)
Q_PROPERTY(QObject *target READ target WRITE setTarget NOTIFY targetChanged)
- Q_REVISION(1) Q_PROPERTY(bool enabled READ isEnabled WRITE setEnabled NOTIFY enabledChanged)
+ Q_PROPERTY(bool enabled READ isEnabled WRITE setEnabled NOTIFY enabledChanged REVISION 1)
Q_PROPERTY(bool ignoreUnknownSignals READ ignoreUnknownSignals WRITE setIgnoreUnknownSignals)
public:
- QQmlConnections(QObject *parent=0);
+ QQmlConnections(QObject *parent=nullptr);
~QQmlConnections();
QObject *target() const;
@@ -98,8 +98,8 @@ private:
class QQmlConnectionsParser : public QQmlCustomParser
{
public:
- void verifyBindings(const QV4::CompiledData::Unit *qmlUnit, const QList<const QV4::CompiledData::Binding *> &props) override;
- void applyBindings(QObject *object, QV4::CompiledData::CompilationUnit *compilationUnit, const QList<const QV4::CompiledData::Binding *> &bindings) override;
+ void verifyBindings(const QQmlRefPointer<QV4::CompiledData::CompilationUnit> &compilationUnit, const QList<const QV4::CompiledData::Binding *> &props) override;
+ void applyBindings(QObject *object, const QQmlRefPointer<QV4::CompiledData::CompilationUnit> &compilationUnit, const QList<const QV4::CompiledData::Binding *> &bindings) override;
};
diff --git a/src/qml/types/qqmldelegatecomponent.cpp b/src/qml/types/qqmldelegatecomponent.cpp
new file mode 100644
index 0000000000..470f6cab6a
--- /dev/null
+++ b/src/qml/types/qqmldelegatecomponent.cpp
@@ -0,0 +1,300 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQml module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE: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$
+**
+****************************************************************************/
+
+#include "qqmldelegatecomponent_p.h"
+#include <QtQml/private/qqmladaptormodel_p.h>
+
+QT_BEGIN_NAMESPACE
+
+QQmlAbstractDelegateComponent::QQmlAbstractDelegateComponent(QObject *parent)
+ : QQmlComponent(parent)
+{
+}
+
+QQmlAbstractDelegateComponent::~QQmlAbstractDelegateComponent()
+{
+}
+
+QVariant QQmlAbstractDelegateComponent::value(QQmlAdaptorModel *adaptorModel, int row, int column, const QString &role) const
+{
+ if (!adaptorModel)
+ return QVariant();
+ return adaptorModel->value(adaptorModel->indexAt(row, column), role);
+}
+
+/*!
+ \qmlmodule Qt.labs.qmlmodels 1.0
+ \title Qt Labs QML Models - QML Types
+ \ingroup qmlmodules
+ \brief The Qt Labs QML Models module provides various model-related types for use with views.
+
+ To use this module, import the module with the following line:
+
+ \qml
+ import Qt.labs.qmlmodels 1.0
+ \endqml
+*/
+
+/*!
+ \qmltype DelegateChoice
+ \instantiates QQmlDelegateChoice
+ \inqmlmodule Qt.labs.qmlmodels
+ \brief Encapsulates a delegate and when to use it.
+
+ The DelegateChoice type wraps a delegate and defines the circumstances
+ in which it should be chosen.
+
+ DelegateChoices can be nested inside a DelegateChooser.
+
+ \sa DelegateChooser
+*/
+
+/*!
+ \qmlproperty string QtQml.Models::DelegateChoice::roleValue
+ This property holds the value used to match the role data for the role provided by \l DelegateChooser::role.
+*/
+QVariant QQmlDelegateChoice::roleValue() const
+{
+ return m_value;
+}
+
+void QQmlDelegateChoice::setRoleValue(const QVariant &value)
+{
+ if (m_value == value)
+ return;
+ m_value = value;
+ emit roleValueChanged();
+ emit changed();
+}
+
+/*!
+ \qmlproperty index QtQml.Models::DelegateChoice::row
+ This property holds the value used to match the row value of model elements.
+ With models that have only the index property (and thus only one column), this property
+ should be intended as an index, and set to the desired index value.
+
+ \note Setting both row and index has undefined behavior. The two are equivalent and only
+ one should be used.
+
+ \sa index
+*/
+
+/*!
+ \qmlproperty index QtQml.Models::DelegateChoice::index
+ This property holds the value used to match the index value of model elements.
+ This is effectively an alias for \l row.
+
+ \sa row
+*/
+int QQmlDelegateChoice::row() const
+{
+ return m_row;
+}
+
+void QQmlDelegateChoice::setRow(int r)
+{
+ if (m_row == r)
+ return;
+ m_row = r;
+ emit rowChanged();
+ emit indexChanged();
+ emit changed();
+}
+
+/*!
+ \qmlproperty index QtQml.Models::DelegateChoice::column
+ This property holds the value used to match the column value of model elements.
+*/
+int QQmlDelegateChoice::column() const
+{
+ return m_column;
+}
+
+void QQmlDelegateChoice::setColumn(int c)
+{
+ if (m_column == c)
+ return;
+ m_column = c;
+ emit columnChanged();
+ emit changed();
+}
+
+QQmlComponent *QQmlDelegateChoice::delegate() const
+{
+ return m_delegate;
+}
+
+/*!
+ \qmlproperty Component QtQml.Models::DelegateChoice::delegate
+ This property holds the delegate to use if this choice matches the model item.
+*/
+void QQmlDelegateChoice::setDelegate(QQmlComponent *delegate)
+{
+ if (m_delegate == delegate)
+ return;
+ QQmlAbstractDelegateComponent *adc = static_cast<QQmlAbstractDelegateComponent *>(m_delegate);
+ if (adc)
+ disconnect(adc, &QQmlAbstractDelegateComponent::delegateChanged, this, &QQmlDelegateChoice::delegateChanged);
+ m_delegate = delegate;
+ adc = static_cast<QQmlAbstractDelegateComponent *>(delegate);
+ if (adc)
+ connect(adc, &QQmlAbstractDelegateComponent::delegateChanged, this, &QQmlDelegateChoice::delegateChanged);
+ emit delegateChanged();
+ emit changed();
+}
+
+bool QQmlDelegateChoice::match(int row, int column, const QVariant &value) const
+{
+ if (!m_value.isValid() && m_row < 0 && m_column < 0)
+ return true;
+
+ const bool roleMatched = (m_value.isValid()) ? value == m_value : true;
+ const bool rowMatched = (m_row < 0 ) ? true : m_row == row;
+ const bool columnMatched = (m_column < 0 ) ? true : m_column == column;
+ return roleMatched && rowMatched && columnMatched;
+}
+
+/*!
+ \qmltype DelegateChooser
+ \instantiates QQmlDelegateChooser
+ \inqmlmodule Qt.labs.qmlmodels
+ \brief Allows a view to use different delegates for different types of items in the model.
+
+ The DelegateChooser is a special \l Component type intended for those scenarios where a Component is required
+ by a view and used as a delegate.
+ DelegateChooser encapsulates a set of \l {DelegateChoice}s.
+ These choices are used determine the delegate that will be instantiated for each
+ item in the model.
+ The selection of the choice is performed based on the value that a model item has for \l role,
+ and also based on index.
+
+ \note This type is intended to transparently work only with TableView and any DelegateModel-based view.
+ Views (including user-defined views) that aren't internally based on a DelegateModel need to explicitly support
+ this type of component to make it function as described.
+
+ \sa DelegateChoice
+*/
+
+/*!
+ \qmlproperty string QtQml.Models::DelegateChooser::role
+ This property holds the role used to determine the delegate for a given model item.
+
+ \sa DelegateChoice
+*/
+void QQmlDelegateChooser::setRole(const QString &role)
+{
+ if (m_role == role)
+ return;
+ m_role = role;
+ emit roleChanged();
+}
+
+/*!
+ \qmlproperty list<DelegateChoice> QtQml.Models::DelegateChooser::choices
+ \default
+
+ The list of DelegateChoices for the chooser.
+
+ The list is treated as an ordered list, where the first DelegateChoice to match
+ will be used be a view.
+
+ It should not generally be necessary to refer to the \c choices property,
+ as it is the default property for DelegateChooser and thus all child items are
+ automatically assigned to this property.
+*/
+
+QQmlListProperty<QQmlDelegateChoice> QQmlDelegateChooser::choices()
+{
+ return QQmlListProperty<QQmlDelegateChoice>(this, nullptr,
+ QQmlDelegateChooser::choices_append,
+ QQmlDelegateChooser::choices_count,
+ QQmlDelegateChooser::choices_at,
+ QQmlDelegateChooser::choices_clear);
+}
+
+void QQmlDelegateChooser::choices_append(QQmlListProperty<QQmlDelegateChoice> *prop, QQmlDelegateChoice *choice)
+{
+ QQmlDelegateChooser *q = static_cast<QQmlDelegateChooser *>(prop->object);
+ q->m_choices.append(choice);
+ connect(choice, &QQmlDelegateChoice::changed, q, &QQmlAbstractDelegateComponent::delegateChanged);
+ q->delegateChanged();
+}
+
+int QQmlDelegateChooser::choices_count(QQmlListProperty<QQmlDelegateChoice> *prop)
+{
+ QQmlDelegateChooser *q = static_cast<QQmlDelegateChooser*>(prop->object);
+ return q->m_choices.count();
+}
+
+QQmlDelegateChoice *QQmlDelegateChooser::choices_at(QQmlListProperty<QQmlDelegateChoice> *prop, int index)
+{
+ QQmlDelegateChooser *q = static_cast<QQmlDelegateChooser*>(prop->object);
+ return q->m_choices.at(index);
+}
+
+void QQmlDelegateChooser::choices_clear(QQmlListProperty<QQmlDelegateChoice> *prop)
+{
+ QQmlDelegateChooser *q = static_cast<QQmlDelegateChooser *>(prop->object);
+ for (QQmlDelegateChoice *choice : q->m_choices)
+ disconnect(choice, &QQmlDelegateChoice::changed, q, &QQmlAbstractDelegateComponent::delegateChanged);
+ q->m_choices.clear();
+ q->delegateChanged();
+}
+
+QQmlComponent *QQmlDelegateChooser::delegate(QQmlAdaptorModel *adaptorModel, int row, int column) const
+{
+ QVariant v;
+ if (!m_role.isNull())
+ v = value(adaptorModel, row, column, m_role);
+ if (!v.isValid()) { // check if the row only has modelData, for example if the row is a QVariantMap
+ v = value(adaptorModel, row, column, QStringLiteral("modelData"));
+ if (v.isValid())
+ v = v.toMap().value(m_role);
+ }
+ // loop through choices, finding first one that fits
+ for (int i = 0; i < m_choices.count(); ++i) {
+ const QQmlDelegateChoice *choice = m_choices.at(i);
+ if (choice->match(row, column, v))
+ return choice->delegate();
+ }
+
+ return nullptr;
+}
+
+QT_END_NAMESPACE
diff --git a/src/qml/types/qqmldelegatecomponent_p.h b/src/qml/types/qqmldelegatecomponent_p.h
new file mode 100644
index 0000000000..c925ed9a60
--- /dev/null
+++ b/src/qml/types/qqmldelegatecomponent_p.h
@@ -0,0 +1,155 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQml module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE: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$
+**
+****************************************************************************/
+
+#ifndef QQMLDELEGATECOMPONENT_P_H
+#define QQMLDELEGATECOMPONENT_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <private/qtqmlglobal_p.h>
+#include <qqmlcomponent.h>
+
+QT_REQUIRE_CONFIG(qml_delegate_model);
+
+QT_BEGIN_NAMESPACE
+
+// TODO: consider making QQmlAbstractDelegateComponent public API
+class QQmlAbstractDelegateComponentPrivate;
+class QQmlAdaptorModel;
+class Q_QML_PRIVATE_EXPORT QQmlAbstractDelegateComponent : public QQmlComponent
+{
+ Q_OBJECT
+public:
+ QQmlAbstractDelegateComponent(QObject *parent = nullptr);
+ ~QQmlAbstractDelegateComponent() override;
+
+ virtual QQmlComponent *delegate(QQmlAdaptorModel *adaptorModel, int row, int column = 0) const = 0;
+
+signals:
+ void delegateChanged();
+
+protected:
+ QVariant value(QQmlAdaptorModel *adaptorModel,int row, int column, const QString &role) const;
+
+private:
+ Q_DECLARE_PRIVATE(QQmlAbstractDelegateComponent)
+ Q_DISABLE_COPY(QQmlAbstractDelegateComponent)
+};
+
+class Q_QML_PRIVATE_EXPORT QQmlDelegateChoice : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(QVariant roleValue READ roleValue WRITE setRoleValue NOTIFY roleValueChanged)
+ Q_PROPERTY(int row READ row WRITE setRow NOTIFY rowChanged)
+ Q_PROPERTY(int index READ row WRITE setRow NOTIFY indexChanged)
+ Q_PROPERTY(int column READ column WRITE setColumn NOTIFY columnChanged)
+ Q_PROPERTY(QQmlComponent* delegate READ delegate WRITE setDelegate NOTIFY delegateChanged)
+ Q_CLASSINFO("DefaultProperty", "delegate")
+public:
+ QVariant roleValue() const;
+ void setRoleValue(const QVariant &roleValue);
+
+ int row() const;
+ void setRow(int r);
+
+ int column() const;
+ void setColumn(int c);
+
+ QQmlComponent *delegate() const;
+ void setDelegate(QQmlComponent *delegate);
+
+ virtual bool match(int row, int column, const QVariant &value) const;
+
+signals:
+ void roleValueChanged();
+ void rowChanged();
+ void indexChanged();
+ void columnChanged();
+ void delegateChanged();
+ void changed();
+
+private:
+ QVariant m_value;
+ int m_row = -1;
+ int m_column = -1;
+ QQmlComponent *m_delegate = nullptr;
+};
+
+class Q_QML_PRIVATE_EXPORT QQmlDelegateChooser : public QQmlAbstractDelegateComponent
+{
+ Q_OBJECT
+ Q_PROPERTY(QString role READ role WRITE setRole NOTIFY roleChanged)
+ Q_PROPERTY(QQmlListProperty<QQmlDelegateChoice> choices READ choices CONSTANT)
+ Q_CLASSINFO("DefaultProperty", "choices")
+
+public:
+ QString role() const { return m_role; }
+ void setRole(const QString &role);
+
+ virtual QQmlListProperty<QQmlDelegateChoice> choices();
+ static void choices_append(QQmlListProperty<QQmlDelegateChoice> *, QQmlDelegateChoice *);
+ static int choices_count(QQmlListProperty<QQmlDelegateChoice> *);
+ static QQmlDelegateChoice *choices_at(QQmlListProperty<QQmlDelegateChoice> *, int);
+ static void choices_clear(QQmlListProperty<QQmlDelegateChoice> *);
+
+ QQmlComponent *delegate(QQmlAdaptorModel *adaptorModel, int row, int column = -1) const override;
+
+signals:
+ void roleChanged();
+
+private:
+ QString m_role;
+ QList<QQmlDelegateChoice *> m_choices;
+};
+
+QT_END_NAMESPACE
+
+QML_DECLARE_TYPE(QQmlDelegateChoice)
+QML_DECLARE_TYPE(QQmlDelegateChooser)
+
+#endif // QQMLDELEGATECOMPONENT_P_H
diff --git a/src/qml/types/qqmldelegatemodel.cpp b/src/qml/types/qqmldelegatemodel.cpp
index 9620df6af9..572f58339f 100644
--- a/src/qml/types/qqmldelegatemodel.cpp
+++ b/src/qml/types/qqmldelegatemodel.cpp
@@ -38,6 +38,7 @@
****************************************************************************/
#include "qqmldelegatemodel_p_p.h"
+#include "qqmldelegatecomponent_p.h"
#include <QtQml/qqmlinfo.h>
@@ -93,20 +94,19 @@ struct DelegateModelGroupFunction : QV4::FunctionObject
static Heap::DelegateModelGroupFunction *create(QV4::ExecutionContext *scope, uint flag, QV4::ReturnedValue (*code)(QQmlDelegateModelItem *item, uint flag, const QV4::Value &arg))
{
- return scope->engine()->memoryManager->allocObject<DelegateModelGroupFunction>(scope, flag, code);
+ return scope->engine()->memoryManager->allocate<DelegateModelGroupFunction>(scope, flag, code);
}
- static void call(const QV4::Managed *that, QV4::Scope &scope, QV4::CallData *callData)
+ static ReturnedValue virtualCall(const QV4::FunctionObject *that, const Value *thisObject, const Value *argv, int argc)
{
+ QV4::Scope scope(that->engine());
QV4::Scoped<DelegateModelGroupFunction> f(scope, static_cast<const DelegateModelGroupFunction *>(that));
- QV4::Scoped<QQmlDelegateModelItemObject> o(scope, callData->thisObject);
- if (!o) {
- scope.result = scope.engine->throwTypeError(QStringLiteral("Not a valid VisualData object"));
- return;
- }
+ QV4::Scoped<QQmlDelegateModelItemObject> o(scope, thisObject);
+ if (!o)
+ return scope.engine->throwTypeError(QStringLiteral("Not a valid DelegateModel object"));
- QV4::ScopedValue v(scope, callData->argument(0));
- scope.result = f->d()->code(o->d()->item, f->d()->flag, v);
+ QV4::ScopedValue v(scope, argc ? argv[0] : Value::undefinedValue());
+ return f->d()->code(o->d()->item, f->d()->flag, v);
}
};
@@ -129,7 +129,8 @@ public:
QQmlDelegateModelEngineData(QV4::ExecutionEngine *v4);
~QQmlDelegateModelEngineData();
- QV4::ReturnedValue array(QV8Engine *engine, const QVector<QQmlChangeSet::Change> &changes);
+ QV4::ReturnedValue array(QV4::ExecutionEngine *engine,
+ const QVector<QQmlChangeSet::Change> &changes);
QV4::PersistentValue changeProto;
};
@@ -160,26 +161,10 @@ QQmlDelegateModelParts::QQmlDelegateModelParts(QQmlDelegateModel *parent)
//---------------------------------------------------------------------------
/*!
- \qmltype VisualDataModel
- \instantiates QQmlDelegateModel
- \inqmlmodule QtQuick
- \ingroup qtquick-models
- \brief Encapsulates a model and delegate
-
- The VisualDataModel type encapsulates a model and the delegate that will
- be instantiated for items in a model.
-
- This type is provided by the \l{Qt QML} module due to compatibility reasons.
- The same implementation is now primarily available as DelegateModel in the
- \l{Qt QML Models QML Types}{Qt QML Models} module.
-
- \sa {QtQml.Models::DelegateModel}
-*/
-/*!
\qmltype DelegateModel
\instantiates QQmlDelegateModel
\inqmlmodule QtQml.Models
- \brief Encapsulates a model and delegate
+ \brief Encapsulates a model and delegate.
The DelegateModel type encapsulates a model and the delegate that will
be instantiated for items in the model.
@@ -193,17 +178,14 @@ QQmlDelegateModelParts::QQmlDelegateModelParts(QQmlDelegateModel *parent)
The example below illustrates using a DelegateModel with a ListView.
- \snippet delegatemodel/visualdatamodel.qml 0
-
- \note This type is also available as \l VisualDataModel in the \l{Qt QML}
- module due to compatibility reasons.
+ \snippet delegatemodel/delegatemodel.qml 0
*/
QQmlDelegateModelPrivate::QQmlDelegateModelPrivate(QQmlContext *ctxt)
- : m_delegate(0)
- , m_cacheMetaType(0)
+ : m_delegateChooser(nullptr)
+ , m_cacheMetaType(nullptr)
, m_context(ctxt)
- , m_parts(0)
+ , m_parts(nullptr)
, m_filterGroup(QStringLiteral("items"))
, m_count(0)
, m_groupCount(Compositor::MinimumGroupCount)
@@ -214,9 +196,9 @@ QQmlDelegateModelPrivate::QQmlDelegateModelPrivate(QQmlContext *ctxt)
, m_transaction(false)
, m_incubatorCleanupScheduled(false)
, m_waitingToFetchMore(false)
- , m_cacheItems(0)
- , m_items(0)
- , m_persistedItems(0)
+ , m_cacheItems(nullptr)
+ , m_items(nullptr)
+ , m_persistedItems(nullptr)
{
}
@@ -228,6 +210,14 @@ QQmlDelegateModelPrivate::~QQmlDelegateModelPrivate()
m_cacheMetaType->release();
}
+int QQmlDelegateModelPrivate::adaptorModelCount() const
+{
+ // QQmlDelegateModel currently only support list models.
+ // So even if a model is a table model, only the first
+ // column will be used.
+ return m_adaptorModel.rowCount();
+}
+
void QQmlDelegateModelPrivate::requestMoreIfNecessary()
{
Q_Q(QQmlDelegateModel);
@@ -263,14 +253,17 @@ QQmlDelegateModel::QQmlDelegateModel(QQmlContext *ctxt, QObject *parent)
QQmlDelegateModel::~QQmlDelegateModel()
{
Q_D(QQmlDelegateModel);
+ d->disconnectFromAbstractItemModel();
+ d->m_adaptorModel.setObject(nullptr, this);
for (QQmlDelegateModelItem *cacheItem : qAsConst(d->m_cache)) {
if (cacheItem->object) {
delete cacheItem->object;
- cacheItem->object = 0;
- cacheItem->contextData->destroy();
- cacheItem->contextData = 0;
+ cacheItem->object = nullptr;
+ cacheItem->contextData->invalidate();
+ Q_ASSERT(cacheItem->contextData->refCount == 1);
+ cacheItem->contextData = nullptr;
cacheItem->scriptRef -= 1;
}
cacheItem->groups &= ~Compositor::UnresolvedFlag;
@@ -278,7 +271,7 @@ QQmlDelegateModel::~QQmlDelegateModel()
if (!cacheItem->isReferenced())
delete cacheItem;
else if (cacheItem->incubationTask)
- cacheItem->incubationTask->vdm = 0;
+ cacheItem->incubationTask->vdm = nullptr;
}
}
@@ -325,7 +318,7 @@ void QQmlDelegateModel::componentComplete()
}
d->m_cacheMetaType = new QQmlDelegateModelItemMetaType(
- QQmlEnginePrivate::getV8Engine(d->m_context->engine()), this, groupNames);
+ d->m_context->engine()->handle(), this, groupNames);
d->m_compositor.setGroupCount(d->m_groupCount);
d->m_compositor.setDefaultGroups(defaultGroups);
@@ -335,7 +328,7 @@ void QQmlDelegateModel::componentComplete()
static_cast<QQmlPartsModel *>(d->m_pendingParts.first())->updateFilterGroup();
QVector<Compositor::Insert> inserts;
- d->m_count = d->m_adaptorModel.count();
+ d->m_count = d->adaptorModelCount();
d->m_compositor.append(
&d->m_adaptorModel,
0,
@@ -357,9 +350,10 @@ void QQmlDelegateModel::componentComplete()
{QAbstractItemModel} subclass or a simple list.
Models can also be created directly in QML, using a \l{ListModel} or
- \l{XmlListModel}.
+ \l{QtQuick.XmlListModel::XmlListModel}{XmlListModel}.
\sa {qml-data-models}{Data Models}
+ \keyword dm-model-property
*/
QVariant QQmlDelegateModel::model() const
{
@@ -367,6 +361,54 @@ QVariant QQmlDelegateModel::model() const
return d->m_adaptorModel.model();
}
+void QQmlDelegateModelPrivate::connectToAbstractItemModel()
+{
+ Q_Q(QQmlDelegateModel);
+ if (!m_adaptorModel.adaptsAim())
+ return;
+
+ auto aim = m_adaptorModel.aim();
+
+ qmlobject_connect(aim, QAbstractItemModel, SIGNAL(rowsInserted(QModelIndex,int,int)),
+ q, QQmlDelegateModel, SLOT(_q_rowsInserted(QModelIndex,int,int)));
+ qmlobject_connect(aim, QAbstractItemModel, SIGNAL(rowsRemoved(QModelIndex,int,int)),
+ q, QQmlDelegateModel, SLOT(_q_rowsRemoved(QModelIndex,int,int)));
+ qmlobject_connect(aim, QAbstractItemModel, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)),
+ q, QQmlDelegateModel, SLOT(_q_rowsAboutToBeRemoved(QModelIndex,int,int)));
+ qmlobject_connect(aim, QAbstractItemModel, SIGNAL(dataChanged(QModelIndex,QModelIndex,QVector<int>)),
+ q, QQmlDelegateModel, SLOT(_q_dataChanged(QModelIndex,QModelIndex,QVector<int>)));
+ qmlobject_connect(aim, QAbstractItemModel, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)),
+ q, QQmlDelegateModel, SLOT(_q_rowsMoved(QModelIndex,int,int,QModelIndex,int)));
+ qmlobject_connect(aim, QAbstractItemModel, SIGNAL(modelReset()),
+ q, QQmlDelegateModel, SLOT(_q_modelReset()));
+ qmlobject_connect(aim, QAbstractItemModel, SIGNAL(layoutChanged(QList<QPersistentModelIndex>,QAbstractItemModel::LayoutChangeHint)),
+ q, QQmlDelegateModel, SLOT(_q_layoutChanged(QList<QPersistentModelIndex>,QAbstractItemModel::LayoutChangeHint)));
+}
+
+void QQmlDelegateModelPrivate::disconnectFromAbstractItemModel()
+{
+ Q_Q(QQmlDelegateModel);
+ if (!m_adaptorModel.adaptsAim())
+ return;
+
+ auto aim = m_adaptorModel.aim();
+
+ QObject::disconnect(aim, SIGNAL(rowsInserted(QModelIndex,int,int)),
+ q, SLOT(_q_rowsInserted(QModelIndex,int,int)));
+ QObject::disconnect(aim, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)),
+ q, SLOT(_q_rowsAboutToBeRemoved(QModelIndex,int,int)));
+ QObject::disconnect(aim, SIGNAL(rowsRemoved(QModelIndex,int,int)),
+ q, SLOT(_q_rowsRemoved(QModelIndex,int,int)));
+ QObject::disconnect(aim, SIGNAL(dataChanged(QModelIndex,QModelIndex,QVector<int>)),
+ q, SLOT(_q_dataChanged(QModelIndex,QModelIndex,QVector<int>)));
+ QObject::disconnect(aim, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)),
+ q, SLOT(_q_rowsMoved(QModelIndex,int,int,QModelIndex,int)));
+ QObject::disconnect(aim, SIGNAL(modelReset()),
+ q, SLOT(_q_modelReset()));
+ QObject::disconnect(aim, SIGNAL(layoutChanged(QList<QPersistentModelIndex>,QAbstractItemModel::LayoutChangeHint)),
+ q, SLOT(_q_layoutChanged(QList<QPersistentModelIndex>,QAbstractItemModel::LayoutChangeHint)));
+}
+
void QQmlDelegateModel::setModel(const QVariant &model)
{
Q_D(QQmlDelegateModel);
@@ -374,7 +416,10 @@ void QQmlDelegateModel::setModel(const QVariant &model)
if (d->m_complete)
_q_itemsRemoved(0, d->m_count);
+ d->disconnectFromAbstractItemModel();
d->m_adaptorModel.setModel(model, this, d->m_context->engine());
+ d->connectToAbstractItemModel();
+
d->m_adaptorModel.replaceWatchedRoles(QList<QByteArray>(), d->m_watchedRoles);
for (int i = 0; d->m_parts && i < d->m_parts->models.count(); ++i) {
d->m_adaptorModel.replaceWatchedRoles(
@@ -382,7 +427,7 @@ void QQmlDelegateModel::setModel(const QVariant &model)
}
if (d->m_complete) {
- _q_itemsInserted(0, d->m_adaptorModel.count());
+ _q_itemsInserted(0, d->adaptorModelCount());
d->requestMoreIfNecessary();
}
}
@@ -407,22 +452,25 @@ void QQmlDelegateModel::setDelegate(QQmlComponent *delegate)
qmlWarning(this) << tr("The delegate of a DelegateModel cannot be changed within onUpdated.");
return;
}
- bool wasValid = d->m_delegate != 0;
- d->m_delegate = delegate;
+ if (d->m_delegate == delegate)
+ return;
+ bool wasValid = d->m_delegate != nullptr;
+ d->m_delegate.setObject(delegate, this);
d->m_delegateValidated = false;
- if (wasValid && d->m_complete) {
- for (int i = 1; i < d->m_groupCount; ++i) {
- QQmlDelegateModelGroupPrivate::get(d->m_groups[i])->changeSet.remove(
- 0, d->m_compositor.count(Compositor::Group(i)));
- }
- }
- if (d->m_complete && d->m_delegate) {
- for (int i = 1; i < d->m_groupCount; ++i) {
- QQmlDelegateModelGroupPrivate::get(d->m_groups[i])->changeSet.insert(
- 0, d->m_compositor.count(Compositor::Group(i)));
+ if (d->m_delegateChooser)
+ QObject::disconnect(d->m_delegateChooserChanged);
+
+ d->m_delegateChooser = nullptr;
+ if (delegate) {
+ QQmlAbstractDelegateComponent *adc =
+ qobject_cast<QQmlAbstractDelegateComponent *>(delegate);
+ if (adc) {
+ d->m_delegateChooser = adc;
+ d->m_delegateChooserChanged = connect(adc, &QQmlAbstractDelegateComponent::delegateChanged,
+ [d](){ d->delegateChanged(); });
}
}
- d->emitChanges();
+ d->delegateChanged(d->m_delegate, wasValid);
}
/*!
@@ -441,16 +489,15 @@ void QQmlDelegateModel::setDelegate(QQmlComponent *delegate)
the new directory's contents.
\c main.cpp:
- \snippet delegatemodel/visualdatamodel_rootindex/main.cpp 0
+ \snippet delegatemodel/delegatemodel_rootindex/main.cpp 0
\c view.qml:
- \snippet delegatemodel/visualdatamodel_rootindex/view.qml 0
-
- If the \l model is a QAbstractItemModel subclass, the delegate can also
- reference a \c hasModelChildren property (optionally qualified by a
- \e model. prefix) that indicates whether the delegate's model item has
- any child nodes.
+ \snippet delegatemodel/delegatemodel_rootindex/view.qml 0
+ If the \l {dm-model-property}{model} is a QAbstractItemModel subclass,
+ the delegate can also reference a \c hasModelChildren property (optionally
+ qualified by a \e model. prefix) that indicates whether the delegate's
+ model item has any child nodes.
\sa modelIndex(), parentModelIndex()
*/
@@ -469,12 +516,16 @@ void QQmlDelegateModel::setRootIndex(const QVariant &root)
if (changed || !d->m_adaptorModel.isValid()) {
const int oldCount = d->m_count;
d->m_adaptorModel.rootIndex = modelIndex;
- if (!d->m_adaptorModel.isValid() && d->m_adaptorModel.aim()) // The previous root index was invalidated, so we need to reconnect the model.
+ if (!d->m_adaptorModel.isValid() && d->m_adaptorModel.aim()) {
+ // The previous root index was invalidated, so we need to reconnect the model.
+ d->disconnectFromAbstractItemModel();
d->m_adaptorModel.setModel(d->m_adaptorModel.list.list(), this, d->m_context->engine());
+ d->connectToAbstractItemModel();
+ }
if (d->m_adaptorModel.canFetchMore())
d->m_adaptorModel.fetchMore();
if (d->m_complete) {
- const int newCount = d->m_adaptorModel.count();
+ const int newCount = d->adaptorModelCount();
if (oldCount)
_q_itemsRemoved(0, oldCount);
if (newCount)
@@ -535,25 +586,24 @@ int QQmlDelegateModel::count() const
QQmlDelegateModel::ReleaseFlags QQmlDelegateModelPrivate::release(QObject *object)
{
- QQmlDelegateModel::ReleaseFlags stat = 0;
if (!object)
- return stat;
-
- if (QQmlDelegateModelItem *cacheItem = QQmlDelegateModelItem::dataForObject(object)) {
- if (cacheItem->releaseObject()) {
- cacheItem->destroyObject();
- emitDestroyingItem(object);
- if (cacheItem->incubationTask) {
- releaseIncubator(cacheItem->incubationTask);
- cacheItem->incubationTask = 0;
- }
- cacheItem->Dispose();
- stat |= QQmlInstanceModel::Destroyed;
- } else {
- stat |= QQmlDelegateModel::Referenced;
- }
+ return QQmlDelegateModel::ReleaseFlags(0);
+
+ QQmlDelegateModelItem *cacheItem = QQmlDelegateModelItem::dataForObject(object);
+ if (!cacheItem)
+ return QQmlDelegateModel::ReleaseFlags(0);
+
+ if (!cacheItem->releaseObject())
+ return QQmlDelegateModel::Referenced;
+
+ cacheItem->destroyObject();
+ emitDestroyingItem(object);
+ if (cacheItem->incubationTask) {
+ releaseIncubator(cacheItem->incubationTask);
+ cacheItem->incubationTask = nullptr;
}
- return stat;
+ cacheItem->Dispose();
+ return QQmlInstanceModel::Destroyed;
}
/*
@@ -581,7 +631,7 @@ void QQmlDelegateModel::cancel(int index)
if (cacheItem) {
if (cacheItem->incubationTask && !cacheItem->isObjectReferenced()) {
d->releaseIncubator(cacheItem->incubationTask);
- cacheItem->incubationTask = 0;
+ cacheItem->incubationTask = nullptr;
if (cacheItem->object) {
QObject *object = cacheItem->object;
@@ -630,7 +680,7 @@ QQmlDelegateModelGroup *QQmlDelegateModelPrivate::group_at(
QQmlDelegateModelPrivate *d = static_cast<QQmlDelegateModelPrivate *>(property->data);
return index >= 0 && index < d->m_groupCount - 1
? d->m_groups[index + 1]
- : 0;
+ : nullptr;
}
/*!
@@ -648,7 +698,8 @@ QQmlDelegateModelGroup *QQmlDelegateModelPrivate::group_at(
The following example illustrates using groups to select items in a model.
- \snippet delegatemodel/visualdatagroup.qml 0
+ \snippet delegatemodel/delegatemodelgroup.qml 0
+ \keyword dm-groups-property
*/
QQmlListProperty<QQmlDelegateModelGroup> QQmlDelegateModel::groups()
@@ -660,13 +711,13 @@ QQmlListProperty<QQmlDelegateModelGroup> QQmlDelegateModel::groups()
QQmlDelegateModelPrivate::group_append,
QQmlDelegateModelPrivate::group_count,
QQmlDelegateModelPrivate::group_at,
- 0);
+ nullptr);
}
/*!
\qmlproperty DelegateModelGroup QtQml.Models::DelegateModel::items
- This property holds visual data model's default group to which all new items are added.
+ This property holds default group to which all new items are added.
*/
QQmlDelegateModelGroup *QQmlDelegateModel::items()
@@ -678,7 +729,7 @@ QQmlDelegateModelGroup *QQmlDelegateModel::items()
/*!
\qmlproperty DelegateModelGroup QtQml.Models::DelegateModel::persistedItems
- This property holds visual data model's persisted items group.
+ This property holds delegate model's persisted items group.
Items in this group are not destroyed when released by a view, instead they are persisted
until removed from the group.
@@ -701,9 +752,9 @@ QQmlDelegateModelGroup *QQmlDelegateModel::persistedItems()
/*!
\qmlproperty string QtQml.Models::DelegateModel::filterOnGroup
- This property holds the name of the group used to filter the visual data model.
+ This property holds name of the group that is used to filter the delegate model.
- Only items which belong to this group are visible to a view.
+ Only items that belong to this group are visible to a view.
By default this is the \l items group.
*/
@@ -807,6 +858,12 @@ QObject *QQmlDelegateModel::parts()
return d->m_parts;
}
+const QAbstractItemModel *QQmlDelegateModel::abstractItemModel() const
+{
+ Q_D(const QQmlDelegateModel);
+ return d->m_adaptorModel.adaptsAim() ? d->m_adaptorModel.aim() : nullptr;
+}
+
void QQmlDelegateModelPrivate::emitCreatedPackage(QQDMIncubationTask *incubationTask, QQuickPackage *package)
{
for (int i = 1; i < m_groupCount; ++i)
@@ -838,10 +895,11 @@ void QQDMIncubationTask::statusChanged(Status status)
Q_ASSERT(incubating);
// The model was deleted from under our feet, cleanup ourselves
delete incubating->object;
- incubating->object = 0;
+ incubating->object = nullptr;
if (incubating->contextData) {
- incubating->contextData->destroy();
- incubating->contextData = 0;
+ incubating->contextData->invalidate();
+ Q_ASSERT(incubating->contextData->refCount == 1);
+ incubating->contextData = nullptr;
}
incubating->scriptRef = 0;
incubating->deleteLater();
@@ -860,9 +918,16 @@ void QQmlDelegateModelPrivate::releaseIncubator(QQDMIncubationTask *incubationTa
}
}
+void QQmlDelegateModelPrivate::addCacheItem(QQmlDelegateModelItem *item, Compositor::iterator it)
+{
+ m_cache.insert(it.cacheIndex, item);
+ m_compositor.setFlags(it, 1, Compositor::CacheFlag);
+ Q_ASSERT(m_cache.count() == m_compositor.count(Compositor::Cache));
+}
+
void QQmlDelegateModelPrivate::removeCacheItem(QQmlDelegateModelItem *cacheItem)
{
- int cidx = m_cache.indexOf(cacheItem);
+ int cidx = m_cache.lastIndexOf(cacheItem);
if (cidx >= 0) {
m_compositor.clearFlags(Compositor::Cache, cidx, 1, Compositor::CacheFlag);
m_cache.removeAt(cidx);
@@ -872,13 +937,14 @@ void QQmlDelegateModelPrivate::removeCacheItem(QQmlDelegateModelItem *cacheItem)
void QQmlDelegateModelPrivate::incubatorStatusChanged(QQDMIncubationTask *incubationTask, QQmlIncubator::Status status)
{
- Q_Q(QQmlDelegateModel);
if (!isDoneIncubating(status))
return;
+ const QList<QQmlError> incubationTaskErrors = incubationTask->errors();
+
QQmlDelegateModelItem *cacheItem = incubationTask->incubating;
- cacheItem->incubationTask = 0;
- incubationTask->incubating = 0;
+ cacheItem->incubationTask = nullptr;
+ incubationTask->incubating = nullptr;
releaseIncubator(incubationTask);
if (status == QQmlIncubator::Ready) {
@@ -889,7 +955,7 @@ void QQmlDelegateModelPrivate::incubatorStatusChanged(QQDMIncubationTask *incuba
emitCreatedItem(incubationTask, cacheItem->object);
cacheItem->releaseObject();
} else if (status == QQmlIncubator::Error) {
- qmlWarning(q, m_delegate->errors()) << "Error creating delegate";
+ qmlInfo(m_delegate, incubationTaskErrors + m_delegate->errors()) << "Cannot create delegate";
}
if (!cacheItem->isObjectReferenced()) {
@@ -898,11 +964,13 @@ void QQmlDelegateModelPrivate::incubatorStatusChanged(QQDMIncubationTask *incuba
else
emitDestroyingItem(cacheItem->object);
delete cacheItem->object;
- cacheItem->object = 0;
+ cacheItem->object = nullptr;
cacheItem->scriptRef -= 1;
- if (cacheItem->contextData)
- cacheItem->contextData->destroy();
- cacheItem->contextData = 0;
+ if (cacheItem->contextData) {
+ cacheItem->contextData->invalidate();
+ Q_ASSERT(cacheItem->contextData->refCount == 1);
+ }
+ cacheItem->contextData = nullptr;
if (!cacheItem->isReferenced()) {
removeCacheItem(cacheItem);
@@ -927,13 +995,13 @@ void QQmlDelegateModelPrivate::setInitialState(QQDMIncubationTask *incubationTas
emitInitItem(incubationTask, cacheItem->object);
}
-QObject *QQmlDelegateModelPrivate::object(Compositor::Group group, int index, bool asynchronous)
+QObject *QQmlDelegateModelPrivate::object(Compositor::Group group, int index, QQmlIncubator::IncubationMode incubationMode)
{
if (!m_delegate || index < 0 || index >= m_compositor.count(group)) {
qWarning() << "DelegateModel::item: index out range" << index << m_compositor.count(group);
- return 0;
+ return nullptr;
} else if (!m_context || !m_context->isValid()) {
- return 0;
+ return nullptr;
}
Compositor::iterator it = m_compositor.find(group, index);
@@ -941,15 +1009,12 @@ QObject *QQmlDelegateModelPrivate::object(Compositor::Group group, int index, bo
QQmlDelegateModelItem *cacheItem = it->inCache() ? m_cache.at(it.cacheIndex) : 0;
if (!cacheItem) {
- cacheItem = m_adaptorModel.createItem(m_cacheMetaType, m_context->engine(), it.modelIndex());
+ cacheItem = m_adaptorModel.createItem(m_cacheMetaType, it.modelIndex());
if (!cacheItem)
- return 0;
+ return nullptr;
cacheItem->groups = it->flags;
-
- m_cache.insert(it.cacheIndex, cacheItem);
- m_compositor.setFlags(it, 1, Compositor::CacheFlag);
- Q_ASSERT(m_cache.count() == m_compositor.count(Compositor::Cache));
+ addCacheItem(cacheItem, it);
}
// Bump the reference counts temporarily so neither the content data or the delegate object
@@ -958,16 +1023,28 @@ QObject *QQmlDelegateModelPrivate::object(Compositor::Group group, int index, bo
cacheItem->referenceObject();
if (cacheItem->incubationTask) {
- if (!asynchronous && cacheItem->incubationTask->incubationMode() == QQmlIncubator::Asynchronous) {
+ bool sync = (incubationMode == QQmlIncubator::Synchronous || incubationMode == QQmlIncubator::AsynchronousIfNested);
+ if (sync && cacheItem->incubationTask->incubationMode() == QQmlIncubator::Asynchronous) {
// previously requested async - now needed immediately
cacheItem->incubationTask->forceCompletion();
}
} else if (!cacheItem->object) {
- QQmlContext *creationContext = m_delegate->creationContext();
+ QQmlComponent *delegate = m_delegate;
+ if (m_delegateChooser) {
+ QQmlAbstractDelegateComponent *chooser = m_delegateChooser;
+ do {
+ delegate = chooser->delegate(&m_adaptorModel, index);
+ chooser = qobject_cast<QQmlAbstractDelegateComponent *>(delegate);
+ } while (chooser);
+ if (!delegate)
+ return nullptr;
+ }
+
+ QQmlContext *creationContext = delegate->creationContext();
cacheItem->scriptRef += 1;
- cacheItem->incubationTask = new QQDMIncubationTask(this, asynchronous ? QQmlIncubator::Asynchronous : QQmlIncubator::AsynchronousIfNested);
+ cacheItem->incubationTask = new QQDMIncubationTask(this, incubationMode);
cacheItem->incubationTask->incubating = cacheItem;
cacheItem->incubationTask->clear();
@@ -983,15 +1060,19 @@ QObject *QQmlDelegateModelPrivate::object(Compositor::Group group, int index, bo
if (QQmlAdaptorModelProxyInterface *proxy
= qobject_cast<QQmlAdaptorModelProxyInterface *>(cacheItem)) {
ctxt = new QQmlContextData;
- ctxt->setParent(cacheItem->contextData, true);
- ctxt->contextObject = proxy->proxiedObject();
+ ctxt->setParent(cacheItem->contextData, /*stronglyReferencedByParent*/true);
+ QObject *proxied = proxy->proxiedObject();
+ ctxt->contextObject = proxied;
+ // We don't own the proxied object. We need to clear it if it goes away.
+ QObject::connect(proxied, &QObject::destroyed,
+ cacheItem, &QQmlDelegateModelItem::childContextObjectDestroyed);
}
}
- QQmlComponentPrivate *cp = QQmlComponentPrivate::get(m_delegate);
+ QQmlComponentPrivate *cp = QQmlComponentPrivate::get(delegate);
cp->incubateObject(
cacheItem->incubationTask,
- m_delegate,
+ delegate,
m_context->engine(),
ctxt,
QQmlContextData::get(m_context));
@@ -1011,30 +1092,39 @@ QObject *QQmlDelegateModelPrivate::object(Compositor::Group group, int index, bo
delete cacheItem;
}
- return 0;
+ return nullptr;
}
/*
If asynchronous is true or the component is being loaded asynchronously due
- to an ancestor being loaded asynchronously, item() may return 0. In this
- case createdItem() will be emitted when the item is available. The item
- at this stage does not have any references, so item() must be called again
- to ensure a reference is held. Any call to item() which returns a valid item
- must be matched by a call to release() in order to destroy the item.
+ to an ancestor being loaded asynchronously, object() may return 0. In this
+ case createdItem() will be emitted when the object is available. The object
+ at this stage does not have any references, so object() must be called again
+ to ensure a reference is held. Any call to object() which returns a valid object
+ must be matched by a call to release() in order to destroy the object.
*/
-QObject *QQmlDelegateModel::object(int index, bool asynchronous)
+QObject *QQmlDelegateModel::object(int index, QQmlIncubator::IncubationMode incubationMode)
{
Q_D(QQmlDelegateModel);
if (!d->m_delegate || index < 0 || index >= d->m_compositor.count(d->m_compositorGroup)) {
qWarning() << "DelegateModel::item: index out range" << index << d->m_compositor.count(d->m_compositorGroup);
- return 0;
+ return nullptr;
}
- QObject *object = d->object(d->m_compositorGroup, index, asynchronous);
- if (!object)
- return 0;
+ return d->object(d->m_compositorGroup, index, incubationMode);
+}
+
+QQmlIncubator::Status QQmlDelegateModel::incubationStatus(int index)
+{
+ Q_D(QQmlDelegateModel);
+ Compositor::iterator it = d->m_compositor.find(d->m_compositorGroup, index);
+ if (!it->inCache())
+ return QQmlIncubator::Null;
+
+ if (auto incubationTask = d->m_cache.at(it.cacheIndex)->incubationTask)
+ return incubationTask->status();
- return object;
+ return QQmlIncubator::Ready;
}
QString QQmlDelegateModelPrivate::stringValue(Compositor::Group group, int index, const QString &name)
@@ -1254,8 +1344,12 @@ void QQmlDelegateModel::_q_itemsInserted(int index, int count)
const QList<QQmlDelegateModelItem *> cache = d->m_cache;
for (int i = 0, c = cache.count(); i < c; ++i) {
QQmlDelegateModelItem *item = cache.at(i);
- if (item->modelIndex() >= index)
- item->setModelIndex(item->modelIndex() + count);
+ if (item->modelIndex() >= index) {
+ const int newIndex = item->modelIndex() + count;
+ const int row = newIndex;
+ const int column = 0;
+ item->setModelIndex(newIndex, row, column);
+ }
}
QVector<Compositor::Insert> inserts;
@@ -1264,6 +1358,13 @@ void QQmlDelegateModel::_q_itemsInserted(int index, int count)
d->emitChanges();
}
+//### This method should be split in two. It will remove delegates, and it will re-render the list.
+// When e.g. QQmlListModel::remove is called, the removal of the delegates should be done on
+// QAbstractItemModel::rowsAboutToBeRemoved, and the re-rendering on
+// QAbstractItemModel::rowsRemoved. Currently both are done on the latter signal. The problem is
+// that the destruction of an item will emit a changed signal that ends up at the delegate, which
+// in turn will try to load the data from the model (which should have already freed it), resulting
+// in a use-after-free. See QTBUG-59256.
void QQmlDelegateModelPrivate::itemsRemoved(
const QVector<Compositor::Remove> &removes,
QVarLengthArray<QVector<QQmlChangeSet::Change>, Compositor::MaximumGroupCount> *translatedRemoves,
@@ -1329,7 +1430,7 @@ void QQmlDelegateModelPrivate::itemsRemoved(
if (QQDMIncubationTask *incubationTask = cacheItem->incubationTask) {
if (!cacheItem->isObjectReferenced()) {
releaseIncubator(cacheItem->incubationTask);
- cacheItem->incubationTask = 0;
+ cacheItem->incubationTask = nullptr;
if (cacheItem->object) {
QObject *object = cacheItem->object;
cacheItem->destroyObject();
@@ -1389,10 +1490,14 @@ void QQmlDelegateModel::_q_itemsRemoved(int index, int count)
if (!d->m_cache.contains(item))
continue;
- if (item->modelIndex() >= index + count)
- item->setModelIndex(item->modelIndex() - count);
- else if (item->modelIndex() >= index)
- item->setModelIndex(-1);
+ if (item->modelIndex() >= index + count) {
+ const int newIndex = item->modelIndex() - count;
+ const int row = newIndex;
+ const int column = 0;
+ item->setModelIndex(newIndex, row, column);
+ } else if (item->modelIndex() >= index) {
+ item->setModelIndex(-1, -1, -1);
+ }
}
QVector<Compositor::Remove> removes;
@@ -1437,10 +1542,17 @@ void QQmlDelegateModel::_q_itemsMoved(int from, int to, int count)
const QList<QQmlDelegateModelItem *> cache = d->m_cache;
for (int i = 0, c = cache.count(); i < c; ++i) {
QQmlDelegateModelItem *item = cache.at(i);
- if (item->modelIndex() >= from && item->modelIndex() < from + count)
- item->setModelIndex(item->modelIndex() - from + to);
- else if (item->modelIndex() >= minimum && item->modelIndex() < maximum)
- item->setModelIndex(item->modelIndex() + difference);
+ if (item->modelIndex() >= from && item->modelIndex() < from + count) {
+ const int newIndex = item->modelIndex() - from + to;
+ const int row = newIndex;
+ const int column = 0;
+ item->setModelIndex(newIndex, row, column);
+ } else if (item->modelIndex() >= minimum && item->modelIndex() < maximum) {
+ const int newIndex = item->modelIndex() + difference;
+ const int row = newIndex;
+ const int column = 0;
+ item->setModelIndex(newIndex, row, column);
+ }
}
QVector<Compositor::Remove> removes;
@@ -1458,13 +1570,39 @@ void QQmlDelegateModelPrivate::emitModelUpdated(const QQmlChangeSet &changeSet,
emit q->countChanged();
}
+void QQmlDelegateModelPrivate::delegateChanged(bool add, bool remove)
+{
+ Q_Q(QQmlDelegateModel);
+ if (!m_complete)
+ return;
+
+ if (m_transaction) {
+ qmlWarning(q) << QQmlDelegateModel::tr("The delegates of a DelegateModel cannot be changed within onUpdated.");
+ return;
+ }
+
+ if (remove) {
+ for (int i = 1; i < m_groupCount; ++i) {
+ QQmlDelegateModelGroupPrivate::get(m_groups[i])->changeSet.remove(
+ 0, m_compositor.count(Compositor::Group(i)));
+ }
+ }
+ if (add) {
+ for (int i = 1; i < m_groupCount; ++i) {
+ QQmlDelegateModelGroupPrivate::get(m_groups[i])->changeSet.insert(
+ 0, m_compositor.count(Compositor::Group(i)));
+ }
+ }
+ emitChanges();
+}
+
void QQmlDelegateModelPrivate::emitChanges()
{
if (m_transaction || !m_complete || !m_context || !m_context->isValid())
return;
m_transaction = true;
- QV8Engine *engine = QQmlEnginePrivate::getV8Engine(m_context->engine());
+ QV4::ExecutionEngine *engine = m_context->engine()->handle();
for (int i = 1; i < m_groupCount; ++i)
QQmlDelegateModelGroupPrivate::get(m_groups[i])->emitChanges(engine);
m_transaction = false;
@@ -1491,13 +1629,13 @@ void QQmlDelegateModel::_q_modelReset()
d->m_adaptorModel.rootIndex = QModelIndex();
if (d->m_complete) {
- d->m_count = d->m_adaptorModel.count();
+ d->m_count = d->adaptorModelCount();
const QList<QQmlDelegateModelItem *> cache = d->m_cache;
for (int i = 0, c = cache.count(); i < c; ++i) {
QQmlDelegateModelItem *item = cache.at(i);
if (item->modelIndex() != -1)
- item->setModelIndex(-1);
+ item->setModelIndex(-1, -1, -1);
}
QVector<Compositor::Remove> removes;
@@ -1533,7 +1671,8 @@ void QQmlDelegateModel::_q_rowsAboutToBeRemoved(const QModelIndex &parent, int b
if (index.parent() == parent && index.row() >= begin && index.row() <= end) {
const int oldCount = d->m_count;
d->m_count = 0;
- d->m_adaptorModel.invalidateModel(this);
+ d->disconnectFromAbstractItemModel();
+ d->m_adaptorModel.invalidateModel();
if (d->m_complete && oldCount > 0) {
QVector<Compositor::Remove> removes;
@@ -1623,7 +1762,7 @@ bool QQmlDelegateModelPrivate::insert(Compositor::insert_iterator &before, const
if (!m_context || !m_context->isValid())
return false;
- QQmlDelegateModelItem *cacheItem = m_adaptorModel.createItem(m_cacheMetaType, m_context->engine(), -1);
+ QQmlDelegateModelItem *cacheItem = m_adaptorModel.createItem(m_cacheMetaType, -1);
if (!cacheItem)
return false;
if (!object.isObject())
@@ -1635,7 +1774,7 @@ bool QQmlDelegateModelPrivate::insert(Compositor::insert_iterator &before, const
if (!o)
return false;
- QV4::ObjectIterator it(scope, o, QV4::ObjectIterator::EnumerableOnly|QV4::ObjectIterator::WithProtoChain);
+ QV4::ObjectIterator it(scope, o, QV4::ObjectIterator::EnumerableOnly);
QV4::ScopedValue propertyName(scope);
QV4::ScopedValue v(scope);
while (1) {
@@ -1650,7 +1789,7 @@ bool QQmlDelegateModelPrivate::insert(Compositor::insert_iterator &before, const
// Must be before the new object is inserted into the cache or its indexes will be adjusted too.
itemsInserted(QVector<Compositor::Insert>(1, Compositor::Insert(before, 1, cacheItem->groups & ~Compositor::CacheFlag)));
- before = m_compositor.insert(before, 0, 0, 1, cacheItem->groups);
+ before = m_compositor.insert(before, nullptr, 0, 1, cacheItem->groups);
m_cache.insert(before.cacheIndex, cacheItem);
return true;
@@ -1659,11 +1798,11 @@ bool QQmlDelegateModelPrivate::insert(Compositor::insert_iterator &before, const
//============================================================================
QQmlDelegateModelItemMetaType::QQmlDelegateModelItemMetaType(
- QV8Engine *engine, QQmlDelegateModel *model, const QStringList &groupNames)
+ QV4::ExecutionEngine *engine, QQmlDelegateModel *model, const QStringList &groupNames)
: model(model)
, groupCount(groupNames.count() + 1)
- , v8Engine(engine)
- , metaObject(0)
+ , v4Engine(engine)
+ , metaObject(nullptr)
, groupNames(groupNames)
{
}
@@ -1703,57 +1842,56 @@ void QQmlDelegateModelItemMetaType::initializeMetaObject()
void QQmlDelegateModelItemMetaType::initializePrototype()
{
- QV4::ExecutionEngine *v4 = QV8Engine::getV4(v8Engine);
- QV4::Scope scope(v4);
+ QV4::Scope scope(v4Engine);
- QV4::ScopedObject proto(scope, v4->newObject());
- proto->defineAccessorProperty(QStringLiteral("model"), QQmlDelegateModelItem::get_model, 0);
+ QV4::ScopedObject proto(scope, v4Engine->newObject());
+ proto->defineAccessorProperty(QStringLiteral("model"), QQmlDelegateModelItem::get_model, nullptr);
proto->defineAccessorProperty(QStringLiteral("groups"), QQmlDelegateModelItem::get_groups, QQmlDelegateModelItem::set_groups);
QV4::ScopedString s(scope);
QV4::ScopedProperty p(scope);
- s = v4->newString(QStringLiteral("isUnresolved"));
+ s = v4Engine->newString(QStringLiteral("isUnresolved"));
QV4::ScopedFunctionObject f(scope);
QV4::ExecutionContext *global = scope.engine->rootContext();
p->setGetter((f = QV4::DelegateModelGroupFunction::create(global, 30, QQmlDelegateModelItem::get_member)));
- p->setSetter(0);
+ p->setSetter(nullptr);
proto->insertMember(s, p, QV4::Attr_Accessor|QV4::Attr_NotConfigurable|QV4::Attr_NotEnumerable);
- s = v4->newString(QStringLiteral("inItems"));
+ s = v4Engine->newString(QStringLiteral("inItems"));
p->setGetter((f = QV4::DelegateModelGroupFunction::create(global, QQmlListCompositor::Default, QQmlDelegateModelItem::get_member)));
p->setSetter((f = QV4::DelegateModelGroupFunction::create(global, QQmlListCompositor::Default, QQmlDelegateModelItem::set_member)));
proto->insertMember(s, p, QV4::Attr_Accessor|QV4::Attr_NotConfigurable|QV4::Attr_NotEnumerable);
- s = v4->newString(QStringLiteral("inPersistedItems"));
+ s = v4Engine->newString(QStringLiteral("inPersistedItems"));
p->setGetter((f = QV4::DelegateModelGroupFunction::create(global, QQmlListCompositor::Persisted, QQmlDelegateModelItem::get_member)));
p->setSetter((f = QV4::DelegateModelGroupFunction::create(global, QQmlListCompositor::Persisted, QQmlDelegateModelItem::set_member)));
proto->insertMember(s, p, QV4::Attr_Accessor|QV4::Attr_NotConfigurable|QV4::Attr_NotEnumerable);
- s = v4->newString(QStringLiteral("itemsIndex"));
+ s = v4Engine->newString(QStringLiteral("itemsIndex"));
p->setGetter((f = QV4::DelegateModelGroupFunction::create(global, QQmlListCompositor::Default, QQmlDelegateModelItem::get_index)));
proto->insertMember(s, p, QV4::Attr_Accessor|QV4::Attr_NotConfigurable|QV4::Attr_NotEnumerable);
- s = v4->newString(QStringLiteral("persistedItemsIndex"));
+ s = v4Engine->newString(QStringLiteral("persistedItemsIndex"));
p->setGetter((f = QV4::DelegateModelGroupFunction::create(global, QQmlListCompositor::Persisted, QQmlDelegateModelItem::get_index)));
- p->setSetter(0);
+ p->setSetter(nullptr);
proto->insertMember(s, p, QV4::Attr_Accessor|QV4::Attr_NotConfigurable|QV4::Attr_NotEnumerable);
for (int i = 2; i < groupNames.count(); ++i) {
QString propertyName = QLatin1String("in") + groupNames.at(i);
propertyName.replace(2, 1, propertyName.at(2).toUpper());
- s = v4->newString(propertyName);
+ s = v4Engine->newString(propertyName);
p->setGetter((f = QV4::DelegateModelGroupFunction::create(global, i + 1, QQmlDelegateModelItem::get_member)));
p->setSetter((f = QV4::DelegateModelGroupFunction::create(global, i + 1, QQmlDelegateModelItem::set_member)));
proto->insertMember(s, p, QV4::Attr_Accessor|QV4::Attr_NotConfigurable|QV4::Attr_NotEnumerable);
}
for (int i = 2; i < groupNames.count(); ++i) {
const QString propertyName = groupNames.at(i) + QLatin1String("Index");
- s = v4->newString(propertyName);
+ s = v4Engine->newString(propertyName);
p->setGetter((f = QV4::DelegateModelGroupFunction::create(global, i + 1, QQmlDelegateModelItem::get_index)));
- p->setSetter(0);
+ p->setSetter(nullptr);
proto->insertMember(s, p, QV4::Attr_Accessor|QV4::Attr_NotConfigurable|QV4::Attr_NotEnumerable);
}
- modelItemProto.set(v4, proto);
+ modelItemProto.set(v4Engine, proto);
}
int QQmlDelegateModelItemMetaType::parseGroups(const QStringList &groups) const
@@ -1770,7 +1908,7 @@ int QQmlDelegateModelItemMetaType::parseGroups(const QStringList &groups) const
int QQmlDelegateModelItemMetaType::parseGroups(const QV4::Value &groups) const
{
int groupFlags = 0;
- QV4::Scope scope(QV8Engine::getV4(v8Engine));
+ QV4::Scope scope(v4Engine);
QV4::ScopedString s(scope, groups);
if (s) {
@@ -1786,7 +1924,7 @@ int QQmlDelegateModelItemMetaType::parseGroups(const QV4::Value &groups) const
QV4::ScopedValue v(scope);
uint arrayLength = array->getLength();
for (uint i = 0; i < arrayLength; ++i) {
- v = array->getIndexed(i);
+ v = array->get(i);
const QString groupName = v->toQString();
int index = groupNames.indexOf(groupName);
if (index != -1)
@@ -1796,26 +1934,24 @@ int QQmlDelegateModelItemMetaType::parseGroups(const QV4::Value &groups) const
return groupFlags;
}
-void QQmlDelegateModelItem::get_model(const QV4::BuiltinFunction *, QV4::Scope &scope, QV4::CallData *callData)
+QV4::ReturnedValue QQmlDelegateModelItem::get_model(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int)
{
- QV4::Scoped<QQmlDelegateModelItemObject> o(scope, callData->thisObject.as<QQmlDelegateModelItemObject>());
- if (!o) {
- scope.result = scope.engine->throwTypeError(QStringLiteral("Not a valid VisualData object"));
- return;
- }
+ QV4::Scope scope(b);
+ QV4::Scoped<QQmlDelegateModelItemObject> o(scope, thisObject->as<QQmlDelegateModelItemObject>());
+ if (!o)
+ return b->engine()->throwTypeError(QStringLiteral("Not a valid DelegateModel object"));
if (!o->d()->item->metaType->model)
RETURN_UNDEFINED();
- scope.result = o->d()->item->get();
+ return o->d()->item->get();
}
-void QQmlDelegateModelItem::get_groups(const QV4::BuiltinFunction *, QV4::Scope &scope, QV4::CallData *callData)
+QV4::ReturnedValue QQmlDelegateModelItem::get_groups(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int)
{
- QV4::Scoped<QQmlDelegateModelItemObject> o(scope, callData->thisObject.as<QQmlDelegateModelItemObject>());
- if (!o) {
- scope.result = scope.engine->throwTypeError(QStringLiteral("Not a valid VisualData object"));
- return;
- }
+ QV4::Scope scope(b);
+ QV4::Scoped<QQmlDelegateModelItemObject> o(scope, thisObject->as<QQmlDelegateModelItemObject>());
+ if (!o)
+ return scope.engine->throwTypeError(QStringLiteral("Not a valid DelegateModel object"));
QStringList groups;
for (int i = 1; i < o->d()->item->metaType->groupCount; ++i) {
@@ -1823,29 +1959,28 @@ void QQmlDelegateModelItem::get_groups(const QV4::BuiltinFunction *, QV4::Scope
groups.append(o->d()->item->metaType->groupNames.at(i - 1));
}
- scope.result = scope.engine->fromVariant(groups);
+ return scope.engine->fromVariant(groups);
}
-void QQmlDelegateModelItem::set_groups(const QV4::BuiltinFunction *, QV4::Scope &scope, QV4::CallData *callData)
+QV4::ReturnedValue QQmlDelegateModelItem::set_groups(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
- QV4::Scoped<QQmlDelegateModelItemObject> o(scope, callData->thisObject.as<QQmlDelegateModelItemObject>());
- if (!o) {
- scope.result = scope.engine->throwTypeError(QStringLiteral("Not a valid VisualData object"));
- return;
- }
+ QV4::Scope scope(b);
+ QV4::Scoped<QQmlDelegateModelItemObject> o(scope, thisObject->as<QQmlDelegateModelItemObject>());
+ if (!o)
+ return scope.engine->throwTypeError(QStringLiteral("Not a valid DelegateModel object"));
- if (!callData->argc)
+ if (!argc)
THROW_TYPE_ERROR();
if (!o->d()->item->metaType->model)
RETURN_UNDEFINED();
QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(o->d()->item->metaType->model);
- const int groupFlags = model->m_cacheMetaType->parseGroups(callData->args[0]);
+ const int groupFlags = model->m_cacheMetaType->parseGroups(argv[0]);
const int cacheIndex = model->m_cache.indexOf(o->d()->item);
Compositor::iterator it = model->m_compositor.find(Compositor::Cache, cacheIndex);
model->setGroups(it, 1, Compositor::Cache, groupFlags);
- scope.result = QV4::Encode::undefined();
+ return QV4::Encode::undefined();
}
QV4::ReturnedValue QQmlDelegateModelItem::get_member(QQmlDelegateModelItem *thisItem, uint flag, const QV4::Value &)
@@ -1879,6 +2014,17 @@ QV4::ReturnedValue QQmlDelegateModelItem::get_index(QQmlDelegateModelItem *thisI
return QV4::Encode((int)thisItem->groupIndex(Compositor::Group(flag)));
}
+void QQmlDelegateModelItem::childContextObjectDestroyed(QObject *childContextObject)
+{
+ if (!contextData)
+ return;
+
+ for (QQmlContextData *ctxt = contextData->childContexts; ctxt; ctxt = ctxt->nextChild) {
+ if (ctxt->contextObject == childContextObject)
+ ctxt->contextObject = nullptr;
+ }
+}
+
//---------------------------------------------------------------------------
@@ -1891,20 +2037,40 @@ void QV4::Heap::QQmlDelegateModelItemObject::destroy()
}
-QQmlDelegateModelItem::QQmlDelegateModelItem(
- QQmlDelegateModelItemMetaType *metaType, int modelIndex)
- : v4(QV8Engine::getV4(metaType->v8Engine))
+QQmlDelegateModelItem::QQmlDelegateModelItem(QQmlDelegateModelItemMetaType *metaType,
+ QQmlAdaptorModel::Accessors *accessor,
+ int modelIndex, int row, int column)
+ : v4(metaType->v4Engine)
, metaType(metaType)
- , contextData(0)
- , object(0)
- , attached(0)
- , incubationTask(0)
+ , contextData(nullptr)
+ , object(nullptr)
+ , attached(nullptr)
+ , incubationTask(nullptr)
+ , delegate(nullptr)
+ , poolTime(0)
, objectRef(0)
, scriptRef(0)
, groups(0)
, index(modelIndex)
+ , row(row)
+ , column(column)
{
metaType->addref();
+
+ if (accessor->propertyCache) {
+ // The property cache in the accessor is common for all the model
+ // items in the model it wraps. It describes available model roles,
+ // together with revisioned properties like row, column and index, all
+ // which should be available in the delegate. We assign this cache to the
+ // model item so that the QML engine can use the revision information
+ // when resolving the properties (rather than falling back to just
+ // inspecting the QObject in the model item directly).
+ QQmlData *qmldata = QQmlData::get(this, true);
+ if (qmldata->propertyCache)
+ qmldata->propertyCache->release();
+ qmldata->propertyCache = accessor->propertyCache.data();
+ qmldata->propertyCache->addref();
+ }
}
QQmlDelegateModelItem::~QQmlDelegateModelItem()
@@ -1937,6 +2103,24 @@ void QQmlDelegateModelItem::Dispose()
delete this;
}
+void QQmlDelegateModelItem::setModelIndex(int idx, int newRow, int newColumn)
+{
+ const int prevIndex = index;
+ const int prevRow = row;
+ const int prevColumn = column;
+
+ index = idx;
+ row = newRow;
+ column = newColumn;
+
+ if (idx != prevIndex)
+ emit modelIndexChanged();
+ if (row != prevRow)
+ emit rowChanged();
+ if (column != prevColumn)
+ emit columnChanged();
+}
+
void QQmlDelegateModelItem::destroyObject()
{
Q_ASSERT(object);
@@ -1944,38 +2128,43 @@ void QQmlDelegateModelItem::destroyObject()
QQmlData *data = QQmlData::get(object);
Q_ASSERT(data);
- if (data->ownContext && data->context)
- data->context->clearContext();
+ if (data->ownContext) {
+ data->ownContext->clearContext();
+ if (data->ownContext->contextObject == object)
+ data->ownContext->contextObject = nullptr;
+ data->ownContext = nullptr;
+ data->context = nullptr;
+ }
object->deleteLater();
if (attached) {
- attached->m_cacheItem = 0;
- attached = 0;
+ attached->m_cacheItem = nullptr;
+ attached = nullptr;
}
- contextData->destroy();
- contextData = 0;
- object = 0;
+ contextData->invalidate();
+ contextData = nullptr;
+ object = nullptr;
}
QQmlDelegateModelItem *QQmlDelegateModelItem::dataForObject(QObject *object)
{
QQmlData *d = QQmlData::get(object);
- QQmlContextData *context = d ? d->context : 0;
- for (context = context ? context->parent : 0; context; context = context->parent) {
+ QQmlContextData *context = d ? d->context : nullptr;
+ for (context = context ? context->parent : nullptr; context; context = context->parent) {
if (QQmlDelegateModelItem *cacheItem = qobject_cast<QQmlDelegateModelItem *>(
context->contextObject)) {
return cacheItem;
}
}
- return 0;
+ return nullptr;
}
int QQmlDelegateModelItem::groupIndex(Compositor::Group group)
{
if (QQmlDelegateModelPrivate * const model = metaType->model
? QQmlDelegateModelPrivate::get(metaType->model)
- : 0) {
+ : nullptr) {
return model->m_compositor.find(Compositor::Cache, model->m_cache.indexOf(this)).index[group];
}
return -1;
@@ -2048,7 +2237,7 @@ int QQmlDelegateModelAttachedMetaObject::metaCall(QObject *object, QMetaObject::
}
QQmlDelegateModelAttached::QQmlDelegateModelAttached(QObject *parent)
- : m_cacheItem(0)
+ : m_cacheItem(nullptr)
, m_previousGroups(0)
{
QQml_setParent_noEvent(this, parent);
@@ -2060,35 +2249,42 @@ QQmlDelegateModelAttached::QQmlDelegateModelAttached(
, m_previousGroups(cacheItem->groups)
{
QQml_setParent_noEvent(this, parent);
+ resetCurrentIndex();
+ // Let m_previousIndex be equal to m_currentIndex
+ std::copy(std::begin(m_currentIndex), std::end(m_currentIndex), std::begin(m_previousIndex));
+
+ if (!cacheItem->metaType->metaObject)
+ cacheItem->metaType->initializeMetaObject();
+
+ QObjectPrivate::get(this)->metaObject = cacheItem->metaType->metaObject;
+ cacheItem->metaType->metaObject->addref();
+}
+
+void QQmlDelegateModelAttached::resetCurrentIndex()
+{
if (QQDMIncubationTask *incubationTask = m_cacheItem->incubationTask) {
for (int i = 1; i < qMin<int>(m_cacheItem->metaType->groupCount, Compositor::MaximumGroupCount); ++i)
- m_currentIndex[i] = m_previousIndex[i] = incubationTask->index[i];
+ m_currentIndex[i] = incubationTask->index[i];
} else {
QQmlDelegateModelPrivate * const model = QQmlDelegateModelPrivate::get(m_cacheItem->metaType->model);
Compositor::iterator it = model->m_compositor.find(
Compositor::Cache, model->m_cache.indexOf(m_cacheItem));
for (int i = 1; i < m_cacheItem->metaType->groupCount; ++i)
- m_currentIndex[i] = m_previousIndex[i] = it.index[i];
+ m_currentIndex[i] = it.index[i];
}
-
- if (!cacheItem->metaType->metaObject)
- cacheItem->metaType->initializeMetaObject();
-
- QObjectPrivate::get(this)->metaObject = cacheItem->metaType->metaObject;
- cacheItem->metaType->metaObject->addref();
}
/*!
- \qmlattachedproperty int QtQml.Models::DelegateModel::model
+ \qmlattachedproperty model QtQml.Models::DelegateModel::model
- This attached property holds the visual data model this delegate instance belongs to.
+ This attached property holds the data model this delegate instance belongs to.
It is attached to each instance of the delegate.
*/
QQmlDelegateModel *QQmlDelegateModelAttached::model() const
{
- return m_cacheItem ? m_cacheItem->metaType->model : 0;
+ return m_cacheItem ? m_cacheItem->metaType->model : nullptr;
}
/*!
@@ -2128,7 +2324,7 @@ void QQmlDelegateModelAttached::setGroups(const QStringList &groups)
/*!
\qmlattachedproperty bool QtQml.Models::DelegateModel::isUnresolved
- This attached property holds whether the visual item is bound to a data model index.
+ This attached property indicates whether the visual item is bound to a data model index.
Returns true if the item is not bound to the model, and false if it is.
An unresolved item can be bound to the data model using the DelegateModelGroup::resolve()
@@ -2200,11 +2396,11 @@ void QQmlDelegateModelAttached::emitChanges()
const QMetaObject *meta = metaObject();
for (int i = 1; i < m_cacheItem->metaType->groupCount; ++i, ++notifierId) {
if (groupChanges & (1 << i))
- QMetaObject::activate(this, meta, notifierId, 0);
+ QMetaObject::activate(this, meta, notifierId, nullptr);
}
for (int i = 1; i < m_cacheItem->metaType->groupCount; ++i, ++notifierId) {
if (indexChanges & (1 << i))
- QMetaObject::activate(this, meta, notifierId, 0);
+ QMetaObject::activate(this, meta, notifierId, nullptr);
}
if (groupChanges)
@@ -2226,13 +2422,13 @@ bool QQmlDelegateModelGroupPrivate::isChangedConnected()
IS_SIGNAL_CONNECTED(q, QQmlDelegateModelGroup, changed, (const QQmlV4Handle &,const QQmlV4Handle &));
}
-void QQmlDelegateModelGroupPrivate::emitChanges(QV8Engine *engine)
+void QQmlDelegateModelGroupPrivate::emitChanges(QV4::ExecutionEngine *v4)
{
Q_Q(QQmlDelegateModelGroup);
if (isChangedConnected() && !changeSet.isEmpty()) {
- QV4::Scope scope(QV8Engine::getV4(engine));
- QV4::ScopedValue removed(scope, engineData(scope.engine)->array(engine, changeSet.removes()));
- QV4::ScopedValue inserted(scope, engineData(scope.engine)->array(engine, changeSet.inserts()));
+ QV4::Scope scope(v4);
+ QV4::ScopedValue removed(scope, engineData(scope.engine)->array(v4, changeSet.removes()));
+ QV4::ScopedValue inserted(scope, engineData(scope.engine)->array(v4, changeSet.inserts()));
emit q->changed(QQmlV4Handle(removed), QQmlV4Handle(inserted));
}
if (changeSet.difference() != 0)
@@ -2267,27 +2463,11 @@ void QQmlDelegateModelGroupPrivate::destroyingPackage(QQuickPackage *package)
}
/*!
- \qmltype VisualDataGroup
- \instantiates QQmlDelegateModelGroup
- \inqmlmodule QtQuick
- \ingroup qtquick-models
- \brief Encapsulates a filtered set of visual data items
-
- The VisualDataGroup type provides a means to address the model data of a
- model's delegate items, as well as sort and filter these delegate items.
-
- This type is provided by the \l{Qt QML} module due to compatibility reasons.
- The same implementation is now primarily available as \l DelegateModelGroup
- in the \l{Qt QML Models QML Types}{Qt QML Models} module.
-
- \sa {QtQml.Models::DelegateModelGroup}
-*/
-/*!
\qmltype DelegateModelGroup
\instantiates QQmlDelegateModelGroup
\inqmlmodule QtQml.Models
\ingroup qtquick-models
- \brief Encapsulates a filtered set of visual data items
+ \brief Encapsulates a filtered set of visual data items.
The DelegateModelGroup type provides a means to address the model data of a
DelegateModel's delegate items, as well as sort and filter these delegate
@@ -2303,7 +2483,8 @@ void QQmlDelegateModelGroupPrivate::destroyingPackage(QQuickPackage *package)
information about group membership and indexes as well as model data. In combination
with the move() function this can be used to implement view sorting, with remove() to filter
items out of a view, or with setGroups() and \l Package delegates to categorize items into
- different views.
+ different views. Different groups can only be sorted independently if they are disjunct. Moving
+ an item in one group will also move it in all other groups it is a part of.
Data from models can be supplemented by inserting data directly into a DelegateModelGroup
with the insert() function. This can be used to introduce mock items into a view, or
@@ -2315,9 +2496,6 @@ void QQmlDelegateModelGroupPrivate::destroyingPackage(QQuickPackage *package)
type or to cherry-pick specific items that should be instantiated irregardless of whether
they're currently within a view's visible area.
- \note This type is also available as \l VisualDataGroup in the \l{Qt QML}
- module due to compatibility reasons.
-
\sa {QML Dynamic View Ordering Tutorial}
*/
QQmlDelegateModelGroup::QQmlDelegateModelGroup(QObject *parent)
@@ -2450,7 +2628,7 @@ QQmlV4Handle QQmlDelegateModelGroup::get(int index)
if (!cacheItem) {
cacheItem = model->m_adaptorModel.createItem(
- model->m_cacheMetaType, model->m_context->engine(), it.modelIndex());
+ model->m_cacheMetaType, it.modelIndex());
if (!cacheItem)
return QQmlV4Handle(QV4::Encode::undefined());
cacheItem->groups = it->flags;
@@ -2461,12 +2639,11 @@ QQmlV4Handle QQmlDelegateModelGroup::get(int index)
if (model->m_cacheMetaType->modelItemProto.isUndefined())
model->m_cacheMetaType->initializePrototype();
- QV8Engine *v8 = model->m_cacheMetaType->v8Engine;
- QV4::ExecutionEngine *v4 = QV8Engine::getV4(v8);
+ QV4::ExecutionEngine *v4 = model->m_cacheMetaType->v4Engine;
QV4::Scope scope(v4);
- QV4::ScopedObject o(scope, v4->memoryManager->allocObject<QQmlDelegateModelItemObject>(cacheItem));
+ QV4::ScopedObject o(scope, v4->memoryManager->allocate<QQmlDelegateModelItemObject>(cacheItem));
QV4::ScopedObject p(scope, model->m_cacheMetaType->modelItemProto.value());
- o->setPrototype(p);
+ o->setPrototypeOf(p);
++cacheItem->scriptRef;
return QQmlV4Handle(o);
@@ -2490,7 +2667,7 @@ bool QQmlDelegateModelGroupPrivate::parseIndex(const QV4::Value &value, int *ind
QQmlDelegateModelItem * const cacheItem = object->d()->item;
if (QQmlDelegateModelPrivate *model = cacheItem->metaType->model
? QQmlDelegateModelPrivate::get(cacheItem->metaType->model)
- : 0) {
+ : nullptr) {
*index = model->m_cache.indexOf(cacheItem);
*group = Compositor::Cache;
return true;
@@ -2621,7 +2798,7 @@ void QQmlDelegateModelGroup::create(QQmlV4Function *args)
return;
}
- QObject *object = model->object(group, index, false);
+ QObject *object = model->object(group, index, QQmlIncubator::AsynchronousIfNested);
if (object) {
QVector<Compositor::Insert> inserts;
Compositor::iterator it = model->m_compositor.find(group, index);
@@ -2919,6 +3096,11 @@ void QQmlDelegateModelGroup::setGroups(QQmlV4Function *args)
\qmlmethod QtQml.Models::DelegateModelGroup::move(var from, var to, int count)
Moves \a count at \a from in a group \a to a new position.
+
+ \note The DelegateModel acts as a proxy model: it holds the delegates in a
+ different order than the \l{dm-model-property}{underlying model} has them.
+ Any subsequent changes to the underlying model will not undo whatever
+ reordering you have done via this function.
*/
void QQmlDelegateModelGroup::move(QQmlV4Function *args)
@@ -3109,21 +3291,21 @@ bool QQmlPartsModel::isValid() const
return m_model->isValid();
}
-QObject *QQmlPartsModel::object(int index, bool asynchronous)
+QObject *QQmlPartsModel::object(int index, QQmlIncubator::IncubationMode incubationMode)
{
QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(m_model);
if (!model->m_delegate || index < 0 || index >= model->m_compositor.count(m_compositorGroup)) {
qWarning() << "DelegateModel::item: index out range" << index << model->m_compositor.count(m_compositorGroup);
- return 0;
+ return nullptr;
}
- QObject *object = model->object(m_compositorGroup, index, asynchronous);
+ QObject *object = model->object(m_compositorGroup, index, incubationMode);
if (QQuickPackage *package = qmlobject_cast<QQuickPackage *>(object)) {
QObject *part = package->part(m_part);
if (!part)
- return 0;
+ return nullptr;
m_packaged.insertMulti(part, package);
return part;
}
@@ -3135,12 +3317,12 @@ QObject *QQmlPartsModel::object(int index, bool asynchronous)
model->m_delegateValidated = true;
}
- return 0;
+ return nullptr;
}
QQmlInstanceModel::ReleaseFlags QQmlPartsModel::release(QObject *item)
{
- QQmlInstanceModel::ReleaseFlags flags = 0;
+ QQmlInstanceModel::ReleaseFlags flags = nullptr;
QHash<QObject *, QQuickPackage *>::iterator it = m_packaged.find(item);
if (it != m_packaged.end()) {
@@ -3168,6 +3350,19 @@ void QQmlPartsModel::setWatchedRoles(const QList<QByteArray> &roles)
m_watchedRoles = roles;
}
+QQmlIncubator::Status QQmlPartsModel::incubationStatus(int index)
+{
+ QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(m_model);
+ Compositor::iterator it = model->m_compositor.find(model->m_compositorGroup, index);
+ if (!it->inCache())
+ return QQmlIncubator::Null;
+
+ if (auto incubationTask = model->m_cache.at(it.cacheIndex)->incubationTask)
+ return incubationTask->status();
+
+ return QQmlIncubator::Ready;
+}
+
int QQmlPartsModel::indexOf(QObject *item, QObject *) const
{
QHash<QObject *, QQuickPackage *>::const_iterator it = m_packaged.find(item);
@@ -3185,7 +3380,10 @@ void QQmlPartsModel::createdPackage(int index, QQuickPackage *package)
void QQmlPartsModel::initPackage(int index, QQuickPackage *package)
{
- emit initItem(index, package->part(m_part));
+ if (m_modelUpdatePending)
+ m_pendingPackageInitializations << index;
+ else
+ emit initItem(index, package->part(m_part));
}
void QQmlPartsModel::destroyingPackage(QQuickPackage *package)
@@ -3197,9 +3395,22 @@ void QQmlPartsModel::destroyingPackage(QQuickPackage *package)
void QQmlPartsModel::emitModelUpdated(const QQmlChangeSet &changeSet, bool reset)
{
+ m_modelUpdatePending = false;
emit modelUpdated(changeSet, reset);
if (changeSet.difference() != 0)
emit countChanged();
+
+ QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(m_model);
+ QVector<int> pendingPackageInitializations;
+ qSwap(pendingPackageInitializations, m_pendingPackageInitializations);
+ for (int index : pendingPackageInitializations) {
+ if (!model->m_delegate || index < 0 || index >= model->m_compositor.count(m_compositorGroup))
+ continue;
+ QObject *object = model->object(m_compositorGroup, index, QQmlIncubator::Asynchronous);
+ if (QQuickPackage *package = qmlobject_cast<QQuickPackage *>(object))
+ emit initItem(index, package->part(m_part));
+ model->release(object);
+ }
}
//============================================================================
@@ -3209,28 +3420,31 @@ struct QQmlDelegateModelGroupChange : QV4::Object
V4_OBJECT2(QQmlDelegateModelGroupChange, QV4::Object)
static QV4::Heap::QQmlDelegateModelGroupChange *create(QV4::ExecutionEngine *e) {
- return e->memoryManager->allocObject<QQmlDelegateModelGroupChange>();
+ return e->memoryManager->allocate<QQmlDelegateModelGroupChange>();
}
- static void method_get_index(const QV4::BuiltinFunction *, QV4::Scope &scope, QV4::CallData *callData) {
- QV4::Scoped<QQmlDelegateModelGroupChange> that(scope, callData->thisObject.as<QQmlDelegateModelGroupChange>());
+ static QV4::ReturnedValue method_get_index(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int) {
+ QV4::Scope scope(b);
+ QV4::Scoped<QQmlDelegateModelGroupChange> that(scope, thisObject->as<QQmlDelegateModelGroupChange>());
if (!that)
THROW_TYPE_ERROR();
- scope.result = QV4::Encode(that->d()->change.index);
+ return QV4::Encode(that->d()->change.index);
}
- static void method_get_count(const QV4::BuiltinFunction *, QV4::Scope &scope, QV4::CallData *callData) {
- QV4::Scoped<QQmlDelegateModelGroupChange> that(scope, callData->thisObject.as<QQmlDelegateModelGroupChange>());
+ static QV4::ReturnedValue method_get_count(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int) {
+ QV4::Scope scope(b);
+ QV4::Scoped<QQmlDelegateModelGroupChange> that(scope, thisObject->as<QQmlDelegateModelGroupChange>());
if (!that)
THROW_TYPE_ERROR();
- scope.result = QV4::Encode(that->d()->change.count);
+ return QV4::Encode(that->d()->change.count);
}
- static void method_get_moveId(const QV4::BuiltinFunction *, QV4::Scope &scope, QV4::CallData *callData) {
- QV4::Scoped<QQmlDelegateModelGroupChange> that(scope, callData->thisObject.as<QQmlDelegateModelGroupChange>());
+ static QV4::ReturnedValue method_get_moveId(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int) {
+ QV4::Scope scope(b);
+ QV4::Scoped<QQmlDelegateModelGroupChange> that(scope, thisObject->as<QQmlDelegateModelGroupChange>());
if (!that)
THROW_TYPE_ERROR();
if (that->d()->change.moveId < 0)
RETURN_UNDEFINED();
- scope.result = QV4::Encode(that->d()->change.moveId);
+ return QV4::Encode(that->d()->change.moveId);
}
};
@@ -3243,49 +3457,49 @@ struct QQmlDelegateModelGroupChangeArray : public QV4::Object
public:
static QV4::Heap::QQmlDelegateModelGroupChangeArray *create(QV4::ExecutionEngine *engine, const QVector<QQmlChangeSet::Change> &changes)
{
- return engine->memoryManager->allocObject<QQmlDelegateModelGroupChangeArray>(changes);
+ return engine->memoryManager->allocate<QQmlDelegateModelGroupChangeArray>(changes);
}
quint32 count() const { return d()->changes->count(); }
const QQmlChangeSet::Change &at(int index) const { return d()->changes->at(index); }
- static QV4::ReturnedValue getIndexed(const QV4::Managed *m, uint index, bool *hasProperty)
+ static QV4::ReturnedValue virtualGet(const QV4::Managed *m, QV4::PropertyKey id, const QV4::Value *receiver, bool *hasProperty)
{
- Q_ASSERT(m->as<QQmlDelegateModelGroupChangeArray>());
- QV4::ExecutionEngine *v4 = static_cast<const QQmlDelegateModelGroupChangeArray *>(m)->engine();
- QV4::Scope scope(v4);
- QV4::Scoped<QQmlDelegateModelGroupChangeArray> array(scope, static_cast<const QQmlDelegateModelGroupChangeArray *>(m));
-
- if (index >= array->count()) {
- if (hasProperty)
- *hasProperty = false;
- return QV4::Primitive::undefinedValue().asReturnedValue();
- }
+ if (id.isArrayIndex()) {
+ uint index = id.asArrayIndex();
+ Q_ASSERT(m->as<QQmlDelegateModelGroupChangeArray>());
+ QV4::ExecutionEngine *v4 = static_cast<const QQmlDelegateModelGroupChangeArray *>(m)->engine();
+ QV4::Scope scope(v4);
+ QV4::Scoped<QQmlDelegateModelGroupChangeArray> array(scope, static_cast<const QQmlDelegateModelGroupChangeArray *>(m));
+
+ if (index >= array->count()) {
+ if (hasProperty)
+ *hasProperty = false;
+ return QV4::Value::undefinedValue().asReturnedValue();
+ }
- const QQmlChangeSet::Change &change = array->at(index);
+ const QQmlChangeSet::Change &change = array->at(index);
- QV4::ScopedObject changeProto(scope, engineData(v4)->changeProto.value());
- QV4::Scoped<QQmlDelegateModelGroupChange> object(scope, QQmlDelegateModelGroupChange::create(v4));
- object->setPrototype(changeProto);
- object->d()->change = change;
+ QV4::ScopedObject changeProto(scope, engineData(v4)->changeProto.value());
+ QV4::Scoped<QQmlDelegateModelGroupChange> object(scope, QQmlDelegateModelGroupChange::create(v4));
+ object->setPrototypeOf(changeProto);
+ object->d()->change = change;
- if (hasProperty)
- *hasProperty = true;
- return object.asReturnedValue();
- }
+ if (hasProperty)
+ *hasProperty = true;
+ return object.asReturnedValue();
+ }
- static QV4::ReturnedValue get(const QV4::Managed *m, QV4::String *name, bool *hasProperty)
- {
Q_ASSERT(m->as<QQmlDelegateModelGroupChangeArray>());
const QQmlDelegateModelGroupChangeArray *array = static_cast<const QQmlDelegateModelGroupChangeArray *>(m);
- if (name->equals(array->engine()->id_length())) {
+ if (id == array->engine()->id_length()->propertyKey()) {
if (hasProperty)
*hasProperty = true;
return QV4::Encode(array->count());
}
- return Object::get(m, name, hasProperty);
+ return Object::virtualGet(m, id, receiver, hasProperty);
}
};
@@ -3305,9 +3519,9 @@ QQmlDelegateModelEngineData::QQmlDelegateModelEngineData(QV4::ExecutionEngine *v
QV4::Scope scope(v4);
QV4::ScopedObject proto(scope, v4->newObject());
- proto->defineAccessorProperty(QStringLiteral("index"), QQmlDelegateModelGroupChange::method_get_index, 0);
- proto->defineAccessorProperty(QStringLiteral("count"), QQmlDelegateModelGroupChange::method_get_count, 0);
- proto->defineAccessorProperty(QStringLiteral("moveId"), QQmlDelegateModelGroupChange::method_get_moveId, 0);
+ proto->defineAccessorProperty(QStringLiteral("index"), QQmlDelegateModelGroupChange::method_get_index, nullptr);
+ proto->defineAccessorProperty(QStringLiteral("count"), QQmlDelegateModelGroupChange::method_get_count, nullptr);
+ proto->defineAccessorProperty(QStringLiteral("moveId"), QQmlDelegateModelGroupChange::method_get_moveId, nullptr);
changeProto.set(v4, proto);
}
@@ -3315,9 +3529,9 @@ QQmlDelegateModelEngineData::~QQmlDelegateModelEngineData()
{
}
-QV4::ReturnedValue QQmlDelegateModelEngineData::array(QV8Engine *engine, const QVector<QQmlChangeSet::Change> &changes)
+QV4::ReturnedValue QQmlDelegateModelEngineData::array(QV4::ExecutionEngine *v4,
+ const QVector<QQmlChangeSet::Change> &changes)
{
- QV4::ExecutionEngine *v4 = QV8Engine::getV4(engine);
QV4::Scope scope(v4);
QV4::ScopedObject o(scope, QQmlDelegateModelGroupChangeArray::create(v4, changes));
return o.asReturnedValue();
diff --git a/src/qml/types/qqmldelegatemodel_p.h b/src/qml/types/qqmldelegatemodel_p.h
index 186144d107..0ad8939732 100644
--- a/src/qml/types/qqmldelegatemodel_p.h
+++ b/src/qml/types/qqmldelegatemodel_p.h
@@ -54,6 +54,7 @@
#include <private/qtqmlglobal_p.h>
#include <private/qqmllistcompositor_p.h>
#include <private/qqmlobjectmodel_p.h>
+#include <private/qqmlincubator_p.h>
#include <QtCore/qabstractitemmodel.h>
#include <QtCore/qstringlist.h>
@@ -61,10 +62,11 @@
#include <private/qv8engine_p.h>
#include <private/qqmlglobal_p.h>
+QT_REQUIRE_CONFIG(qml_delegate_model);
+
QT_BEGIN_NAMESPACE
class QQmlChangeSet;
-class QQmlComponent;
class QQuickPackage;
class QQmlV4Function;
class QQmlDelegateModelGroup;
@@ -89,7 +91,7 @@ class Q_QML_PRIVATE_EXPORT QQmlDelegateModel : public QQmlInstanceModel, public
Q_INTERFACES(QQmlParserStatus)
public:
QQmlDelegateModel();
- QQmlDelegateModel(QQmlContext *, QObject *parent=0);
+ QQmlDelegateModel(QQmlContext *, QObject *parent=nullptr);
~QQmlDelegateModel();
void classBegin() override;
@@ -108,12 +110,13 @@ public:
Q_INVOKABLE QVariant parentModelIndex() const;
int count() const override;
- bool isValid() const override { return delegate() != 0; }
- QObject *object(int index, bool asynchronous = false) override;
+ bool isValid() const override { return delegate() != nullptr; }
+ QObject *object(int index, QQmlIncubator::IncubationMode incubationMode = QQmlIncubator::AsynchronousIfNested) override;
ReleaseFlags release(QObject *object) override;
void cancel(int index) override;
QString stringValue(int index, const QString &role) override;
void setWatchedRoles(const QList<QByteArray> &roles) override;
+ QQmlIncubator::Status incubationStatus(int index) override;
int indexOf(QObject *object, QObject *objectContext) const override;
@@ -126,6 +129,8 @@ public:
QQmlListProperty<QQmlDelegateModelGroup> groups();
QObject *parts();
+ const QAbstractItemModel *abstractItemModel() const override;
+
bool event(QEvent *) override;
static QQmlDelegateModelAttached *qmlAttachedProperties(QObject *obj);
@@ -162,8 +167,8 @@ class Q_QML_PRIVATE_EXPORT QQmlDelegateModelGroup : public QObject
Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
Q_PROPERTY(bool includeByDefault READ defaultInclude WRITE setDefaultInclude NOTIFY defaultIncludeChanged)
public:
- QQmlDelegateModelGroup(QObject *parent = 0);
- QQmlDelegateModelGroup(const QString &name, QQmlDelegateModel *model, int compositorType, QObject *parent = 0);
+ QQmlDelegateModelGroup(QObject *parent = nullptr);
+ QQmlDelegateModelGroup(const QString &name, QQmlDelegateModel *model, int compositorType, QObject *parent = nullptr);
~QQmlDelegateModelGroup();
QString name() const;
@@ -208,6 +213,7 @@ public:
QQmlDelegateModelAttached(QQmlDelegateModelItem *cacheItem, QObject *parent);
~QQmlDelegateModelAttached() {}
+ void resetCurrentIndex();
void setCacheItem(QQmlDelegateModelItem *item);
QQmlDelegateModel *model() const;
diff --git a/src/qml/types/qqmldelegatemodel_p_p.h b/src/qml/types/qqmldelegatemodel_p_p.h
index 4ebfd9b938..0028849828 100644
--- a/src/qml/types/qqmldelegatemodel_p_p.h
+++ b/src/qml/types/qqmldelegatemodel_p_p.h
@@ -60,16 +60,19 @@
// We mean it.
//
+QT_REQUIRE_CONFIG(qml_delegate_model);
+
QT_BEGIN_NAMESPACE
typedef QQmlListCompositor Compositor;
class QQmlDelegateModelAttachedMetaObject;
+class QQmlAbstractDelegateComponent;
-class QQmlDelegateModelItemMetaType : public QQmlRefCount
+class Q_QML_PRIVATE_EXPORT QQmlDelegateModelItemMetaType : public QQmlRefCount
{
public:
- QQmlDelegateModelItemMetaType(QV8Engine *engine, QQmlDelegateModel *model, const QStringList &groupNames);
+ QQmlDelegateModelItemMetaType(QV4::ExecutionEngine *engine, QQmlDelegateModel *model, const QStringList &groupNames);
~QQmlDelegateModelItemMetaType();
void initializeMetaObject();
@@ -80,7 +83,7 @@ public:
QPointer<QQmlDelegateModel> model;
const int groupCount;
- QV8Engine * const v8Engine;
+ QV4::ExecutionEngine * const v4Engine;
QQmlDelegateModelAttachedMetaObject *metaObject;
const QStringList groupNames;
QV4::PersistentValue modelItemProto;
@@ -93,14 +96,19 @@ class QQmlDelegateModelItem : public QObject
{
Q_OBJECT
Q_PROPERTY(int index READ modelIndex NOTIFY modelIndexChanged)
+ Q_PROPERTY(int row READ modelRow NOTIFY rowChanged REVISION 12)
+ Q_PROPERTY(int column READ modelColumn NOTIFY columnChanged REVISION 12)
Q_PROPERTY(QObject *model READ modelObject CONSTANT)
public:
- QQmlDelegateModelItem(QQmlDelegateModelItemMetaType *metaType, int modelIndex);
+ QQmlDelegateModelItem(QQmlDelegateModelItemMetaType *metaType,
+ QQmlAdaptorModel::Accessors *accessor, int modelIndex,
+ int row, int column);
~QQmlDelegateModelItem();
void referenceObject() { ++objectRef; }
bool releaseObject() { return --objectRef == 0 && !(groups & Compositor::PersistedFlag); }
bool isObjectReferenced() const { return objectRef != 0 || (groups & Compositor::PersistedFlag); }
+ void childContextObjectDestroyed(QObject *childContextObject);
bool isReferenced() const {
return scriptRef
@@ -118,27 +126,31 @@ public:
int groupIndex(Compositor::Group group);
+ int modelRow() const { return row; }
+ int modelColumn() const { return column; }
int modelIndex() const { return index; }
- void setModelIndex(int idx) { index = idx; Q_EMIT modelIndexChanged(); }
+ virtual void setModelIndex(int idx, int newRow, int newColumn);
virtual QV4::ReturnedValue get() { return QV4::QObjectWrapper::wrap(v4, this); }
virtual void setValue(const QString &role, const QVariant &value) { Q_UNUSED(role); Q_UNUSED(value); }
virtual bool resolveIndex(const QQmlAdaptorModel &, int) { return false; }
- static void get_model(const QV4::BuiltinFunction *, QV4::Scope &scope, QV4::CallData *callData);
- static void get_groups(const QV4::BuiltinFunction *, QV4::Scope &scope, QV4::CallData *callData);
- static void set_groups(const QV4::BuiltinFunction *, QV4::Scope &scope, QV4::CallData *callData);
+ static QV4::ReturnedValue get_model(const QV4::FunctionObject *, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
+ static QV4::ReturnedValue get_groups(const QV4::FunctionObject *, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
+ static QV4::ReturnedValue set_groups(const QV4::FunctionObject *, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
static QV4::ReturnedValue get_member(QQmlDelegateModelItem *thisItem, uint flag, const QV4::Value &);
static QV4::ReturnedValue set_member(QQmlDelegateModelItem *thisItem, uint flag, const QV4::Value &arg);
static QV4::ReturnedValue get_index(QQmlDelegateModelItem *thisItem, uint flag, const QV4::Value &arg);
QV4::ExecutionEngine *v4;
QQmlDelegateModelItemMetaType * const metaType;
- QQmlContextData *contextData;
+ QQmlContextDataRef contextData;
QPointer<QObject> object;
QPointer<QQmlDelegateModelAttached> attached;
QQDMIncubationTask *incubationTask;
+ QQmlComponent *delegate;
+ int poolTime;
int objectRef;
int scriptRef;
int groups;
@@ -146,9 +158,13 @@ public:
Q_SIGNALS:
void modelIndexChanged();
+ Q_REVISION(12) void rowChanged();
+ Q_REVISION(12) void columnChanged();
protected:
void objectDestroyed(QObject *);
+ int row;
+ int column;
};
namespace QV4 {
@@ -182,14 +198,14 @@ class QQDMIncubationTask : public QQmlIncubator
public:
QQDMIncubationTask(QQmlDelegateModelPrivate *l, IncubationMode mode)
: QQmlIncubator(mode)
- , incubating(0)
+ , incubating(nullptr)
, vdm(l) {}
void statusChanged(Status) override;
void setInitialState(QObject *) override;
- QQmlDelegateModelItem *incubating;
- QQmlDelegateModelPrivate *vdm;
+ QQmlDelegateModelItem *incubating = nullptr;
+ QQmlDelegateModelPrivate *vdm = nullptr;
int index[QQmlListCompositor::MaximumGroupCount];
};
@@ -220,7 +236,7 @@ public:
void setModel(QQmlDelegateModel *model, Compositor::Group group);
bool isChangedConnected();
- void emitChanges(QV8Engine *engine);
+ void emitChanges(QV4::ExecutionEngine *engine);
void emitModelUpdated(bool reset);
void createdPackage(int index, QQuickPackage *package);
@@ -254,9 +270,11 @@ public:
void init();
void connectModel(QQmlAdaptorModel *model);
+ void connectToAbstractItemModel();
+ void disconnectFromAbstractItemModel();
void requestMoreIfNecessary();
- QObject *object(Compositor::Group group, int index, bool asynchronous);
+ QObject *object(Compositor::Group group, int index, QQmlIncubator::IncubationMode incubationMode);
QQmlDelegateModel::ReleaseFlags release(QObject *object);
QString stringValue(Compositor::Group group, int index, const QString &name);
void emitCreatedPackage(QQDMIncubationTask *incubationTask, QQuickPackage *package);
@@ -267,6 +285,7 @@ public:
Q_EMIT q_func()->initItem(incubationTask->index[m_compositorGroup], item); }
void emitDestroyingPackage(QQuickPackage *package);
void emitDestroyingItem(QObject *item) { Q_EMIT q_func()->destroyingItem(item); }
+ void addCacheItem(QQmlDelegateModelItem *item, Compositor::iterator it);
void removeCacheItem(QQmlDelegateModelItem *cacheItem);
void updateFilterGroup();
@@ -278,21 +297,24 @@ public:
void itemsInserted(
const QVector<Compositor::Insert> &inserts,
QVarLengthArray<QVector<QQmlChangeSet::Change>, Compositor::MaximumGroupCount> *translatedInserts,
- QHash<int, QList<QQmlDelegateModelItem *> > *movedItems = 0);
+ QHash<int, QList<QQmlDelegateModelItem *> > *movedItems = nullptr);
void itemsInserted(const QVector<Compositor::Insert> &inserts);
void itemsRemoved(
const QVector<Compositor::Remove> &removes,
QVarLengthArray<QVector<QQmlChangeSet::Change>, Compositor::MaximumGroupCount> *translatedRemoves,
- QHash<int, QList<QQmlDelegateModelItem *> > *movedItems = 0);
+ QHash<int, QList<QQmlDelegateModelItem *> > *movedItems = nullptr);
void itemsRemoved(const QVector<Compositor::Remove> &removes);
void itemsMoved(
const QVector<Compositor::Remove> &removes, const QVector<Compositor::Insert> &inserts);
void itemsChanged(const QVector<Compositor::Change> &changes);
void emitChanges();
void emitModelUpdated(const QQmlChangeSet &changeSet, bool reset) override;
+ void delegateChanged(bool add = true, bool remove = true);
bool insert(Compositor::insert_iterator &before, const QV4::Value &object, int groups);
+ int adaptorModelCount() const;
+
static void group_append(QQmlListProperty<QQmlDelegateModelGroup> *property, QQmlDelegateModelGroup *group);
static int group_count(QQmlListProperty<QQmlDelegateModelGroup> *property);
static QQmlDelegateModelGroup *group_at(QQmlListProperty<QQmlDelegateModelGroup> *property, int index);
@@ -303,7 +325,9 @@ public:
QQmlAdaptorModel m_adaptorModel;
QQmlListCompositor m_compositor;
- QQmlComponent *m_delegate;
+ QQmlStrongJSQObjectReference<QQmlComponent> m_delegate;
+ QQmlAbstractDelegateComponent *m_delegateChooser;
+ QMetaObject::Connection m_delegateChooserChanged;
QQmlDelegateModelItemMetaType *m_cacheMetaType;
QPointer<QQmlContext> m_context;
QQmlDelegateModelParts *m_parts;
@@ -341,7 +365,7 @@ class QQmlPartsModel : public QQmlInstanceModel, public QQmlDelegateModelGroupEm
Q_OBJECT
Q_PROPERTY(QString filterOnGroup READ filterGroup WRITE setFilterGroup NOTIFY filterGroupChanged RESET resetFilterGroup)
public:
- QQmlPartsModel(QQmlDelegateModel *model, const QString &part, QObject *parent = 0);
+ QQmlPartsModel(QQmlDelegateModel *model, const QString &part, QObject *parent = nullptr);
~QQmlPartsModel();
QString filterGroup() const;
@@ -352,11 +376,12 @@ public:
int count() const override;
bool isValid() const override;
- QObject *object(int index, bool asynchronous = false) override;
+ QObject *object(int index, QQmlIncubator::IncubationMode incubationMode = QQmlIncubator::AsynchronousIfNested) override;
ReleaseFlags release(QObject *item) override;
QString stringValue(int index, const QString &role) override;
QList<QByteArray> watchedRoles() const { return m_watchedRoles; }
void setWatchedRoles(const QList<QByteArray> &roles) override;
+ QQmlIncubator::Status incubationStatus(int index) override;
int indexOf(QObject *item, QObject *objectContext) const override;
@@ -375,8 +400,10 @@ private:
QString m_part;
QString m_filterGroup;
QList<QByteArray> m_watchedRoles;
+ QVector<int> m_pendingPackageInitializations; // vector holds model indices
Compositor::Group m_compositorGroup;
bool m_inheritGroup;
+ bool m_modelUpdatePending = true;
};
class QMetaPropertyBuilder;
diff --git a/src/qml/types/qqmlinstantiator.cpp b/src/qml/types/qqmlinstantiator.cpp
index 2de5875deb..a23ec0f2b4 100644
--- a/src/qml/types/qqmlinstantiator.cpp
+++ b/src/qml/types/qqmlinstantiator.cpp
@@ -44,7 +44,9 @@
#include <QtQml/QQmlInfo>
#include <QtQml/QQmlError>
#include <QtQml/private/qqmlobjectmodel_p.h>
+#if QT_CONFIG(qml_delegate_model)
#include <QtQml/private/qqmldelegatemodel_p.h>
+#endif
QT_BEGIN_NAMESPACE
@@ -53,11 +55,13 @@ QQmlInstantiatorPrivate::QQmlInstantiatorPrivate()
, effectiveReset(false)
, active(true)
, async(false)
+#if QT_CONFIG(qml_delegate_model)
, ownModel(false)
+#endif
, requestedIndex(-1)
, model(QVariant(1))
- , instanceModel(0)
- , delegate(0)
+ , instanceModel(nullptr)
+ , delegate(nullptr)
{
}
@@ -85,7 +89,7 @@ void QQmlInstantiatorPrivate::clear()
QObject *QQmlInstantiatorPrivate::modelObject(int index, bool async)
{
requestedIndex = index;
- QObject *o = instanceModel->object(index, async);
+ QObject *o = instanceModel->object(index, async ? QQmlIncubator::Asynchronous : QQmlIncubator::AsynchronousIfNested);
requestedIndex = -1;
return o;
}
@@ -123,7 +127,7 @@ void QQmlInstantiatorPrivate::_q_createdItem(int idx, QObject* item)
if (objects.contains(item)) //Case when it was created synchronously in regenerate
return;
if (requestedIndex != idx) // Asynchronous creation, reference the object
- (void)instanceModel->object(idx, false);
+ (void)instanceModel->object(idx);
item->setParent(q);
if (objects.size() < idx + 1) {
int modelCount = instanceModel->count();
@@ -183,7 +187,7 @@ void QQmlInstantiatorPrivate::_q_modelUpdated(const QQmlChangeSet &changeSet, bo
objects = objects.mid(0, index) + movedObjects + objects.mid(index);
} else {
if (insert.index <= objects.size())
- objects.insert(insert.index, insert.count, 0);
+ objects.insert(insert.index, insert.count, nullptr);
for (int i = 0; i < insert.count; ++i) {
int modelIndex = index + i;
QObject* obj = modelObject(modelIndex, async);
@@ -198,6 +202,7 @@ void QQmlInstantiatorPrivate::_q_modelUpdated(const QQmlChangeSet &changeSet, bo
q->countChanged();
}
+#if QT_CONFIG(qml_delegate_model)
void QQmlInstantiatorPrivate::makeModel()
{
Q_Q(QQmlInstantiator);
@@ -209,13 +214,14 @@ void QQmlInstantiatorPrivate::makeModel()
if (componentComplete)
delegateModel->componentComplete();
}
+#endif
/*!
\qmltype Instantiator
\instantiates QQmlInstantiator
\inqmlmodule QtQml
- \brief Dynamically creates objects
+ \brief Dynamically creates objects.
A Instantiator can be used to control the dynamic creation of objects, or to dynamically
create multiple objects from a template.
@@ -349,6 +355,7 @@ void QQmlInstantiator::setDelegate(QQmlComponent* c)
d->delegate = c;
emit delegateChanged();
+#if QT_CONFIG(qml_delegate_model)
if (!d->ownModel)
return;
@@ -356,6 +363,7 @@ void QQmlInstantiator::setDelegate(QQmlComponent* c)
dModel->setDelegate(c);
if (d->componentComplete)
d->regenerate();
+#endif
}
/*!
@@ -396,14 +404,17 @@ void QQmlInstantiator::setModel(const QVariant &v)
QQmlInstanceModel *prevModel = d->instanceModel;
QObject *object = qvariant_cast<QObject*>(v);
- QQmlInstanceModel *vim = 0;
+ QQmlInstanceModel *vim = nullptr;
if (object && (vim = qobject_cast<QQmlInstanceModel *>(object))) {
+#if QT_CONFIG(qml_delegate_model)
if (d->ownModel) {
delete d->instanceModel;
- prevModel = 0;
+ prevModel = nullptr;
d->ownModel = false;
}
+#endif
d->instanceModel = vim;
+#if QT_CONFIG(qml_delegate_model)
} else if (v != QVariant(0)){
if (!d->ownModel)
d->makeModel();
@@ -413,6 +424,7 @@ void QQmlInstantiator::setModel(const QVariant &v)
dataModel->setModel(v);
d->effectiveReset = false;
}
+#endif
}
if (d->instanceModel != prevModel) {
@@ -423,10 +435,12 @@ void QQmlInstantiator::setModel(const QVariant &v)
//disconnect(prevModel, SIGNAL(initItem(int,QObject*)), this, SLOT(initItem(int,QObject*)));
}
- connect(d->instanceModel, SIGNAL(modelUpdated(QQmlChangeSet,bool)),
- this, SLOT(_q_modelUpdated(QQmlChangeSet,bool)));
- connect(d->instanceModel, SIGNAL(createdItem(int,QObject*)), this, SLOT(_q_createdItem(int,QObject*)));
- //connect(d->instanceModel, SIGNAL(initItem(int,QObject*)), this, SLOT(initItem(int,QObject*)));
+ if (d->instanceModel) {
+ connect(d->instanceModel, SIGNAL(modelUpdated(QQmlChangeSet,bool)),
+ this, SLOT(_q_modelUpdated(QQmlChangeSet,bool)));
+ connect(d->instanceModel, SIGNAL(createdItem(int,QObject*)), this, SLOT(_q_createdItem(int,QObject*)));
+ //connect(d->instanceModel, SIGNAL(initItem(int,QObject*)), this, SLOT(initItem(int,QObject*)));
+ }
}
d->regenerate();
@@ -444,7 +458,7 @@ QObject *QQmlInstantiator::object() const
Q_D(const QQmlInstantiator);
if (d->objects.count())
return d->objects[0];
- return 0;
+ return nullptr;
}
/*!
@@ -457,7 +471,7 @@ QObject *QQmlInstantiator::objectAt(int index) const
Q_D(const QQmlInstantiator);
if (index >= 0 && index < d->objects.count())
return d->objects[index];
- return 0;
+ return nullptr;
}
/*!
@@ -476,10 +490,13 @@ void QQmlInstantiator::componentComplete()
{
Q_D(QQmlInstantiator);
d->componentComplete = true;
+#if QT_CONFIG(qml_delegate_model)
if (d->ownModel) {
static_cast<QQmlDelegateModel*>(d->instanceModel)->componentComplete();
d->regenerate();
- } else {
+ } else
+#endif
+ {
QVariant realModel = d->model;
d->model = QVariant(0);
setModel(realModel); //If realModel == d->model this won't do anything, but that's fine since the model's 0
diff --git a/src/qml/types/qqmlinstantiator_p.h b/src/qml/types/qqmlinstantiator_p.h
index ee18daa48c..ca371adc23 100644
--- a/src/qml/types/qqmlinstantiator_p.h
+++ b/src/qml/types/qqmlinstantiator_p.h
@@ -53,11 +53,12 @@
#include <QtQml/qqmlcomponent.h>
#include <QtQml/qqmlparserstatus.h>
+#include <QtQml/private/qtqmlglobal_p.h>
QT_BEGIN_NAMESPACE
class QQmlInstantiatorPrivate;
-class Q_AUTOTEST_EXPORT QQmlInstantiator : public QObject, public QQmlParserStatus
+class Q_QML_PRIVATE_EXPORT QQmlInstantiator : public QObject, public QQmlParserStatus
{
Q_OBJECT
Q_INTERFACES(QQmlParserStatus)
@@ -71,7 +72,7 @@ class Q_AUTOTEST_EXPORT QQmlInstantiator : public QObject, public QQmlParserStat
Q_CLASSINFO("DefaultProperty", "delegate")
public:
- QQmlInstantiator(QObject *parent = 0);
+ QQmlInstantiator(QObject *parent = nullptr);
~QQmlInstantiator();
bool isActive() const;
diff --git a/src/qml/types/qqmlinstantiator_p_p.h b/src/qml/types/qqmlinstantiator_p_p.h
index 9edaecf7a8..4c76d5c689 100644
--- a/src/qml/types/qqmlinstantiator_p_p.h
+++ b/src/qml/types/qqmlinstantiator_p_p.h
@@ -59,7 +59,7 @@
QT_BEGIN_NAMESPACE
-class QQmlInstantiatorPrivate : public QObjectPrivate
+class Q_QML_PRIVATE_EXPORT QQmlInstantiatorPrivate : public QObjectPrivate
{
Q_DECLARE_PUBLIC(QQmlInstantiator)
@@ -69,16 +69,23 @@ public:
void clear();
void regenerate();
+#if QT_CONFIG(qml_delegate_model)
void makeModel();
+#endif
void _q_createdItem(int, QObject *);
void _q_modelUpdated(const QQmlChangeSet &, bool);
QObject *modelObject(int index, bool async);
+ static QQmlInstantiatorPrivate *get(QQmlInstantiator *instantiator) { return instantiator->d_func(); }
+ static const QQmlInstantiatorPrivate *get(const QQmlInstantiator *instantiator) { return instantiator->d_func(); }
+
bool componentComplete:1;
bool effectiveReset:1;
bool active:1;
bool async:1;
+#if QT_CONFIG(qml_delegate_model)
bool ownModel:1;
+#endif
int requestedIndex;
QVariant model;
QQmlInstanceModel *instanceModel;
diff --git a/src/qml/types/qqmlitemmodels.qdoc b/src/qml/types/qqmlitemmodels.qdoc
index 6733330209..f6e1b0b1b9 100644
--- a/src/qml/types/qqmlitemmodels.qdoc
+++ b/src/qml/types/qqmlitemmodels.qdoc
@@ -1,7 +1,7 @@
/****************************************************************************
**
-** Copyright (C) 2015 The Qt Company Ltd.
-** Contact: http://www.qt.io/licensing/
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
**
** This file is part of the documentation of the Qt Toolkit.
**
@@ -11,8 +11,8 @@
** 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 http://www.qt.io/terms-conditions. For further
-** information use the contact form at http://www.qt.io/contact-us.
+** 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
@@ -20,7 +20,7 @@
** 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: http://www.gnu.org/copyleft/fdl.html.
+** will be met: https://www.gnu.org/licenses/fdl-1.3.html.
** $QT_END_LICENSE$
**
****************************************************************************/
diff --git a/src/qml/types/qqmlitemselectionmodel.qdoc b/src/qml/types/qqmlitemselectionmodel.qdoc
index c223ef614e..43da4f7a55 100644
--- a/src/qml/types/qqmlitemselectionmodel.qdoc
+++ b/src/qml/types/qqmlitemselectionmodel.qdoc
@@ -1,7 +1,7 @@
/****************************************************************************
**
-** Copyright (C) 2015 The Qt Company Ltd.
-** Contact: http://www.qt.io/licensing/
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
**
** This file is part of the documentation of the Qt Toolkit.
**
@@ -11,8 +11,8 @@
** 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 http://www.qt.io/terms-conditions. For further
-** information use the contact form at http://www.qt.io/contact-us.
+** 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
@@ -20,7 +20,7 @@
** 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: http://www.gnu.org/copyleft/fdl.html.
+** will be met: https://www.gnu.org/licenses/fdl-1.3.html.
** $QT_END_LICENSE$
**
****************************************************************************/
@@ -35,9 +35,6 @@
\brief Instantiates a QItemSelectionModel to be used in conjunction
with a QAbstractItemModel and any view supporting it.
- This page only enumerates the properties, methods, and signals available in QML.
- See \l QItemSelectionModel for the actual documentation of this class.
-
\sa QItemSelectionModel, {Models and Views in Qt Quick}
*/
@@ -55,7 +52,7 @@
It will trigger property binding updates every time \l selectionChanged()
is emitted, even though its value hasn't changed.
- \sa selection(), selectedIndexes, select(), selectionChanged()
+ \sa selection, selectedIndexes, select(), selectionChanged()
*/
/*!
@@ -76,68 +73,167 @@
/*!
\qmlmethod bool ItemSelectionModel::isSelected(QModelIndex index)
+
+ Returns \c true if the given model item \a index is selected.
*/
/*!
\qmlmethod bool ItemSelectionModel::isRowSelected(int row, QModelIndex parent)
+
+ Returns \c true if all items are selected in the \a row with the given
+ \a parent.
+
+ Note that this function is usually faster than calling isSelected()
+ on all items in the same row, and that unselectable items are ignored.
*/
/*!
\qmlmethod bool ItemSelectionModel::isColumnSelected(int column, QModelIndex parent)
+
+ Returns \c true if all items are selected in the \a column with the given
+ \a parent.
+
+ Note that this function is usually faster than calling isSelected()
+ on all items in the same column, and that unselectable items are ignored.
*/
/*!
\qmlmethod bool ItemSelectionModel::rowIntersectsSelection(int row, QModelIndex parent)
+
+ Returns \c true if there are any items selected in the \a row with the
+ given \a parent.
*/
/*!
\qmlmethod bool ItemSelectionModel::columnIntersectsSelection(int column, QModelIndex parent)
+
+ Returns \c true if there are any items selected in the \a column with the
+ given \a parent.
*/
/*!
\qmlmethod QModelIndexList ItemSelectionModel::selectedRows(int column)
+
+ Returns the indexes in the given \a column for the rows where all columns
+ are selected.
+
+ \sa selectedColumns()
*/
/*!
\qmlmethod QModelIndexList ItemSelectionModel::selectedColumns(int row)
+
+ Returns the indexes in the given \a row for columns where all rows are
+ selected.
+
+ \sa selectedRows()
*/
/*!
- \qmlmethod QItemSelection ItemSelectionModel::selection()
+ \qmlproperty object ItemSelectionModel::selection
+ \readonly
+
+ Holds the selection ranges stored in the selection model.
*/
/*!
\qmlmethod void ItemSelectionModel::setCurrentIndex(QModelIndex index, SelectionFlags command)
+
+ Sets the model item \a index to be the current item, and emits
+ currentChanged(). The current item is used for keyboard navigation and
+ focus indication; it is independent of any selected items, although a
+ selected item can also be the current item.
+
+ Depending on the specified \a command, the \a index can also become part
+ of the current selection.
+
+ Valid \a command values are described in \l {itemselectionmodelselectindex}
+ {select(\e index, \e command)}.
+
+ \sa select()
*/
/*!
\qmlmethod void ItemSelectionModel::select(QModelIndex index, SelectionFlags command)
+ \keyword itemselectionmodelselectindex
+
+ Selects the model item \a index using the specified \a command, and emits
+ selectionChanged().
+
+ Valid values for the \a command parameter, are:
+
+ \value NoUpdate No selection will be made.
+ \value Clear The complete selection will be cleared.
+ \value Select All specified indexes will be selected.
+ \value Deselect All specified indexes will be deselected.
+ \value Toggle All specified indexes will be selected or
+ deselected depending on their current state.
+ \value Current The current selection will be updated.
+ \value Rows All indexes will be expanded to span rows.
+ \value Columns All indexes will be expanded to span columns.
+ \value SelectCurrent A combination of Select and Current, provided for
+ convenience.
+ \value ToggleCurrent A combination of Toggle and Current, provided for
+ convenience.
+ \value ClearAndSelect A combination of Clear and Select, provided for
+ convenience.
*/
/*!
\qmlmethod void ItemSelectionModel::select(QItemSelection selection, SelectionFlags command)
+
+ Selects the item \a selection using the specified \a command, and emits
+ selectionChanged().
+
+ Valid \a command values are described in \l {itemselectionmodelselectindex}
+ {select(\e index, \e command)}.
*/
/*!
\qmlmethod void ItemSelectionModel::clear()
+
+ Clears the selection model. Emits selectionChanged() and currentChanged().
*/
/*!
\qmlmethod void ItemSelectionModel::reset()
+
+ Clears the selection model. Does not emit any signals.
*/
/*!
\qmlmethod void ItemSelectionModel::clearSelection()
+
+ Clears the selection in the selection model. Emits selectionChanged().
*/
/*!
\qmlmethod void ItemSelectionModel::clearCurrentIndex()
+
+ Clears the current index. Emits currentChanged().
*/
/*!
\qmlsignal ItemSelectionModel::selectionChanged(QItemSelection selected, QItemSelection deselected)
+
+ This signal is emitted whenever the selection changes. The change in the
+ selection is represented as an item selection of \a deselected items and
+ an item selection of \a selected items.
+
+ Note the that the current index changes independently from the selection.
+ Also note that this signal will not be emitted when the item model is reset.
+
+ \sa select(), currentChanged()
*/
/*!
\qmlsignal ItemSelectionModel::currentChanged(QModelIndex current, QModelIndex previous)
+
+ This signal is emitted whenever the current item changes. The \a previous
+ model item index is replaced by the \a current index as the selection's
+ current item.
+
+ Note that this signal will not be emitted when the item model is reset.
+
+ \sa currentIndex, setCurrentIndex(), selectionChanged()
*/
diff --git a/src/qml/types/qqmllistmodel.cpp b/src/qml/types/qqmllistmodel.cpp
index 517411fa47..27171b9bd4 100644
--- a/src/qml/types/qqmllistmodel.cpp
+++ b/src/qml/types/qqmllistmodel.cpp
@@ -42,6 +42,7 @@
#include <private/qqmlopenmetaobject_p.h>
#include <private/qqmljsast_p.h>
#include <private/qqmljsengine_p.h>
+#include <private/qjsvalue_p.h>
#include <private/qqmlcustomparser_p.h>
#include <private/qqmlengine_p.h>
@@ -61,6 +62,8 @@
#include <QtCore/qdatetime.h>
#include <QScopedValueRollback>
+Q_DECLARE_METATYPE(const QV4::CompiledData::Binding*);
+
QT_BEGIN_NAMESPACE
// Set to 1024 as a debugging aid - easier to distinguish uids from indices of elements/models.
@@ -84,7 +87,7 @@ static QString roleTypeName(ListLayout::Role::DataType t)
static const QString roleTypeNames[] = {
QStringLiteral("String"), QStringLiteral("Number"), QStringLiteral("Bool"),
QStringLiteral("List"), QStringLiteral("QObject"), QStringLiteral("VariantMap"),
- QStringLiteral("DateTime")
+ QStringLiteral("DateTime"), QStringLiteral("Function")
};
if (t > ListLayout::Role::Invalid && t < ListLayout::Role::MaxDataType)
@@ -99,7 +102,7 @@ const ListLayout::Role &ListLayout::getRoleOrCreate(const QString &key, Role::Da
if (node) {
const Role &r = *node->value;
if (type != r.type)
- qmlWarning(0) << QStringLiteral("Can't assign to existing role '%1' of different type [%2 -> %3]").arg(r.name).arg(roleTypeName(type)).arg(roleTypeName(r.type));
+ qmlWarning(nullptr) << QStringLiteral("Can't assign to existing role '%1' of different type [%2 -> %3]").arg(r.name).arg(roleTypeName(type)).arg(roleTypeName(r.type));
return r;
}
@@ -112,7 +115,7 @@ const ListLayout::Role &ListLayout::getRoleOrCreate(QV4::String *key, Role::Data
if (node) {
const Role &r = *node->value;
if (type != r.type)
- qmlWarning(0) << QStringLiteral("Can't assign to existing role '%1' of different type [%2 -> %3]").arg(r.name).arg(roleTypeName(type)).arg(roleTypeName(r.type));
+ qmlWarning(nullptr) << QStringLiteral("Can't assign to existing role '%1' of different type [%2 -> %3]").arg(r.name).arg(roleTypeName(type)).arg(roleTypeName(r.type));
return r;
}
@@ -123,8 +126,8 @@ const ListLayout::Role &ListLayout::getRoleOrCreate(QV4::String *key, Role::Data
const ListLayout::Role &ListLayout::createRole(const QString &key, ListLayout::Role::DataType type)
{
- const int dataSizes[] = { sizeof(QString), sizeof(double), sizeof(bool), sizeof(ListModel *), sizeof(QPointer<QObject>), sizeof(QVariantMap), sizeof(QDateTime) };
- const int dataAlignments[] = { sizeof(QString), sizeof(double), sizeof(bool), sizeof(ListModel *), sizeof(QObject *), sizeof(QVariantMap), sizeof(QDateTime) };
+ const int dataSizes[] = { sizeof(StringOrTranslation), sizeof(double), sizeof(bool), sizeof(ListModel *), sizeof(QPointer<QObject>), sizeof(QVariantMap), sizeof(QDateTime), sizeof(QJSValue) };
+ const int dataAlignments[] = { sizeof(StringOrTranslation), sizeof(double), sizeof(bool), sizeof(ListModel *), sizeof(QObject *), sizeof(QVariantMap), sizeof(QDateTime), sizeof(QJSValue) };
Role *r = new Role;
r->name = key;
@@ -133,7 +136,7 @@ const ListLayout::Role &ListLayout::createRole(const QString &key, ListLayout::R
if (type == Role::List) {
r->subLayout = new ListLayout;
} else {
- r->subLayout = 0;
+ r->subLayout = nullptr;
}
int dataSize = dataSizes[type];
@@ -202,7 +205,7 @@ ListLayout::Role::Role(const Role *other)
if (other->subLayout)
subLayout = new ListLayout(other->subLayout);
else
- subLayout = 0;
+ subLayout = nullptr;
}
ListLayout::Role::~Role()
@@ -217,17 +220,30 @@ const ListLayout::Role *ListLayout::getRoleOrCreate(const QString &key, const QV
switch (data.type()) {
case QVariant::Double: type = Role::Number; break;
case QVariant::Int: type = Role::Number; break;
- case QVariant::UserType: type = Role::List; break;
case QVariant::Bool: type = Role::Bool; break;
case QVariant::String: type = Role::String; break;
case QVariant::Map: type = Role::VariantMap; break;
case QVariant::DateTime: type = Role::DateTime; break;
+ case QVariant::UserType: {
+ if (data.userType() == qMetaTypeId<QJSValue>() &&
+ data.value<QJSValue>().isCallable()) {
+ type = Role::Function;
+ break;
+ } else if (data.userType() == qMetaTypeId<const QV4::CompiledData::Binding*>()
+ && data.value<const QV4::CompiledData::Binding*>()->isTranslationBinding()) {
+ type = Role::String;
+ break;
+ } else {
+ type = Role::List;
+ break;
+ }
+ }
default: type = Role::Invalid; break;
}
if (type == Role::Invalid) {
- qmlWarning(0) << "Can't create role for unsupported data type";
- return 0;
+ qmlWarning(nullptr) << "Can't create role for unsupported data type";
+ return nullptr;
}
return &getRoleOrCreate(key, type);
@@ -235,7 +251,7 @@ const ListLayout::Role *ListLayout::getRoleOrCreate(const QString &key, const QV
const ListLayout::Role *ListLayout::getExistingRole(const QString &key) const
{
- Role *r = 0;
+ Role *r = nullptr;
QStringHash<Role *>::Node *node = roleHash.findNode(key);
if (node)
r = node->value;
@@ -244,40 +260,114 @@ const ListLayout::Role *ListLayout::getExistingRole(const QString &key) const
const ListLayout::Role *ListLayout::getExistingRole(QV4::String *key) const
{
- Role *r = 0;
+ Role *r = nullptr;
QStringHash<Role *>::Node *node = roleHash.findNode(key);
if (node)
r = node->value;
return r;
}
+StringOrTranslation::StringOrTranslation(const QString &s)
+{
+ d.setFlag();
+ setString(s);
+}
+
+StringOrTranslation::StringOrTranslation(const QV4::CompiledData::Binding *binding)
+{
+ d.setFlag();
+ clear();
+ d = binding;
+}
+
+StringOrTranslation::~StringOrTranslation()
+{
+ clear();
+}
+
+void StringOrTranslation::setString(const QString &s)
+{
+ d.setFlag();
+ clear();
+ QStringData *stringData = const_cast<QString &>(s).data_ptr();
+ d = stringData;
+ if (stringData)
+ stringData->ref.ref();
+}
+
+void StringOrTranslation::setTranslation(const QV4::CompiledData::Binding *binding)
+{
+ d.setFlag();
+ clear();
+ d = binding;
+}
+
+QString StringOrTranslation::toString(const QQmlListModel *owner) const
+{
+ if (d.isNull())
+ return QString();
+ if (d.isT1()) {
+ QStringDataPtr holder = { d.asT1() };
+ holder.ptr->ref.ref();
+ return QString(holder);
+ }
+ if (!owner)
+ return QString();
+ return d.asT2()->valueAsString(owner->m_compilationUnit.data());
+}
+
+QString StringOrTranslation::asString() const
+{
+ if (d.isNull())
+ return QString();
+ if (!d.isT1())
+ return QString();
+ QStringDataPtr holder = { d.asT1() };
+ holder.ptr->ref.ref();
+ return QString(holder);
+}
+
+void StringOrTranslation::clear()
+{
+ if (QStringData *strData = d.isT1() ? d.asT1() : nullptr) {
+ if (!strData->ref.deref())
+ QStringData::deallocate(strData);
+ }
+ d = static_cast<QStringData *>(nullptr);
+}
+
QObject *ListModel::getOrCreateModelObject(QQmlListModel *model, int elementIndex)
{
ListElement *e = elements[elementIndex];
- if (e->m_objectCache == 0) {
- e->m_objectCache = new QObject;
+ if (e->m_objectCache == nullptr) {
+ void *memory = operator new(sizeof(QObject) + sizeof(QQmlData));
+ void *ddataMemory = ((char *)memory) + sizeof(QObject);
+ e->m_objectCache = new (memory) QObject;
+ QQmlData *ddata = new (ddataMemory) QQmlData;
+ ddata->ownMemory = false;
+ QObjectPrivate::get(e->m_objectCache)->declarativeData = ddata;
(void)new ModelNodeMetaObject(e->m_objectCache, model, elementIndex);
}
return e->m_objectCache;
}
-void ListModel::sync(ListModel *src, ListModel *target, QHash<int, ListModel *> *targetModelHash)
+bool ListModel::sync(ListModel *src, ListModel *target)
{
// Sanity check
- target->m_uid = src->m_uid;
- if (targetModelHash)
- targetModelHash->insert(target->m_uid, target);
+
+ bool hasChanges = false;
// Build hash of elements <-> uid for each of the lists
QHash<int, ElementSync> elementHash;
- for (int i=0 ; i < target->elements.count() ; ++i) {
+ for (int i = 0; i < target->elements.count(); ++i) {
ListElement *e = target->elements.at(i);
int uid = e->getUid();
ElementSync sync;
sync.target = e;
+ sync.targetIndex = i;
elementHash.insert(uid, sync);
}
- for (int i=0 ; i < src->elements.count() ; ++i) {
+ for (int i = 0; i < src->elements.count(); ++i) {
ListElement *e = src->elements.at(i);
int uid = e->getUid();
@@ -285,24 +375,39 @@ void ListModel::sync(ListModel *src, ListModel *target, QHash<int, ListModel *>
if (it == elementHash.end()) {
ElementSync sync;
sync.src = e;
+ sync.srcIndex = i;
elementHash.insert(uid, sync);
} else {
ElementSync &sync = it.value();
sync.src = e;
+ sync.srcIndex = i;
}
}
+ QQmlListModel *targetModel = target->m_modelCache;
+
// Get list of elements that are in the target but no longer in the source. These get deleted first.
- QHash<int, ElementSync>::iterator it = elementHash.begin();
- QHash<int, ElementSync>::iterator end = elementHash.end();
- while (it != end) {
- const ElementSync &s = it.value();
- if (s.src == 0) {
+ int rowsRemoved = 0;
+ for (int i = 0 ; i < target->elements.count() ; ++i) {
+ ListElement *element = target->elements.at(i);
+ ElementSync &s = elementHash.find(element->getUid()).value();
+ Q_ASSERT(s.targetIndex >= 0);
+ // need to update the targetIndex, to keep it correct after removals
+ s.targetIndex -= rowsRemoved;
+ if (s.src == nullptr) {
+ Q_ASSERT(s.targetIndex == i);
+ hasChanges = true;
+ if (targetModel)
+ targetModel->beginRemoveRows(QModelIndex(), i, i);
s.target->destroy(target->m_layout);
target->elements.removeOne(s.target);
delete s.target;
+ if (targetModel)
+ targetModel->endRemoveRows();
+ ++rowsRemoved;
+ --i;
+ continue;
}
- ++it;
}
// Sync the layouts
@@ -310,15 +415,15 @@ void ListModel::sync(ListModel *src, ListModel *target, QHash<int, ListModel *>
// Clear the target list, and append in correct order from the source
target->elements.clear();
- for (int i=0 ; i < src->elements.count() ; ++i) {
+ for (int i = 0; i < src->elements.count(); ++i) {
ListElement *srcElement = src->elements.at(i);
- it = elementHash.find(srcElement->getUid());
- const ElementSync &s = it.value();
+ ElementSync &s = elementHash.find(srcElement->getUid()).value();
+ Q_ASSERT(s.srcIndex >= 0);
ListElement *targetElement = s.target;
- if (targetElement == 0) {
+ if (targetElement == nullptr) {
targetElement = new ListElement(srcElement->getUid());
}
- ListElement::sync(srcElement, src->m_layout, targetElement, target->m_layout, targetModelHash);
+ s.changedRoles = ListElement::sync(srcElement, src->m_layout, targetElement, target->m_layout);
target->elements.append(targetElement);
}
@@ -330,23 +435,54 @@ void ListModel::sync(ListModel *src, ListModel *target, QHash<int, ListModel *>
if (ModelNodeMetaObject *mo = e->objectCache())
mo->updateValues();
}
+
+ // now emit the change notifications required. This can be safely done, as we're only emitting changes, moves and inserts,
+ // so the model indices can't be out of bounds
+ //
+ // to ensure things are kept in the correct order, emit inserts and moves first. This shouls ensure all persistent
+ // model indices are updated correctly
+ int rowsInserted = 0;
+ for (int i = 0 ; i < target->elements.count() ; ++i) {
+ ListElement *element = target->elements.at(i);
+ ElementSync &s = elementHash.find(element->getUid()).value();
+ Q_ASSERT(s.srcIndex >= 0);
+ s.srcIndex += rowsInserted;
+ if (s.srcIndex != s.targetIndex) {
+ if (targetModel) {
+ if (s.targetIndex == -1) {
+ targetModel->beginInsertRows(QModelIndex(), i, i);
+ targetModel->endInsertRows();
+ } else {
+ targetModel->beginMoveRows(QModelIndex(), i, i, QModelIndex(), s.srcIndex);
+ targetModel->endMoveRows();
+ }
+ }
+ hasChanges = true;
+ ++rowsInserted;
+ }
+ if (s.targetIndex != -1 && !s.changedRoles.isEmpty()) {
+ QModelIndex idx = targetModel->createIndex(i, 0);
+ if (targetModel)
+ targetModel->dataChanged(idx, idx, s.changedRoles);
+ hasChanges = true;
+ }
+ }
+ return hasChanges;
}
-ListModel::ListModel(ListLayout *layout, QQmlListModel *modelCache, int uid) : m_layout(layout), m_modelCache(modelCache)
+ListModel::ListModel(ListLayout *layout, QQmlListModel *modelCache) : m_layout(layout), m_modelCache(modelCache)
{
- if (uid == -1)
- uid = uidCounter.fetchAndAddOrdered(1);
- m_uid = uid;
}
void ListModel::destroy()
{
- clear();
- m_uid = -1;
- m_layout = 0;
+ for (const auto &destroyer : remove(0, elements.count()))
+ destroyer();
+
+ m_layout = nullptr;
if (m_modelCache && m_modelCache->m_primary == false)
delete m_modelCache;
- m_modelCache = 0;
+ m_modelCache = nullptr;
}
int ListModel::appendElement()
@@ -427,7 +563,7 @@ void ListModel::set(int elementIndex, QV4::Object *object, QVector<int> *roles)
QV4::Scope scope(v4);
QV4::ScopedObject o(scope);
- QV4::ObjectIterator it(scope, object, QV4::ObjectIterator::WithProtoChain|QV4::ObjectIterator::EnumerableOnly);
+ QV4::ObjectIterator it(scope, object, QV4::ObjectIterator::EnumerableOnly);
QV4::ScopedString propertyName(scope);
QV4::ScopedValue propertyValue(scope);
while (1) {
@@ -447,11 +583,11 @@ void ListModel::set(int elementIndex, QV4::Object *object, QVector<int> *roles)
roleIndex = e->setDoubleProperty(r, propertyValue->asDouble());
} else if (QV4::ArrayObject *a = propertyValue->as<QV4::ArrayObject>()) {
const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::List);
- ListModel *subModel = new ListModel(r.subLayout, 0, -1);
+ ListModel *subModel = new ListModel(r.subLayout, nullptr);
int arrayLength = a->getLength();
for (int j=0 ; j < arrayLength ; ++j) {
- o = a->getIndexed(j);
+ o = a->get(j);
subModel->append(o);
}
@@ -463,6 +599,12 @@ void ListModel::set(int elementIndex, QV4::Object *object, QVector<int> *roles)
const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::DateTime);
QDateTime dt = dd->toQDateTime();
roleIndex = e->setDateTimeProperty(r, dt);
+ } else if (QV4::FunctionObject *f = propertyValue->as<QV4::FunctionObject>()) {
+ const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::Function);
+ QV4::ScopedFunctionObject func(scope, f);
+ QJSValue jsv;
+ QJSValuePrivate::setValue(&jsv, v4, func);
+ roleIndex = e->setFunctionProperty(r, jsv);
} else if (QV4::Object *o = propertyValue->as<QV4::Object>()) {
if (QV4::QObjectWrapper *wrapper = o->as<QV4::QObjectWrapper>()) {
QObject *o = wrapper->object();
@@ -500,7 +642,7 @@ void ListModel::set(int elementIndex, QV4::Object *object)
QV4::ExecutionEngine *v4 = object->engine();
QV4::Scope scope(v4);
- QV4::ObjectIterator it(scope, object, QV4::ObjectIterator::WithProtoChain|QV4::ObjectIterator::EnumerableOnly);
+ QV4::ObjectIterator it(scope, object, QV4::ObjectIterator::EnumerableOnly);
QV4::ScopedString propertyName(scope);
QV4::ScopedValue propertyValue(scope);
QV4::ScopedObject o(scope);
@@ -522,11 +664,11 @@ void ListModel::set(int elementIndex, QV4::Object *object)
} else if (QV4::ArrayObject *a = propertyValue->as<QV4::ArrayObject>()) {
const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::List);
if (r.type == ListLayout::Role::List) {
- ListModel *subModel = new ListModel(r.subLayout, 0, -1);
+ ListModel *subModel = new ListModel(r.subLayout, nullptr);
int arrayLength = a->getLength();
for (int j=0 ; j < arrayLength ; ++j) {
- o = a->getIndexed(j);
+ o = a->get(j);
subModel->append(o);
}
@@ -562,24 +704,20 @@ void ListModel::set(int elementIndex, QV4::Object *object)
}
}
-void ListModel::clear()
-{
- int elementCount = elements.count();
- for (int i=0 ; i < elementCount ; ++i) {
- elements[i]->destroy(m_layout);
- delete elements[i];
- }
- elements.clear();
-}
-
-void ListModel::remove(int index, int count)
+QVector<std::function<void()>> ListModel::remove(int index, int count)
{
+ QVector<std::function<void()>> toDestroy;
+ auto layout = m_layout;
for (int i=0 ; i < count ; ++i) {
- elements[index+i]->destroy(m_layout);
- delete elements[index+i];
+ auto element = elements[index+i];
+ toDestroy.append([element, layout](){
+ element->destroy(layout);
+ delete element;
+ });
}
elements.remove(index, count);
updateCacheIndices(index);
+ return toDestroy;
}
void ListModel::insert(int elementIndex, QV4::Object *object)
@@ -635,7 +773,7 @@ inline char *ListElement::getPropertyMemory(const ListLayout::Role &role)
ListElement *e = this;
int blockIndex = 0;
while (blockIndex < role.blockIndex) {
- if (e->next == 0) {
+ if (e->next == nullptr) {
e->next = new ListElement;
e->next->uid = uid;
}
@@ -650,15 +788,15 @@ inline char *ListElement::getPropertyMemory(const ListLayout::Role &role)
ModelNodeMetaObject *ListElement::objectCache()
{
if (!m_objectCache)
- return 0;
+ return nullptr;
return ModelNodeMetaObject::get(m_objectCache);
}
-QString *ListElement::getStringProperty(const ListLayout::Role &role)
+StringOrTranslation *ListElement::getStringProperty(const ListLayout::Role &role)
{
char *mem = getPropertyMemory(role);
- QString *s = reinterpret_cast<QString *>(mem);
- return s->data_ptr() ? s : 0;
+ StringOrTranslation *s = reinterpret_cast<StringOrTranslation *>(mem);
+ return s;
}
QObject *ListElement::getQObjectProperty(const ListLayout::Role &role)
@@ -670,7 +808,7 @@ QObject *ListElement::getQObjectProperty(const ListLayout::Role &role)
QVariantMap *ListElement::getVariantMapProperty(const ListLayout::Role &role)
{
- QVariantMap *map = 0;
+ QVariantMap *map = nullptr;
char *mem = getPropertyMemory(role);
if (isMemoryUsed<QVariantMap>(mem))
@@ -681,7 +819,7 @@ QVariantMap *ListElement::getVariantMapProperty(const ListLayout::Role &role)
QDateTime *ListElement::getDateTimeProperty(const ListLayout::Role &role)
{
- QDateTime *dt = 0;
+ QDateTime *dt = nullptr;
char *mem = getPropertyMemory(role);
if (isMemoryUsed<QDateTime>(mem))
@@ -690,6 +828,17 @@ QDateTime *ListElement::getDateTimeProperty(const ListLayout::Role &role)
return dt;
}
+QJSValue *ListElement::getFunctionProperty(const ListLayout::Role &role)
+{
+ QJSValue *f = nullptr;
+
+ char *mem = getPropertyMemory(role);
+ if (isMemoryUsed<QJSValue>(mem))
+ f = reinterpret_cast<QJSValue *>(mem);
+
+ return f;
+}
+
QPointer<QObject> *ListElement::getGuardProperty(const ListLayout::Role &role)
{
char *mem = getPropertyMemory(role);
@@ -702,7 +851,7 @@ QPointer<QObject> *ListElement::getGuardProperty(const ListLayout::Role &role)
}
}
- QPointer<QObject> *o = 0;
+ QPointer<QObject> *o = nullptr;
if (existingGuard)
o = reinterpret_cast<QPointer<QObject> *>(mem);
@@ -732,9 +881,9 @@ QVariant ListElement::getProperty(const ListLayout::Role &role, const QQmlListMo
break;
case ListLayout::Role::String:
{
- QString *value = reinterpret_cast<QString *>(mem);
- if (value->data_ptr() != 0)
- data = *value;
+ StringOrTranslation *value = reinterpret_cast<StringOrTranslation *>(mem);
+ if (value->isSet())
+ data = value->toString(owner);
}
break;
case ListLayout::Role::Bool:
@@ -749,7 +898,7 @@ QVariant ListElement::getProperty(const ListLayout::Role &role, const QQmlListMo
ListModel *model = *value;
if (model) {
- if (model->m_modelCache == 0) {
+ if (model->m_modelCache == nullptr) {
model->m_modelCache = new QQmlListModel(owner, model, eng);
QQmlEngine::setContextForObject(model->m_modelCache, QQmlEngine::contextForObject(owner));
}
@@ -783,6 +932,14 @@ QVariant ListElement::getProperty(const ListLayout::Role &role, const QQmlListMo
}
}
break;
+ case ListLayout::Role::Function:
+ {
+ if (isMemoryUsed<QJSValue>(mem)) {
+ QJSValue *func = reinterpret_cast<QJSValue *>(mem);
+ data = QVariant::fromValue(*func);
+ }
+ }
+ break;
default:
break;
}
@@ -796,15 +953,13 @@ int ListElement::setStringProperty(const ListLayout::Role &role, const QString &
if (role.type == ListLayout::Role::String) {
char *mem = getPropertyMemory(role);
- QString *c = reinterpret_cast<QString *>(mem);
+ StringOrTranslation *c = reinterpret_cast<StringOrTranslation *>(mem);
bool changed;
- if (c->data_ptr() == 0) {
- new (mem) QString(s);
+ if (!c->isSet() || c->isTranslation())
changed = true;
- } else {
- changed = c->compare(s) != 0;
- *c = s;
- }
+ else
+ changed = c->asString().compare(s) != 0;
+ c->setString(s);
if (changed)
roleIndex = role.index;
}
@@ -916,7 +1071,11 @@ int ListElement::setVariantMapProperty(const ListLayout::Role &role, QVariantMap
char *mem = getPropertyMemory(role);
if (isMemoryUsed<QVariantMap>(mem)) {
QVariantMap *map = reinterpret_cast<QVariantMap *>(mem);
+ if (m && map->isSharedWith(*m))
+ return roleIndex;
map->~QMap();
+ } else if (!m) {
+ return roleIndex;
}
if (m)
new (mem) QVariantMap(*m);
@@ -945,10 +1104,42 @@ int ListElement::setDateTimeProperty(const ListLayout::Role &role, const QDateTi
return roleIndex;
}
+int ListElement::setFunctionProperty(const ListLayout::Role &role, const QJSValue &f)
+{
+ int roleIndex = -1;
+
+ if (role.type == ListLayout::Role::Function) {
+ char *mem = getPropertyMemory(role);
+ if (isMemoryUsed<QJSValue>(mem)) {
+ QJSValue *f = reinterpret_cast<QJSValue *>(mem);
+ f->~QJSValue();
+ }
+ new (mem) QJSValue(f);
+ roleIndex = role.index;
+ }
+
+ return roleIndex;
+}
+
+int ListElement::setTranslationProperty(const ListLayout::Role &role, const QV4::CompiledData::Binding *b)
+{
+ int roleIndex = -1;
+
+ if (role.type == ListLayout::Role::String) {
+ char *mem = getPropertyMemory(role);
+ StringOrTranslation *s = reinterpret_cast<StringOrTranslation *>(mem);
+ s->setTranslation(b);
+ roleIndex = role.index;
+ }
+
+ return roleIndex;
+}
+
+
void ListElement::setStringPropertyFast(const ListLayout::Role &role, const QString &s)
{
char *mem = getPropertyMemory(role);
- new (mem) QString(s);
+ new (mem) StringOrTranslation(s);
}
void ListElement::setDoublePropertyFast(const ListLayout::Role &role, double d)
@@ -991,6 +1182,12 @@ void ListElement::setDateTimePropertyFast(const ListLayout::Role &role, const QD
new (mem) QDateTime(dt);
}
+void ListElement::setFunctionPropertyFast(const ListLayout::Role &role, const QJSValue &f)
+{
+ char *mem = getPropertyMemory(role);
+ new (mem) QJSValue(f);
+}
+
void ListElement::clearProperty(const ListLayout::Role &role)
{
switch (role.type) {
@@ -1004,16 +1201,19 @@ void ListElement::clearProperty(const ListLayout::Role &role)
setBoolProperty(role, false);
break;
case ListLayout::Role::List:
- setListProperty(role, 0);
+ setListProperty(role, nullptr);
break;
case ListLayout::Role::QObject:
- setQObjectProperty(role, 0);
+ setQObjectProperty(role, nullptr);
break;
case ListLayout::Role::DateTime:
setDateTimeProperty(role, QDateTime());
break;
case ListLayout::Role::VariantMap:
- setVariantMapProperty(role, (QVariantMap *)0);
+ setVariantMapProperty(role, (QVariantMap *)nullptr);
+ break;
+ case ListLayout::Role::Function:
+ setFunctionProperty(role, QJSValue());
break;
default:
break;
@@ -1022,17 +1222,17 @@ void ListElement::clearProperty(const ListLayout::Role &role)
ListElement::ListElement()
{
- m_objectCache = 0;
+ m_objectCache = nullptr;
uid = uidCounter.fetchAndAddOrdered(1);
- next = 0;
+ next = nullptr;
memset(data, 0, sizeof(data));
}
ListElement::ListElement(int existingUid)
{
- m_objectCache = 0;
+ m_objectCache = nullptr;
uid = existingUid;
- next = 0;
+ next = nullptr;
memset(data, 0, sizeof(data));
}
@@ -1041,12 +1241,14 @@ ListElement::~ListElement()
delete next;
}
-void ListElement::sync(ListElement *src, ListLayout *srcLayout, ListElement *target, ListLayout *targetLayout, QHash<int, ListModel *> *targetModelHash)
+QVector<int> ListElement::sync(ListElement *src, ListLayout *srcLayout, ListElement *target, ListLayout *targetLayout)
{
+ QVector<int> changedRoles;
for (int i=0 ; i < srcLayout->roleCount() ; ++i) {
const ListLayout::Role &srcRole = srcLayout->getExistingRole(i);
const ListLayout::Role &targetRole = targetLayout->getExistingRole(i);
+ int roleIndex = -1;
switch (srcRole.type) {
case ListLayout::Role::List:
{
@@ -1054,40 +1256,45 @@ void ListElement::sync(ListElement *src, ListLayout *srcLayout, ListElement *tar
ListModel *targetSubModel = target->getListProperty(targetRole);
if (srcSubModel) {
- if (targetSubModel == 0) {
- targetSubModel = new ListModel(targetRole.subLayout, 0, srcSubModel->getUid());
+ if (targetSubModel == nullptr) {
+ targetSubModel = new ListModel(targetRole.subLayout, nullptr);
target->setListPropertyFast(targetRole, targetSubModel);
}
- ListModel::sync(srcSubModel, targetSubModel, targetModelHash);
+ if (ListModel::sync(srcSubModel, targetSubModel))
+ roleIndex = targetRole.index;
}
}
break;
case ListLayout::Role::QObject:
{
QObject *object = src->getQObjectProperty(srcRole);
- target->setQObjectProperty(targetRole, object);
+ roleIndex = target->setQObjectProperty(targetRole, object);
}
break;
case ListLayout::Role::String:
case ListLayout::Role::Number:
case ListLayout::Role::Bool:
case ListLayout::Role::DateTime:
+ case ListLayout::Role::Function:
{
- QVariant v = src->getProperty(srcRole, 0, 0);
- target->setVariantProperty(targetRole, v);
+ QVariant v = src->getProperty(srcRole, nullptr, nullptr);
+ roleIndex = target->setVariantProperty(targetRole, v);
}
break;
case ListLayout::Role::VariantMap:
{
QVariantMap *map = src->getVariantMapProperty(srcRole);
- target->setVariantMapProperty(targetRole, map);
+ roleIndex = target->setVariantMapProperty(targetRole, map);
}
break;
default:
break;
}
+ if (roleIndex >= 0)
+ changedRoles << roleIndex;
}
+ return changedRoles;
}
void ListElement::destroy(ListLayout *layout)
@@ -1099,9 +1306,9 @@ void ListElement::destroy(ListLayout *layout)
switch (r.type) {
case ListLayout::Role::String:
{
- QString *string = getStringProperty(r);
+ StringOrTranslation *string = getStringProperty(r);
if (string)
- string->~QString();
+ string->~StringOrTranslation();
}
break;
case ListLayout::Role::List:
@@ -1134,17 +1341,27 @@ void ListElement::destroy(ListLayout *layout)
dt->~QDateTime();
}
break;
+ case ListLayout::Role::Function:
+ {
+ QJSValue *f = getFunctionProperty(r);
+ if (f)
+ f->~QJSValue();
+ }
+ break;
default:
// other types don't need explicit cleanup.
break;
}
}
- delete m_objectCache;
+ if (m_objectCache) {
+ m_objectCache->~QObject();
+ operator delete(m_objectCache);
+ }
}
if (next)
- next->destroy(0);
+ next->destroy(nullptr);
uid = -1;
}
@@ -1157,7 +1374,10 @@ int ListElement::setVariantProperty(const ListLayout::Role &role, const QVariant
roleIndex = setDoubleProperty(role, d.toDouble());
break;
case ListLayout::Role::String:
- roleIndex = setStringProperty(role, d.toString());
+ if (d.userType() == qMetaTypeId<const QV4::CompiledData::Binding *>())
+ roleIndex = setTranslationProperty(role, d.value<const QV4::CompiledData::Binding*>());
+ else
+ roleIndex = setStringProperty(role, d.toString());
break;
case ListLayout::Role::Bool:
roleIndex = setBoolProperty(role, d.toBool());
@@ -1173,6 +1393,9 @@ int ListElement::setVariantProperty(const ListLayout::Role &role, const QVariant
case ListLayout::Role::DateTime:
roleIndex = setDateTimeProperty(role, d.toDateTime());
break;
+ case ListLayout::Role::Function:
+ roleIndex = setFunctionProperty(role, d.value<QJSValue>());
+ break;
default:
break;
}
@@ -1199,15 +1422,15 @@ int ListElement::setJsProperty(const ListLayout::Role &role, const QV4::Value &d
QV4::Scope scope(a->engine());
QV4::ScopedObject o(scope);
- ListModel *subModel = new ListModel(role.subLayout, 0, -1);
+ ListModel *subModel = new ListModel(role.subLayout, nullptr);
int arrayLength = a->getLength();
for (int j=0 ; j < arrayLength ; ++j) {
- o = a->getIndexed(j);
+ o = a->get(j);
subModel->append(o);
}
roleIndex = setListProperty(role, subModel);
} else {
- qmlWarning(0) << QStringLiteral("Can't assign to existing role '%1' of different type [%2 -> %3]").arg(role.name).arg(roleTypeName(role.type)).arg(roleTypeName(ListLayout::Role::List));
+ qmlWarning(nullptr) << QStringLiteral("Can't assign to existing role '%1' of different type [%2 -> %3]").arg(role.name).arg(roleTypeName(role.type)).arg(roleTypeName(ListLayout::Role::List));
}
} else if (d.isBoolean()) {
roleIndex = setBoolProperty(role, d.booleanValue());
@@ -1215,6 +1438,11 @@ int ListElement::setJsProperty(const ListLayout::Role &role, const QV4::Value &d
QV4::Scoped<QV4::DateObject> dd(scope, d);
QDateTime dt = dd->toQDateTime();
roleIndex = setDateTimeProperty(role, dt);
+ } else if (d.as<QV4::FunctionObject>()) {
+ QV4::ScopedFunctionObject f(scope, d);
+ QJSValue jsv;
+ QJSValuePrivate::setValue(&jsv, eng, f);
+ roleIndex = setFunctionProperty(role, jsv);
} else if (d.isObject()) {
QV4::ScopedObject o(scope, d);
QV4::QObjectWrapper *wrapper = o->as<QV4::QObjectWrapper>();
@@ -1311,7 +1539,7 @@ void ModelNodeMetaObject::propertyWritten(int index)
return;
QString propName = QString::fromUtf8(name(index));
- QVariant value = operator[](index);
+ const QVariant value = this->value(index);
QV4::Scope scope(m_model->engine());
QV4::ScopedValue v(scope, scope.engine->fromVariant(value));
@@ -1328,8 +1556,8 @@ void ModelNodeMetaObject::emitDirectNotifies(const int *changedRoles, int roleCo
QQmlData *ddata = QQmlData::get(object(), /*create*/false);
if (!ddata)
return;
- QQmlEnginePrivate *ep = QQmlEnginePrivate::get(qmlEngine(m_model));
- if (!ep)
+ // There's nothing to emit if we're a list model in a worker thread.
+ if (!qmlEngine(m_model))
return;
for (int i = 0; i < roleCount; ++i) {
const int changedRole = changedRoles[i];
@@ -1339,66 +1567,89 @@ void ModelNodeMetaObject::emitDirectNotifies(const int *changedRoles, int roleCo
namespace QV4 {
-bool ModelObject::put(Managed *m, String *name, const Value &value)
+bool ModelObject::virtualPut(Managed *m, PropertyKey id, const Value &value, Value *receiver)
{
+ if (!id.isString())
+ return Object::virtualPut(m, id, value, receiver);
+ QString propName = id.toQString();
+
ModelObject *that = static_cast<ModelObject*>(m);
ExecutionEngine *eng = that->engine();
- const int elementIndex = that->d()->m_elementIndex;
- const QString propName = name->toQString();
+ const int elementIndex = that->d()->elementIndex();
int roleIndex = that->d()->m_model->m_listModel->setExistingProperty(elementIndex, propName, value, eng);
if (roleIndex != -1)
that->d()->m_model->emitItemsChanged(elementIndex, 1, QVector<int>(1, roleIndex));
ModelNodeMetaObject *mo = ModelNodeMetaObject::get(that->object());
if (mo->initialized())
- mo->emitPropertyNotification(name->toQString().toUtf8());
+ mo->emitPropertyNotification(propName.toUtf8());
return true;
}
-ReturnedValue ModelObject::get(const Managed *m, String *name, bool *hasProperty)
+ReturnedValue ModelObject::virtualGet(const Managed *m, PropertyKey id, const Value *receiver, bool *hasProperty)
{
+ if (!id.isString())
+ return QObjectWrapper::virtualGet(m, id, receiver, hasProperty);
+
const ModelObject *that = static_cast<const ModelObject*>(m);
+ Scope scope(that);
+ ScopedString name(scope, id.asStringOrSymbol());
const ListLayout::Role *role = that->d()->m_model->m_listModel->getExistingRole(name);
if (!role)
- return QObjectWrapper::get(m, name, hasProperty);
+ return QObjectWrapper::virtualGet(m, id, receiver, hasProperty);
if (hasProperty)
*hasProperty = true;
if (QQmlEngine *qmlEngine = that->engine()->qmlEngine()) {
QQmlEnginePrivate *ep = QQmlEnginePrivate::get(qmlEngine);
- if (ep && ep->propertyCapture) {
- QObjectPrivate *op = QObjectPrivate::get(that->object());
- // Temporarily hide the dynamic meta-object, to prevent it from being created when the capture
- // triggers a QObject::connectNotify() by calling obj->metaObject().
- QScopedValueRollback<QDynamicMetaObjectData*> metaObjectBlocker(op->metaObject, 0);
- ep->propertyCapture->captureProperty(that->object(), -1, role->index);
- }
+ if (ep && ep->propertyCapture)
+ ep->propertyCapture->captureProperty(that->object(), -1, role->index,
+ QQmlPropertyCapture::OnlyOnce, false);
}
- const int elementIndex = that->d()->m_elementIndex;
+ const int elementIndex = that->d()->elementIndex();
QVariant value = that->d()->m_model->data(elementIndex, role->index);
return that->engine()->fromVariant(value);
}
-void ModelObject::advanceIterator(Managed *m, ObjectIterator *it, Value *name, uint *index, Property *p, PropertyAttributes *attributes)
+struct ModelObjectOwnPropertyKeyIterator : ObjectOwnPropertyKeyIterator
{
- ModelObject *that = static_cast<ModelObject*>(m);
+ int roleNameIndex = 0;
+ ~ModelObjectOwnPropertyKeyIterator() override = default;
+ PropertyKey next(const Object *o, Property *pd = nullptr, PropertyAttributes *attrs = nullptr) override;
+
+};
+
+PropertyKey ModelObjectOwnPropertyKeyIterator::next(const Object *o, Property *pd, PropertyAttributes *attrs)
+{
+ const ModelObject *that = static_cast<const ModelObject *>(o);
+
ExecutionEngine *v4 = that->engine();
- name->setM(0);
- *index = UINT_MAX;
- if (it->arrayIndex < uint(that->d()->m_model->m_listModel->roleCount())) {
+ if (roleNameIndex < that->listModel()->roleCount()) {
Scope scope(that->engine());
- const ListLayout::Role &role = that->d()->m_model->m_listModel->getExistingRole(it->arrayIndex);
- ++it->arrayIndex;
+ const ListLayout::Role &role = that->listModel()->getExistingRole(roleNameIndex);
+ ++roleNameIndex;
ScopedString roleName(scope, v4->newString(role.name));
- name->setM(roleName->d());
- *attributes = QV4::Attr_Data;
- QVariant value = that->d()->m_model->data(that->d()->m_elementIndex, role.index);
- p->value = v4->fromVariant(value);
- return;
+ if (attrs)
+ *attrs = QV4::Attr_Data;
+ if (pd) {
+ QVariant value = that->d()->m_model->data(that->d()->elementIndex(), role.index);
+ pd->value = v4->fromVariant(value);
+ }
+ return roleName->toPropertyKey();
}
- QV4::QObjectWrapper::advanceIterator(m, it, name, index, p, attributes);
+
+ // Fall back to QV4::Object as opposed to QV4::QObjectWrapper otherwise it will add
+ // unnecessary entries that relate to the roles used. These just create extra work
+ // later on as they will just be ignored.
+ return ObjectOwnPropertyKeyIterator::next(o, pd, attrs);
+}
+
+OwnPropertyKeyIterator *ModelObject::virtualOwnPropertyKeys(const Object *m, Value *target)
+{
+ *target = *m;
+ return new ModelObjectOwnPropertyKeyIterator;
}
DEFINE_OBJECT_VTABLE(ModelObject);
@@ -1418,20 +1669,22 @@ DynamicRoleModelNode *DynamicRoleModelNode::create(const QVariantMap &obj, QQmlL
return object;
}
-void DynamicRoleModelNode::sync(DynamicRoleModelNode *src, DynamicRoleModelNode *target, QHash<int, QQmlListModel *> *targetModelHash)
+QVector<int> DynamicRoleModelNode::sync(DynamicRoleModelNode *src, DynamicRoleModelNode *target)
{
- for (int i=0 ; i < src->m_meta->count() ; ++i) {
+ QVector<int> changedRoles;
+ for (int i = 0; i < src->m_meta->count(); ++i) {
const QByteArray &name = src->m_meta->name(i);
QVariant value = src->m_meta->value(i);
QQmlListModel *srcModel = qobject_cast<QQmlListModel *>(value.value<QObject *>());
QQmlListModel *targetModel = qobject_cast<QQmlListModel *>(target->m_meta->value(i).value<QObject *>());
+ bool modelHasChanges = false;
if (srcModel) {
- if (targetModel == 0)
+ if (targetModel == nullptr)
targetModel = QQmlListModel::createWithOwner(target->m_owner);
- QQmlListModel::sync(srcModel, targetModel, targetModelHash);
+ modelHasChanges = QQmlListModel::sync(srcModel, targetModel);
QObject *targetModelObject = targetModel;
value = QVariant::fromValue(targetModelObject);
@@ -1439,8 +1692,10 @@ void DynamicRoleModelNode::sync(DynamicRoleModelNode *src, DynamicRoleModelNode
delete targetModel;
}
- target->setValue(name, value);
+ if (target->setValue(name, value) || modelHasChanges)
+ changedRoles << target->m_owner->m_roles.indexOf(QString::fromUtf8(name));
}
+ return changedRoles;
}
void DynamicRoleModelNode::updateValues(const QVariantMap &object, QVector<int> &roles)
@@ -1555,7 +1810,7 @@ void DynamicRoleModelNodeMetaObject::propertyWritten(int index)
\instantiates QQmlListModel
\inqmlmodule QtQml.Models
\ingroup qtquick-models
- \brief Defines a free-form list data source
+ \brief Defines a free-form list data source.
The ListModel is a simple container of ListElement definitions, each
containing data roles. The contents can be defined dynamically, or
@@ -1628,13 +1883,13 @@ void DynamicRoleModelNodeMetaObject::propertyWritten(int index)
\snippet ../quick/threading/threadedlistmodel/timedisplay.qml 0
- The included file, \tt dataloader.js, looks like this:
+ The included file, \tt dataloader.mjs, looks like this:
- \snippet ../quick/threading/threadedlistmodel/dataloader.js 0
+ \snippet ../quick/threading/threadedlistmodel/dataloader.mjs 0
The timer in the main example sends messages to the worker script by calling
\l WorkerScript::sendMessage(). When this message is received,
- \c WorkerScript.onMessage() is invoked in \c dataloader.js,
+ \c WorkerScript.onMessage() is invoked in \c dataloader.mjs,
which appends the current time to the list model.
Note the call to sync() from the external thread.
@@ -1649,14 +1904,13 @@ QQmlListModel::QQmlListModel(QObject *parent)
{
m_mainThread = true;
m_primary = true;
- m_agent = 0;
- m_uid = uidCounter.fetchAndAddOrdered(1);
+ m_agent = nullptr;
m_dynamicRoles = false;
m_layout = new ListLayout;
- m_listModel = new ListModel(m_layout, this, -1);
+ m_listModel = new ListModel(m_layout, this);
- m_engine = 0;
+ m_engine = nullptr;
}
QQmlListModel::QQmlListModel(const QQmlListModel *owner, ListModel *data, QV4::ExecutionEngine *engine, QObject *parent)
@@ -1668,10 +1922,11 @@ QQmlListModel::QQmlListModel(const QQmlListModel *owner, ListModel *data, QV4::E
Q_ASSERT(owner->m_dynamicRoles == false);
m_dynamicRoles = false;
- m_layout = 0;
+ m_layout = nullptr;
m_listModel = data;
m_engine = engine;
+ m_compilationUnit = owner->m_compilationUnit;
}
QQmlListModel::QQmlListModel(QQmlListModel *orig, QQmlListModelWorkerAgent *agent)
@@ -1683,14 +1938,15 @@ QQmlListModel::QQmlListModel(QQmlListModel *orig, QQmlListModelWorkerAgent *agen
m_dynamicRoles = orig->m_dynamicRoles;
m_layout = new ListLayout(orig->m_layout);
- m_listModel = new ListModel(m_layout, this, orig->m_listModel->getUid());
+ m_listModel = new ListModel(m_layout, this);
if (m_dynamicRoles)
- sync(orig, this, 0);
+ sync(orig, this);
else
- ListModel::sync(orig->m_listModel, m_listModel, 0);
+ ListModel::sync(orig->m_listModel, m_listModel);
- m_engine = 0;
+ m_engine = nullptr;
+ m_compilationUnit = orig->m_compilationUnit;
}
QQmlListModel::~QQmlListModel()
@@ -1707,10 +1963,10 @@ QQmlListModel::~QQmlListModel()
}
}
- m_listModel = 0;
+ m_listModel = nullptr;
delete m_layout;
- m_layout = 0;
+ m_layout = nullptr;
}
QQmlListModel *QQmlListModel::createWithOwner(QQmlListModel *newOwner)
@@ -1732,32 +1988,32 @@ QQmlListModel *QQmlListModel::createWithOwner(QQmlListModel *newOwner)
QV4::ExecutionEngine *QQmlListModel::engine() const
{
- if (m_engine == 0) {
- m_engine = QQmlEnginePrivate::get(qmlEngine(this))->v4engine();
+ if (m_engine == nullptr) {
+ m_engine = qmlEngine(this)->handle();
}
return m_engine;
}
-void QQmlListModel::sync(QQmlListModel *src, QQmlListModel *target, QHash<int, QQmlListModel *> *targetModelHash)
+bool QQmlListModel::sync(QQmlListModel *src, QQmlListModel *target)
{
Q_ASSERT(src->m_dynamicRoles && target->m_dynamicRoles);
- target->m_uid = src->m_uid;
- if (targetModelHash)
- targetModelHash->insert(target->m_uid, target);
+ bool hasChanges = false;
+
target->m_roles = src->m_roles;
// Build hash of elements <-> uid for each of the lists
QHash<int, ElementSync> elementHash;
- for (int i=0 ; i < target->m_modelObjects.count() ; ++i) {
+ for (int i = 0 ; i < target->m_modelObjects.count(); ++i) {
DynamicRoleModelNode *e = target->m_modelObjects.at(i);
int uid = e->getUid();
ElementSync sync;
sync.target = e;
+ sync.targetIndex = i;
elementHash.insert(uid, sync);
}
- for (int i=0 ; i < src->m_modelObjects.count() ; ++i) {
+ for (int i = 0 ; i < src->m_modelObjects.count(); ++i) {
DynamicRoleModelNode *e = src->m_modelObjects.at(i);
int uid = e->getUid();
@@ -1765,118 +2021,102 @@ void QQmlListModel::sync(QQmlListModel *src, QQmlListModel *target, QHash<int, Q
if (it == elementHash.end()) {
ElementSync sync;
sync.src = e;
+ sync.srcIndex = i;
elementHash.insert(uid, sync);
} else {
ElementSync &sync = it.value();
sync.src = e;
+ sync.srcIndex = i;
}
}
// Get list of elements that are in the target but no longer in the source. These get deleted first.
- QHash<int, ElementSync>::iterator it = elementHash.begin();
- QHash<int, ElementSync>::iterator end = elementHash.end();
- while (it != end) {
- const ElementSync &s = it.value();
- if (s.src == 0) {
- int targetIndex = target->m_modelObjects.indexOf(s.target);
- target->m_modelObjects.remove(targetIndex, 1);
+ int rowsRemoved = 0;
+ for (int i = 0 ; i < target->m_modelObjects.count() ; ++i) {
+ DynamicRoleModelNode *element = target->m_modelObjects.at(i);
+ ElementSync &s = elementHash.find(element->getUid()).value();
+ Q_ASSERT(s.targetIndex >= 0);
+ // need to update the targetIndex, to keep it correct after removals
+ s.targetIndex -= rowsRemoved;
+ if (s.src == nullptr) {
+ Q_ASSERT(s.targetIndex == i);
+ hasChanges = true;
+ target->beginRemoveRows(QModelIndex(), i, i);
+ target->m_modelObjects.remove(i, 1);
+ target->endRemoveRows();
delete s.target;
+ ++rowsRemoved;
+ --i;
+ continue;
}
- ++it;
}
// Clear the target list, and append in correct order from the source
target->m_modelObjects.clear();
- for (int i=0 ; i < src->m_modelObjects.count() ; ++i) {
- DynamicRoleModelNode *srcElement = src->m_modelObjects.at(i);
- it = elementHash.find(srcElement->getUid());
- const ElementSync &s = it.value();
+ for (int i = 0 ; i < src->m_modelObjects.count() ; ++i) {
+ DynamicRoleModelNode *element = src->m_modelObjects.at(i);
+ ElementSync &s = elementHash.find(element->getUid()).value();
+ Q_ASSERT(s.srcIndex >= 0);
DynamicRoleModelNode *targetElement = s.target;
- if (targetElement == 0) {
- targetElement = new DynamicRoleModelNode(target, srcElement->getUid());
+ if (targetElement == nullptr) {
+ targetElement = new DynamicRoleModelNode(target, element->getUid());
}
- DynamicRoleModelNode::sync(srcElement, targetElement, targetModelHash);
+ s.changedRoles = DynamicRoleModelNode::sync(element, targetElement);
target->m_modelObjects.append(targetElement);
}
-}
-
-void QQmlListModel::emitItemsChanged(int index, int count, const QVector<int> &roles)
-{
- if (count <= 0)
- return;
- if (m_mainThread) {
- emit dataChanged(createIndex(index, 0), createIndex(index + count - 1, 0), roles);;
- } else {
- int uid = m_dynamicRoles ? getUid() : m_listModel->getUid();
- m_agent->data.changedChange(uid, index, count, roles);
+ // now emit the change notifications required. This can be safely done, as we're only emitting changes, moves and inserts,
+ // so the model indices can't be out of bounds
+ //
+ // to ensure things are kept in the correct order, emit inserts and moves first. This shouls ensure all persistent
+ // model indices are updated correctly
+ int rowsInserted = 0;
+ for (int i = 0 ; i < target->m_modelObjects.count() ; ++i) {
+ DynamicRoleModelNode *element = target->m_modelObjects.at(i);
+ ElementSync &s = elementHash.find(element->getUid()).value();
+ Q_ASSERT(s.srcIndex >= 0);
+ s.srcIndex += rowsInserted;
+ if (s.srcIndex != s.targetIndex) {
+ if (s.targetIndex == -1) {
+ target->beginInsertRows(QModelIndex(), i, i);
+ target->endInsertRows();
+ } else {
+ target->beginMoveRows(QModelIndex(), i, i, QModelIndex(), s.srcIndex);
+ target->endMoveRows();
+ }
+ hasChanges = true;
+ ++rowsInserted;
+ }
+ if (s.targetIndex != -1 && !s.changedRoles.isEmpty()) {
+ QModelIndex idx = target->createIndex(i, 0);
+ emit target->dataChanged(idx, idx, s.changedRoles);
+ hasChanges = true;
+ }
}
+ return hasChanges;
}
-void QQmlListModel::emitItemsAboutToBeRemoved(int index, int count)
-{
- if (count <= 0 || !m_mainThread)
- return;
-
- beginRemoveRows(QModelIndex(), index, index + count - 1);
-}
-
-void QQmlListModel::emitItemsRemoved(int index, int count)
+void QQmlListModel::emitItemsChanged(int index, int count, const QVector<int> &roles)
{
if (count <= 0)
return;
- if (m_mainThread) {
- endRemoveRows();
- emit countChanged();
- } else {
- int uid = m_dynamicRoles ? getUid() : m_listModel->getUid();
- if (index == 0 && count == this->count())
- m_agent->data.clearChange(uid);
- m_agent->data.removeChange(uid, index, count);
- }
+ if (m_mainThread)
+ emit dataChanged(createIndex(index, 0), createIndex(index + count - 1, 0), roles);;
}
void QQmlListModel::emitItemsAboutToBeInserted(int index, int count)
{
- if (count <= 0 || !m_mainThread)
- return;
-
- beginInsertRows(QModelIndex(), index, index + count - 1);
+ Q_ASSERT(index >= 0 && count >= 0);
+ if (m_mainThread)
+ beginInsertRows(QModelIndex(), index, index + count - 1);
}
-void QQmlListModel::emitItemsInserted(int index, int count)
+void QQmlListModel::emitItemsInserted()
{
- if (count <= 0)
- return;
-
if (m_mainThread) {
endInsertRows();
emit countChanged();
- } else {
- int uid = m_dynamicRoles ? getUid() : m_listModel->getUid();
- m_agent->data.insertChange(uid, index, count);
- }
-}
-
-void QQmlListModel::emitItemsAboutToBeMoved(int from, int to, int n)
-{
- if (n <= 0 || !m_mainThread)
- return;
-
- beginMoveRows(QModelIndex(), from, from + n - 1, QModelIndex(), to > from ? to + n : to);
-}
-
-void QQmlListModel::emitItemsMoved(int from, int to, int n)
-{
- if (n <= 0)
- return;
-
- if (m_mainThread) {
- endMoveRows();
- } else {
- int uid = m_dynamicRoles ? getUid() : m_listModel->getUid();
- m_agent->data.moveChange(uid, from, n, to);
}
}
@@ -1990,7 +2230,7 @@ QHash<int, QByteArray> QQmlListModel::roleNames() const
*/
void QQmlListModel::setDynamicRoles(bool enableDynamicRoles)
{
- if (m_mainThread && m_agent == 0) {
+ if (m_mainThread && m_agent == nullptr) {
if (enableDynamicRoles) {
if (m_layout->roleCount())
qmlWarning(this) << tr("unable to enable dynamic roles as this model is not empty");
@@ -2026,18 +2266,7 @@ int QQmlListModel::count() const
*/
void QQmlListModel::clear()
{
- const int cleared = count();
-
- emitItemsAboutToBeRemoved(0, cleared);
-
- if (m_dynamicRoles) {
- qDeleteAll(m_modelObjects);
- m_modelObjects.clear();
- } else {
- m_listModel->clear();
- }
-
- emitItemsRemoved(0, cleared);
+ removeElements(0, count());
}
/*!
@@ -2061,20 +2290,41 @@ void QQmlListModel::remove(QQmlV4Function *args)
return;
}
- emitItemsAboutToBeRemoved(index, removeCount);
+ removeElements(index, removeCount);
+ } else {
+ qmlWarning(this) << tr("remove: incorrect number of arguments");
+ }
+}
+
+void QQmlListModel::removeElements(int index, int removeCount)
+{
+ Q_ASSERT(index >= 0 && removeCount >= 0);
- if (m_dynamicRoles) {
- for (int i=0 ; i < removeCount ; ++i)
- delete m_modelObjects[index+i];
- m_modelObjects.remove(index, removeCount);
- } else {
- m_listModel->remove(index, removeCount);
- }
+ if (!removeCount)
+ return;
+
+ if (m_mainThread)
+ beginRemoveRows(QModelIndex(), index, index + removeCount - 1);
- emitItemsRemoved(index, removeCount);
+ QVector<std::function<void()>> toDestroy;
+ if (m_dynamicRoles) {
+ for (int i=0 ; i < removeCount ; ++i) {
+ auto modelObject = m_modelObjects[index+i];
+ toDestroy.append([modelObject](){
+ delete modelObject;
+ });
+ }
+ m_modelObjects.remove(index, removeCount);
} else {
- qmlWarning(this) << tr("remove: incorrect number of arguments");
+ toDestroy = m_listModel->remove(index, removeCount);
}
+
+ if (m_mainThread) {
+ endRemoveRows();
+ emit countChanged();
+ }
+ for (const auto &destroyer : toDestroy)
+ destroyer();
}
/*!
@@ -2113,7 +2363,7 @@ void QQmlListModel::insert(QQmlV4Function *args)
int objectArrayLength = objectArray->getLength();
emitItemsAboutToBeInserted(index, objectArrayLength);
for (int i=0 ; i < objectArrayLength ; ++i) {
- argObject = objectArray->getIndexed(i);
+ argObject = objectArray->get(i);
if (m_dynamicRoles) {
m_modelObjects.insert(index+i, DynamicRoleModelNode::create(scope.engine->variantMapFromJS(argObject), this));
@@ -2121,7 +2371,7 @@ void QQmlListModel::insert(QQmlV4Function *args)
m_listModel->insert(index+i, argObject);
}
}
- emitItemsInserted(index, objectArrayLength);
+ emitItemsInserted();
} else if (argObject) {
emitItemsAboutToBeInserted(index, 1);
@@ -2131,7 +2381,7 @@ void QQmlListModel::insert(QQmlV4Function *args)
m_listModel->insert(index, argObject);
}
- emitItemsInserted(index, 1);
+ emitItemsInserted();
} else {
qmlWarning(this) << tr("insert: value is not an object");
}
@@ -2156,14 +2406,15 @@ void QQmlListModel::insert(QQmlV4Function *args)
*/
void QQmlListModel::move(int from, int to, int n)
{
- if (n==0 || from==to)
+ if (n == 0 || from == to)
return;
if (!canMove(from, to, n)) {
qmlWarning(this) << tr("move: out of range");
return;
}
- emitItemsAboutToBeMoved(from, to, n);
+ if (m_mainThread)
+ beginMoveRows(QModelIndex(), from, from + n - 1, QModelIndex(), to > from ? to + n : to);
if (m_dynamicRoles) {
@@ -2192,7 +2443,8 @@ void QQmlListModel::move(int from, int to, int n)
m_listModel->move(from, to, n);
}
- emitItemsMoved(from, to, n);
+ if (m_mainThread)
+ endMoveRows();
}
/*!
@@ -2218,21 +2470,22 @@ void QQmlListModel::append(QQmlV4Function *args)
QV4::ScopedObject argObject(scope);
int objectArrayLength = objectArray->getLength();
+ if (objectArrayLength > 0) {
+ int index = count();
+ emitItemsAboutToBeInserted(index, objectArrayLength);
- int index = count();
- emitItemsAboutToBeInserted(index, objectArrayLength);
-
- for (int i=0 ; i < objectArrayLength ; ++i) {
- argObject = objectArray->getIndexed(i);
+ for (int i=0 ; i < objectArrayLength ; ++i) {
+ argObject = objectArray->get(i);
- if (m_dynamicRoles) {
- m_modelObjects.append(DynamicRoleModelNode::create(scope.engine->variantMapFromJS(argObject), this));
- } else {
- m_listModel->append(argObject);
+ if (m_dynamicRoles) {
+ m_modelObjects.append(DynamicRoleModelNode::create(scope.engine->variantMapFromJS(argObject), this));
+ } else {
+ m_listModel->append(argObject);
+ }
}
- }
- emitItemsInserted(index, objectArrayLength);
+ emitItemsInserted();
+ }
} else if (argObject) {
int index;
@@ -2246,7 +2499,7 @@ void QQmlListModel::append(QQmlV4Function *args)
m_listModel->append(argObject);
}
- emitItemsInserted(index, 1);
+ emitItemsInserted();
} else {
qmlWarning(this) << tr("append: value is not an object");
}
@@ -2289,7 +2542,7 @@ void QQmlListModel::append(QQmlV4Function *args)
QQmlV4Handle QQmlListModel::get(int index) const
{
QV4::Scope scope(engine());
- QV4::ScopedValue result(scope, QV4::Primitive::undefinedValue());
+ QV4::ScopedValue result(scope, QV4::Value::undefinedValue());
if (index >= 0 && index < count()) {
@@ -2298,10 +2551,14 @@ QQmlV4Handle QQmlListModel::get(int index) const
result = QV4::QObjectWrapper::wrap(scope.engine, object);
} else {
QObject *object = m_listModel->getOrCreateModelObject(const_cast<QQmlListModel *>(this), index);
- result = scope.engine->memoryManager->allocObject<QV4::ModelObject>(object, const_cast<QQmlListModel *>(this), index);
- // Keep track of the QObjectWrapper in persistent value storage
- QV4::Value *val = scope.engine->memoryManager->m_weakValues->allocate();
- *val = result;
+ QQmlData *ddata = QQmlData::get(object);
+ if (ddata->jsWrapper.isNullOrUndefined()) {
+ result = scope.engine->memoryManager->allocate<QV4::ModelObject>(object, const_cast<QQmlListModel *>(this));
+ // Keep track of the QObjectWrapper in persistent value storage
+ ddata->jsWrapper.set(scope.engine, result);
+ } else {
+ result = ddata->jsWrapper.value();
+ }
}
}
@@ -2348,7 +2605,7 @@ void QQmlListModel::set(int index, const QQmlV4Handle &handle)
m_listModel->insert(index, object);
}
- emitItemsInserted(index, 1);
+ emitItemsInserted();
} else {
QVector<int> roles;
@@ -2413,12 +2670,12 @@ void QQmlListModel::sync()
qmlWarning(this) << "List sync() can only be called from a WorkerScript";
}
-bool QQmlListModelParser::verifyProperty(const QV4::CompiledData::Unit *qmlUnit, const QV4::CompiledData::Binding *binding)
+bool QQmlListModelParser::verifyProperty(const QQmlRefPointer<QV4::CompiledData::CompilationUnit> &compilationUnit, const QV4::CompiledData::Binding *binding)
{
if (binding->type >= QV4::CompiledData::Binding::Type_Object) {
const quint32 targetObjectIndex = binding->value.objectIndex;
- const QV4::CompiledData::Object *target = qmlUnit->objectAt(targetObjectIndex);
- QString objName = qmlUnit->stringAt(target->inheritedTypeNameIndex);
+ const QV4::CompiledData::Object *target = compilationUnit->objectAt(targetObjectIndex);
+ QString objName = compilationUnit->stringAt(target->inheritedTypeNameIndex);
if (objName != listElementTypeName) {
const QMetaObject *mo = resolveType(objName);
if (mo != &QQmlListElement::staticMetaObject) {
@@ -2428,24 +2685,24 @@ bool QQmlListModelParser::verifyProperty(const QV4::CompiledData::Unit *qmlUnit,
listElementTypeName = objName; // cache right name for next time
}
- if (!qmlUnit->stringAt(target->idNameIndex).isEmpty()) {
+ if (!compilationUnit->stringAt(target->idNameIndex).isEmpty()) {
error(target->locationOfIdProperty, QQmlListModel::tr("ListElement: cannot use reserved \"id\" property"));
return false;
}
const QV4::CompiledData::Binding *binding = target->bindingTable();
for (quint32 i = 0; i < target->nBindings; ++i, ++binding) {
- QString propName = qmlUnit->stringAt(binding->propertyNameIndex);
+ QString propName = compilationUnit->stringAt(binding->propertyNameIndex);
if (propName.isEmpty()) {
error(binding, QQmlListModel::tr("ListElement: cannot contain nested elements"));
return false;
}
- if (!verifyProperty(qmlUnit, binding))
+ if (!verifyProperty(compilationUnit, binding))
return false;
}
} else if (binding->type == QV4::CompiledData::Binding::Type_Script) {
- QString scriptStr = binding->valueAsScriptString(qmlUnit);
- if (!definesEmptyList(scriptStr)) {
+ QString scriptStr = binding->valueAsScriptString(compilationUnit.data());
+ if (!binding->isFunctionExpression() && !definesEmptyList(scriptStr)) {
QByteArray script = scriptStr.toUtf8();
bool ok;
evaluateEnum(script, &ok);
@@ -2459,24 +2716,24 @@ bool QQmlListModelParser::verifyProperty(const QV4::CompiledData::Unit *qmlUnit,
return true;
}
-bool QQmlListModelParser::applyProperty(const QV4::CompiledData::Unit *qmlUnit, const QV4::CompiledData::Binding *binding, ListModel *model, int outterElementIndex)
+bool QQmlListModelParser::applyProperty(const QQmlRefPointer<QV4::CompiledData::CompilationUnit> &compilationUnit, const QV4::CompiledData::Binding *binding, ListModel *model, int outterElementIndex)
{
- const QString elementName = qmlUnit->stringAt(binding->propertyNameIndex);
+ const QString elementName = compilationUnit->stringAt(binding->propertyNameIndex);
bool roleSet = false;
if (binding->type >= QV4::CompiledData::Binding::Type_Object) {
const quint32 targetObjectIndex = binding->value.objectIndex;
- const QV4::CompiledData::Object *target = qmlUnit->objectAt(targetObjectIndex);
+ const QV4::CompiledData::Object *target = compilationUnit->objectAt(targetObjectIndex);
- ListModel *subModel = 0;
+ ListModel *subModel = nullptr;
if (outterElementIndex == -1) {
subModel = model;
} else {
const ListLayout::Role &role = model->getOrCreateListRole(elementName);
if (role.type == ListLayout::Role::List) {
subModel = model->getListProperty(outterElementIndex, role);
- if (subModel == 0) {
- subModel = new ListModel(role.subLayout, 0, -1);
+ if (subModel == nullptr) {
+ subModel = new ListModel(role.subLayout, nullptr);
QVariant vModel = QVariant::fromValue(subModel);
model->setOrCreateProperty(outterElementIndex, elementName, vModel);
}
@@ -2487,24 +2744,43 @@ bool QQmlListModelParser::applyProperty(const QV4::CompiledData::Unit *qmlUnit,
const QV4::CompiledData::Binding *subBinding = target->bindingTable();
for (quint32 i = 0; i < target->nBindings; ++i, ++subBinding) {
- roleSet |= applyProperty(qmlUnit, subBinding, subModel, elementIndex);
+ roleSet |= applyProperty(compilationUnit, subBinding, subModel, elementIndex);
}
} else {
QVariant value;
- if (binding->evaluatesToString()) {
- value = binding->valueAsString(qmlUnit);
+ if (binding->isTranslationBinding()) {
+ value = QVariant::fromValue<const QV4::CompiledData::Binding*>(binding);
+ } else if (binding->evaluatesToString()) {
+ value = binding->valueAsString(compilationUnit.data());
} else if (binding->type == QV4::CompiledData::Binding::Type_Number) {
- value = binding->valueAsNumber();
+ value = binding->valueAsNumber(compilationUnit->constants);
} else if (binding->type == QV4::CompiledData::Binding::Type_Boolean) {
value = binding->valueAsBoolean();
+ } else if (binding->type == QV4::CompiledData::Binding::Type_Null) {
+ value = QVariant::fromValue(nullptr);
} else if (binding->type == QV4::CompiledData::Binding::Type_Script) {
- QString scriptStr = binding->valueAsScriptString(qmlUnit);
+ QString scriptStr = binding->valueAsScriptString(compilationUnit.data());
if (definesEmptyList(scriptStr)) {
const ListLayout::Role &role = model->getOrCreateListRole(elementName);
- ListModel *emptyModel = new ListModel(role.subLayout, 0, -1);
+ ListModel *emptyModel = new ListModel(role.subLayout, nullptr);
value = QVariant::fromValue(emptyModel);
+ } else if (binding->isFunctionExpression()) {
+ QQmlBinding::Identifier id = binding->value.compiledScriptIndex;
+ Q_ASSERT(id != QQmlBinding::Invalid);
+
+ auto v4 = compilationUnit->engine;
+ QV4::Scope scope(v4);
+ // for now we do not provide a context object; data from the ListElement must be passed to the function
+ QV4::ScopedContext context(scope, QV4::QmlContext::create(v4->rootContext(), QQmlContextData::get(qmlContext(model->m_modelCache)), nullptr));
+ QV4::ScopedFunctionObject function(scope, QV4::FunctionObject::createScriptFunction(context, compilationUnit->runtimeFunctions[id]));
+
+ QV4::ReturnedValue result = function->call(v4->globalObject, nullptr, 0);
+
+ QJSValue v;
+ QJSValuePrivate::setValue(&v, v4, result);
+ value.setValue<QJSValue>(v);
} else {
QByteArray script = scriptStr.toUtf8();
bool ok;
@@ -2520,35 +2796,34 @@ bool QQmlListModelParser::applyProperty(const QV4::CompiledData::Unit *qmlUnit,
return roleSet;
}
-void QQmlListModelParser::verifyBindings(const QV4::CompiledData::Unit *qmlUnit, const QList<const QV4::CompiledData::Binding *> &bindings)
+void QQmlListModelParser::verifyBindings(const QQmlRefPointer<QV4::CompiledData::CompilationUnit> &compilationUnit, const QList<const QV4::CompiledData::Binding *> &bindings)
{
listElementTypeName = QString(); // unknown
for (const QV4::CompiledData::Binding *binding : bindings) {
- QString propName = qmlUnit->stringAt(binding->propertyNameIndex);
+ QString propName = compilationUnit->stringAt(binding->propertyNameIndex);
if (!propName.isEmpty()) { // isn't default property
error(binding, QQmlListModel::tr("ListModel: undefined property '%1'").arg(propName));
return;
}
- if (!verifyProperty(qmlUnit, binding))
+ if (!verifyProperty(compilationUnit, binding))
return;
}
}
-void QQmlListModelParser::applyBindings(QObject *obj, QV4::CompiledData::CompilationUnit *compilationUnit, const QList<const QV4::CompiledData::Binding *> &bindings)
+void QQmlListModelParser::applyBindings(QObject *obj, const QQmlRefPointer<QV4::CompiledData::CompilationUnit> &compilationUnit, const QList<const QV4::CompiledData::Binding *> &bindings)
{
QQmlListModel *rv = static_cast<QQmlListModel *>(obj);
- rv->m_engine = QV8Engine::getV4(qmlEngine(rv));
-
- const QV4::CompiledData::Unit *qmlUnit = compilationUnit->data;
+ rv->m_engine = qmlEngine(rv)->handle();
+ rv->m_compilationUnit = compilationUnit;
bool setRoles = false;
for (const QV4::CompiledData::Binding *binding : bindings) {
if (binding->type != QV4::CompiledData::Binding::Type_Object)
continue;
- setRoles |= applyProperty(qmlUnit, binding, rv->m_listModel, /*outter element index*/-1);
+ setRoles |= applyProperty(compilationUnit, binding, rv->m_listModel, /*outter element index*/-1);
}
if (setRoles == false)
@@ -2572,7 +2847,7 @@ bool QQmlListModelParser::definesEmptyList(const QString &s)
\qmltype ListElement
\instantiates QQmlListElement
\inqmlmodule QtQml.Models
- \brief Defines a data item in a ListModel
+ \brief Defines a data item in a ListModel.
\ingroup qtquick-models
List elements are defined inside ListModel definitions, and represent items in a
@@ -2588,6 +2863,9 @@ bool QQmlListModelParser::definesEmptyList(const QString &s)
strings (quoted and optionally within a call to QT_TR_NOOP), boolean values
(true, false), numbers, or enumeration values (such as AlignText.AlignHCenter).
+ Beginning with Qt 5.11 ListElement also allows assigning a function declaration to
+ a role. This allows the definition of ListElements with callable actions.
+
\section1 Referencing Roles
The role names are used by delegates to obtain data from list elements.
diff --git a/src/qml/types/qqmllistmodel_p.h b/src/qml/types/qqmllistmodel_p.h
index b750d30676..95b797c898 100644
--- a/src/qml/types/qqmllistmodel_p.h
+++ b/src/qml/types/qqmllistmodel_p.h
@@ -64,6 +64,8 @@
#include <private/qv4engine_p.h>
#include <private/qpodvector_p.h>
+QT_REQUIRE_CONFIG(qml_list_model);
+
QT_BEGIN_NAMESPACE
@@ -82,7 +84,7 @@ class Q_QML_PRIVATE_EXPORT QQmlListModel : public QAbstractListModel
Q_PROPERTY(bool dynamicRoles READ dynamicRoles WRITE setDynamicRoles)
public:
- QQmlListModel(QObject *parent=0);
+ QQmlListModel(QObject *parent=nullptr);
~QQmlListModel();
QModelIndex index(int row, int column, const QModelIndex &parent) const override;
@@ -122,10 +124,11 @@ private:
friend class ListElement;
friend class DynamicRoleModelNode;
friend class DynamicRoleModelNodeMetaObject;
+ friend struct StringOrTranslation;
// Constructs a flat list model for a worker agent
QQmlListModel(QQmlListModel *orig, QQmlListModelWorkerAgent *agent);
- QQmlListModel(const QQmlListModel *owner, ListModel *data, QV4::ExecutionEngine *engine, QObject *parent=0);
+ QQmlListModel(const QQmlListModel *owner, ListModel *data, QV4::ExecutionEngine *engine, QObject *parent=nullptr);
QV4::ExecutionEngine *engine() const;
@@ -133,6 +136,7 @@ private:
QQmlListModelWorkerAgent *m_agent;
mutable QV4::ExecutionEngine *m_engine;
+ QQmlRefPointer<QV4::CompiledData::CompilationUnit> m_compilationUnit;
bool m_mainThread;
bool m_primary;
@@ -143,28 +147,24 @@ private:
QVector<class DynamicRoleModelNode *> m_modelObjects;
QVector<QString> m_roles;
- int m_uid;
struct ElementSync
{
- ElementSync() : src(0), target(0) {}
-
- DynamicRoleModelNode *src;
- DynamicRoleModelNode *target;
+ DynamicRoleModelNode *src = nullptr;
+ DynamicRoleModelNode *target = nullptr;
+ int srcIndex = -1;
+ int targetIndex = -1;
+ QVector<int> changedRoles;
};
- int getUid() const { return m_uid; }
-
- static void sync(QQmlListModel *src, QQmlListModel *target, QHash<int, QQmlListModel *> *targetModelHash);
+ static bool sync(QQmlListModel *src, QQmlListModel *target);
static QQmlListModel *createWithOwner(QQmlListModel *newOwner);
void emitItemsChanged(int index, int count, const QVector<int> &roles);
- void emitItemsAboutToBeRemoved(int index, int count);
- void emitItemsRemoved(int index, int count);
void emitItemsAboutToBeInserted(int index, int count);
- void emitItemsInserted(int index, int count);
- void emitItemsAboutToBeMoved(int from, int to, int n);
- void emitItemsMoved(int from, int to, int n);
+ void emitItemsInserted();
+
+ void removeElements(int index, int removeCount);
};
// ### FIXME
@@ -187,13 +187,13 @@ public:
QQmlListModelParser() : QQmlCustomParser(QQmlCustomParser::AcceptsSignalHandlers) {}
- void verifyBindings(const QV4::CompiledData::Unit *qmlUnit, const QList<const QV4::CompiledData::Binding *> &bindings) Q_DECL_OVERRIDE;
- void applyBindings(QObject *obj, QV4::CompiledData::CompilationUnit *compilationUnit, const QList<const QV4::CompiledData::Binding *> &bindings) Q_DECL_OVERRIDE;
+ void verifyBindings(const QQmlRefPointer<QV4::CompiledData::CompilationUnit> &compilationUnit, const QList<const QV4::CompiledData::Binding *> &bindings) override;
+ void applyBindings(QObject *obj, const QQmlRefPointer<QV4::CompiledData::CompilationUnit> &compilationUnit, const QList<const QV4::CompiledData::Binding *> &bindings) override;
private:
- bool verifyProperty(const QV4::CompiledData::Unit *qmlUnit, const QV4::CompiledData::Binding *binding);
+ bool verifyProperty(const QQmlRefPointer<QV4::CompiledData::CompilationUnit> &compilationUnit, const QV4::CompiledData::Binding *binding);
// returns true if a role was set
- bool applyProperty(const QV4::CompiledData::Unit *qmlUnit, const QV4::CompiledData::Binding *binding, ListModel *model, int outterElementIndex);
+ bool applyProperty(const QQmlRefPointer<QV4::CompiledData::CompilationUnit> &compilationUnit, const QV4::CompiledData::Binding *binding, ListModel *model, int outterElementIndex);
static bool definesEmptyList(const QString &);
diff --git a/src/qml/types/qqmllistmodel_p_p.h b/src/qml/types/qqmllistmodel_p_p.h
index e2653c220d..ff52ee049f 100644
--- a/src/qml/types/qqmllistmodel_p_p.h
+++ b/src/qml/types/qqmllistmodel_p_p.h
@@ -57,6 +57,8 @@
#include <private/qv4qobjectwrapper_p.h>
#include <qqml.h>
+QT_REQUIRE_CONFIG(qml_list_model);
+
QT_BEGIN_NAMESPACE
@@ -108,7 +110,7 @@ public:
return m_uid;
}
- static void sync(DynamicRoleModelNode *src, DynamicRoleModelNode *target, QHash<int, QQmlListModel *> *targetModelHash);
+ static QVector<int> sync(DynamicRoleModelNode *src, DynamicRoleModelNode *target);
private:
QQmlListModel *m_owner;
@@ -142,16 +144,6 @@ protected:
private:
using QQmlOpenMetaObject::setValue;
- void setValue(const QByteArray &name, const QVariant &val, bool force)
- {
- if (force) {
- QVariant existingValue = value(name);
- if (existingValue.isValid()) {
- (*this)[name] = QVariant();
- }
- }
- setValue(name, val);
- }
void emitDirectNotifies(const int *changedRoles, int roleCount);
@@ -164,27 +156,32 @@ namespace QV4 {
namespace Heap {
struct ModelObject : public QObjectWrapper {
- void init(QObject *object, QQmlListModel *model, int elementIndex)
+ void init(QObject *object, QQmlListModel *model)
{
QObjectWrapper::init(object);
m_model = model;
- m_elementIndex = elementIndex;
+ QObjectPrivate *op = QObjectPrivate::get(object);
+ m_nodeModelMetaObject = static_cast<ModelNodeMetaObject *>(op->metaObject);
}
void destroy() { QObjectWrapper::destroy(); }
+ int elementIndex() const { return m_nodeModelMetaObject->m_elementIndex; }
QQmlListModel *m_model;
- int m_elementIndex;
+ ModelNodeMetaObject *m_nodeModelMetaObject;
};
}
struct ModelObject : public QObjectWrapper
{
- static bool put(Managed *m, String *name, const Value& value);
- static ReturnedValue get(const Managed *m, String *name, bool *hasProperty);
- static void advanceIterator(Managed *m, ObjectIterator *it, Value *name, uint *index, Property *p, PropertyAttributes *attributes);
-
V4_OBJECT2(ModelObject, QObjectWrapper)
V4_NEEDS_DESTROY
+
+ ListModel *listModel() const { return d()->m_model->m_listModel; }
+
+protected:
+ static bool virtualPut(Managed *m, PropertyKey id, const Value& value, Value *receiver);
+ static ReturnedValue virtualGet(const Managed *m, PropertyKey id, const Value *receiver, bool *hasProperty);
+ static OwnPropertyKeyIterator *virtualOwnPropertyKeys(const Object *m, Value *target);
};
} // namespace QV4
@@ -216,6 +213,7 @@ public:
QObject,
VariantMap,
DateTime,
+ Function,
MaxDataType
};
@@ -249,6 +247,22 @@ private:
QStringHash<Role *> roleHash;
};
+struct StringOrTranslation
+{
+ explicit StringOrTranslation(const QString &s);
+ explicit StringOrTranslation(const QV4::CompiledData::Binding *binding);
+ ~StringOrTranslation();
+ bool isSet() const { return d.flag(); }
+ bool isTranslation() const { return d.isT2(); }
+ void setString(const QString &s);
+ void setTranslation(const QV4::CompiledData::Binding *binding);
+ QString toString(const QQmlListModel *owner) const;
+ QString asString() const;
+private:
+ void clear();
+ QBiPointer<QStringData, const QV4::CompiledData::Binding> d;
+};
+
/*!
\internal
*/
@@ -260,7 +274,7 @@ public:
ListElement(int existingUid);
~ListElement();
- static void sync(ListElement *src, ListLayout *srcLayout, ListElement *target, ListLayout *targetLayout, QHash<int, ListModel *> *targetModelHash);
+ static QVector<int> sync(ListElement *src, ListLayout *srcLayout, ListElement *target, ListLayout *targetLayout);
enum
{
@@ -283,6 +297,8 @@ private:
int setVariantMapProperty(const ListLayout::Role &role, QV4::Object *o);
int setVariantMapProperty(const ListLayout::Role &role, QVariantMap *m);
int setDateTimeProperty(const ListLayout::Role &role, const QDateTime &dt);
+ int setFunctionProperty(const ListLayout::Role &role, const QJSValue &f);
+ int setTranslationProperty(const ListLayout::Role &role, const QV4::CompiledData::Binding *b);
void setStringPropertyFast(const ListLayout::Role &role, const QString &s);
void setDoublePropertyFast(const ListLayout::Role &role, double n);
@@ -291,16 +307,18 @@ private:
void setListPropertyFast(const ListLayout::Role &role, ListModel *m);
void setVariantMapFast(const ListLayout::Role &role, QV4::Object *o);
void setDateTimePropertyFast(const ListLayout::Role &role, const QDateTime &dt);
+ void setFunctionPropertyFast(const ListLayout::Role &role, const QJSValue &f);
void clearProperty(const ListLayout::Role &role);
QVariant getProperty(const ListLayout::Role &role, const QQmlListModel *owner, QV4::ExecutionEngine *eng);
ListModel *getListProperty(const ListLayout::Role &role);
- QString *getStringProperty(const ListLayout::Role &role);
+ StringOrTranslation *getStringProperty(const ListLayout::Role &role);
QObject *getQObjectProperty(const ListLayout::Role &role);
QPointer<QObject> *getGuardProperty(const ListLayout::Role &role);
QVariantMap *getVariantMapProperty(const ListLayout::Role &role);
QDateTime *getDateTimeProperty(const ListLayout::Role &role);
+ QJSValue *getFunctionProperty(const ListLayout::Role &role);
inline char *getPropertyMemory(const ListLayout::Role &role);
@@ -324,7 +342,7 @@ class ListModel
{
public:
- ListModel(ListLayout *layout, QQmlListModel *modelCache, int uid);
+ ListModel(ListLayout *layout, QQmlListModel *modelCache);
~ListModel() {}
void destroy();
@@ -366,33 +384,30 @@ public:
int append(QV4::Object *object);
void insert(int elementIndex, QV4::Object *object);
- void clear();
- void remove(int index, int count);
+ Q_REQUIRED_RESULT QVector<std::function<void()>> remove(int index, int count);
int appendElement();
void insertElement(int index);
void move(int from, int to, int n);
- int getUid() const { return m_uid; }
-
- static void sync(ListModel *src, ListModel *target, QHash<int, ListModel *> *srcModelHash);
+ static bool sync(ListModel *src, ListModel *target);
QObject *getOrCreateModelObject(QQmlListModel *model, int elementIndex);
private:
QPODVector<ListElement *, 4> elements;
ListLayout *m_layout;
- int m_uid;
QQmlListModel *m_modelCache;
struct ElementSync
{
- ElementSync() : src(0), target(0) {}
-
- ListElement *src;
- ListElement *target;
+ ListElement *src = nullptr;
+ ListElement *target = nullptr;
+ int srcIndex = -1;
+ int targetIndex = -1;
+ QVector<int> changedRoles;
};
void newElement(int index);
@@ -401,6 +416,7 @@ private:
friend class ListElement;
friend class QQmlListModelWorkerAgent;
+ friend class QQmlListModelParser;
};
QT_END_NAMESPACE
diff --git a/src/qml/types/qqmllistmodelworkeragent.cpp b/src/qml/types/qqmllistmodelworkeragent.cpp
index 0a5adbf292..fe3eaa3198 100644
--- a/src/qml/types/qqmllistmodelworkeragent.cpp
+++ b/src/qml/types/qqmllistmodelworkeragent.cpp
@@ -50,39 +50,8 @@
QT_BEGIN_NAMESPACE
-
-void QQmlListModelWorkerAgent::Data::clearChange(int uid)
-{
- for (int i=0 ; i < changes.count() ; ++i) {
- if (changes[i].modelUid == uid) {
- changes.removeAt(i);
- --i;
- }
- }
-}
-
-void QQmlListModelWorkerAgent::Data::insertChange(int uid, int index, int count)
+QQmlListModelWorkerAgent::Sync::~Sync()
{
- Change c = { uid, Change::Inserted, index, count, 0, QVector<int>() };
- changes << c;
-}
-
-void QQmlListModelWorkerAgent::Data::removeChange(int uid, int index, int count)
-{
- Change c = { uid, Change::Removed, index, count, 0, QVector<int>() };
- changes << c;
-}
-
-void QQmlListModelWorkerAgent::Data::moveChange(int uid, int index, int count, int to)
-{
- Change c = { uid, Change::Moved, index, count, to, QVector<int>() };
- changes << c;
-}
-
-void QQmlListModelWorkerAgent::Data::changedChange(int uid, int index, int count, const QVector<int> &roles)
-{
- Change c = { uid, Change::Changed, index, count, 0, roles };
- changes << c;
}
QQmlListModelWorkerAgent::QQmlListModelWorkerAgent(QQmlListModel *model)
@@ -117,7 +86,7 @@ void QQmlListModelWorkerAgent::release()
void QQmlListModelWorkerAgent::modelDestroyed()
{
- m_orig = 0;
+ m_orig = nullptr;
}
int QQmlListModelWorkerAgent::count() const
@@ -167,8 +136,7 @@ void QQmlListModelWorkerAgent::move(int from, int to, int count)
void QQmlListModelWorkerAgent::sync()
{
- Sync *s = new Sync(data, m_copy);
- data.changes.clear();
+ Sync *s = new Sync(m_copy);
mutex.lock();
QCoreApplication::postEvent(this, s);
@@ -183,61 +151,14 @@ bool QQmlListModelWorkerAgent::event(QEvent *e)
QMutexLocker locker(&mutex);
if (m_orig) {
Sync *s = static_cast<Sync *>(e);
- const QList<Change> &changes = s->data.changes;
- cc = m_orig->count() != s->list->count();
-
- QHash<int, QQmlListModel *> targetModelDynamicHash;
- QHash<int, ListModel *> targetModelStaticHash;
+ cc = (m_orig->count() != s->list->count());
Q_ASSERT(m_orig->m_dynamicRoles == s->list->m_dynamicRoles);
if (m_orig->m_dynamicRoles)
- QQmlListModel::sync(s->list, m_orig, &targetModelDynamicHash);
+ QQmlListModel::sync(s->list, m_orig);
else
- ListModel::sync(s->list->m_listModel, m_orig->m_listModel, &targetModelStaticHash);
-
- for (int ii = 0; ii < changes.count(); ++ii) {
- const Change &change = changes.at(ii);
-
- QQmlListModel *model = 0;
- if (m_orig->m_dynamicRoles) {
- model = targetModelDynamicHash.value(change.modelUid);
- } else {
- ListModel *lm = targetModelStaticHash.value(change.modelUid);
- if (lm)
- model = lm->m_modelCache;
- }
-
- if (model) {
- switch (change.type) {
- case Change::Inserted:
- model->beginInsertRows(
- QModelIndex(), change.index, change.index + change.count - 1);
- model->endInsertRows();
- break;
- case Change::Removed:
- model->beginRemoveRows(
- QModelIndex(), change.index, change.index + change.count - 1);
- model->endRemoveRows();
- break;
- case Change::Moved:
- model->beginMoveRows(
- QModelIndex(),
- change.index,
- change.index + change.count - 1,
- QModelIndex(),
- change.to > change.index ? change.to + change.count : change.to);
- model->endMoveRows();
- break;
- case Change::Changed:
- emit model->dataChanged(
- model->createIndex(change.index, 0),
- model->createIndex(change.index + change.count - 1, 0),
- change.roles);
- break;
- }
- }
- }
+ ListModel::sync(s->list->m_listModel, m_orig->m_listModel);
}
syncDone.wakeAll();
diff --git a/src/qml/types/qqmllistmodelworkeragent_p.h b/src/qml/types/qqmllistmodelworkeragent_p.h
index 5a39651bf7..ae2d4b11e0 100644
--- a/src/qml/types/qqmllistmodelworkeragent_p.h
+++ b/src/qml/types/qqmllistmodelworkeragent_p.h
@@ -59,6 +59,8 @@
#include <private/qv8engine_p.h>
+QT_REQUIRE_CONFIG(qml_list_model);
+
QT_BEGIN_NAMESPACE
@@ -91,7 +93,7 @@ public:
struct VariantRef
{
- VariantRef() : a(0) {}
+ VariantRef() : a(nullptr) {}
VariantRef(const VariantRef &r) : a(r.a) { if (a) a->addref(); }
VariantRef(QQmlListModelWorkerAgent *_a) : a(_a) { if (a) a->addref(); }
~VariantRef() { if (a) a->release(); }
@@ -114,35 +116,12 @@ private:
friend class QQuickWorkerScriptEnginePrivate;
friend class QQmlListModel;
- struct Change
- {
- int modelUid;
- enum { Inserted, Removed, Moved, Changed } type;
- int index; // Inserted/Removed/Moved/Changed
- int count; // Inserted/Removed/Moved/Changed
- int to; // Moved
- QVector<int> roles;
- };
-
- struct Data
- {
- QList<Change> changes;
-
- void clearChange(int uid);
- void insertChange(int uid, int index, int count);
- void removeChange(int uid, int index, int count);
- void moveChange(int uid, int index, int count, int to);
- void changedChange(int uid, int index, int count, const QVector<int> &roles);
- };
- Data data;
-
struct Sync : public QEvent {
- Sync(const Data &d, QQmlListModel *l)
+ Sync(QQmlListModel *l)
: QEvent(QEvent::User)
- , data(d)
, list(l)
{}
- Data data;
+ ~Sync();
QQmlListModel *list;
};
diff --git a/src/qml/types/qqmlmodelsmodule.cpp b/src/qml/types/qqmlmodelsmodule.cpp
index c36e26a525..b7b9c9ee1c 100644
--- a/src/qml/types/qqmlmodelsmodule.cpp
+++ b/src/qml/types/qqmlmodelsmodule.cpp
@@ -39,9 +39,15 @@
#include "qqmlmodelsmodule_p.h"
#include <QtCore/qitemselectionmodel.h>
+#if QT_CONFIG(qml_list_model)
#include <private/qqmllistmodel_p.h>
+#endif
+#if QT_CONFIG(qml_delegate_model)
#include <private/qqmldelegatemodel_p.h>
+#include <private/qqmldelegatecomponent_p.h>
+#endif
#include <private/qqmlobjectmodel_p.h>
+#include <private/qqmltablemodel_p.h>
QT_BEGIN_NAMESPACE
@@ -49,14 +55,28 @@ void QQmlModelsModule::defineModule()
{
const char uri[] = "QtQml.Models";
+#if QT_CONFIG(qml_list_model)
qmlRegisterType<QQmlListElement>(uri, 2, 1, "ListElement");
qmlRegisterCustomType<QQmlListModel>(uri, 2, 1, "ListModel", new QQmlListModelParser);
+#endif
+#if QT_CONFIG(qml_delegate_model)
qmlRegisterType<QQmlDelegateModel>(uri, 2, 1, "DelegateModel");
qmlRegisterType<QQmlDelegateModelGroup>(uri, 2, 1, "DelegateModelGroup");
+#endif
qmlRegisterType<QQmlObjectModel>(uri, 2, 1, "ObjectModel");
qmlRegisterType<QQmlObjectModel,3>(uri, 2, 3, "ObjectModel");
qmlRegisterType<QItemSelectionModel>(uri, 2, 2, "ItemSelectionModel");
}
+void QQmlModelsModule::defineLabsModule()
+{
+ const char uri[] = "Qt.labs.qmlmodels";
+
+ qmlRegisterUncreatableType<QQmlAbstractDelegateComponent>(uri, 1, 0, "AbstractDelegateComponent", QQmlAbstractDelegateComponent::tr("Cannot create instance of abstract class AbstractDelegateComponent."));
+ qmlRegisterType<QQmlDelegateChooser>(uri, 1, 0, "DelegateChooser");
+ qmlRegisterType<QQmlDelegateChoice>(uri, 1, 0, "DelegateChoice");
+ qmlRegisterType<QQmlTableModel>(uri, 1, 0, "TableModel");
+}
+
QT_END_NAMESPACE
diff --git a/src/qml/types/qqmlmodelsmodule_p.h b/src/qml/types/qqmlmodelsmodule_p.h
index bac9bea81e..939ecc1500 100644
--- a/src/qml/types/qqmlmodelsmodule_p.h
+++ b/src/qml/types/qqmlmodelsmodule_p.h
@@ -59,6 +59,7 @@ class Q_QML_PRIVATE_EXPORT QQmlModelsModule
{
public:
static void defineModule();
+ static void defineLabsModule();
};
QT_END_NAMESPACE
diff --git a/src/qml/types/qqmlobjectmodel.cpp b/src/qml/types/qqmlobjectmodel.cpp
index 2814b9d38f..2f4d427430 100644
--- a/src/qml/types/qqmlobjectmodel.cpp
+++ b/src/qml/types/qqmlobjectmodel.cpp
@@ -72,7 +72,7 @@ public:
int ref;
};
- QQmlObjectModelPrivate() : QObjectPrivate() {}
+ QQmlObjectModelPrivate() : QObjectPrivate(), moveId(0) {}
static void children_append(QQmlListProperty<QObject> *prop, QObject *item) {
int index = static_cast<QQmlObjectModelPrivate *>(prop->data)->children.count();
@@ -129,7 +129,7 @@ public:
}
QQmlChangeSet changeSet;
- changeSet.move(from, to, n, -1);
+ changeSet.move(from, to, n, ++moveId);
emit q->modelUpdated(changeSet, false);
emit q->childrenChanged();
}
@@ -166,7 +166,7 @@ public:
return -1;
}
-
+ uint moveId;
QList<Item> children;
};
@@ -176,7 +176,7 @@ public:
\instantiates QQmlObjectModel
\inqmlmodule QtQml.Models
\ingroup qtquick-models
- \brief Defines a set of items to be used as a model
+ \brief Defines a set of items to be used as a model.
An ObjectModel contains the visual items to be used in a view.
When an ObjectModel is used in a view, the view does not require
@@ -206,27 +206,10 @@ public:
}
\endcode
- \image visualitemmodel.png
+ \image objectmodel.png
\sa {Qt Quick Examples - Views}
*/
-/*!
- \qmltype VisualItemModel
- \instantiates QQmlObjectModel
- \inqmlmodule QtQuick
- \brief Defines a set of objects to be used as a model
-
- The VisualItemModel type contains the objects to be used
- as a model.
-
- This element is now primarily available as ObjectModel in the QtQml.Models module.
- VisualItemModel continues to be provided, with the same implementation, in \c QtQuick for
- compatibility reasons.
-
- For full details about the type, see the \l ObjectModel documentation.
-
- \sa {QtQml.Models::ObjectModel}
-*/
QQmlObjectModel::QQmlObjectModel(QObject *parent)
: QQmlInstanceModel(*(new QQmlObjectModelPrivate), parent)
@@ -267,7 +250,7 @@ bool QQmlObjectModel::isValid() const
return true;
}
-QObject *QQmlObjectModel::object(int index, bool)
+QObject *QQmlObjectModel::object(int index, QQmlIncubator::IncubationMode)
{
Q_D(QQmlObjectModel);
QQmlObjectModelPrivate::Item &item = d->children[index];
@@ -287,7 +270,7 @@ QQmlInstanceModel::ReleaseFlags QQmlObjectModel::release(QObject *item)
if (!d->children[idx].deref())
return QQmlInstanceModel::Referenced;
}
- return 0;
+ return nullptr;
}
QString QQmlObjectModel::stringValue(int index, const QString &name)
@@ -298,6 +281,11 @@ QString QQmlObjectModel::stringValue(int index, const QString &name)
return QQmlEngine::contextForObject(d->children.at(index).item)->contextProperty(name).toString();
}
+QQmlIncubator::Status QQmlObjectModel::incubationStatus(int)
+{
+ return QQmlIncubator::Ready;
+}
+
int QQmlObjectModel::indexOf(QObject *item, QObject *) const
{
Q_D(const QQmlObjectModel);
@@ -332,7 +320,7 @@ QObject *QQmlObjectModel::get(int index) const
{
Q_D(const QQmlObjectModel);
if (index < 0 || index >= d->children.count())
- return 0;
+ return nullptr;
return d->children.at(index).item;
}
diff --git a/src/qml/types/qqmlobjectmodel_p.h b/src/qml/types/qqmlobjectmodel_p.h
index fc4c03874f..4ac4f1c65b 100644
--- a/src/qml/types/qqmlobjectmodel_p.h
+++ b/src/qml/types/qqmlobjectmodel_p.h
@@ -52,6 +52,7 @@
//
#include <private/qtqmlglobal_p.h>
+#include <private/qqmlincubator_p.h>
#include <QtQml/qqml.h>
#include <QtCore/qobject.h>
@@ -59,6 +60,7 @@ QT_BEGIN_NAMESPACE
class QObject;
class QQmlChangeSet;
+class QAbstractItemModel;
class Q_QML_PRIVATE_EXPORT QQmlInstanceModel : public QObject
{
@@ -74,13 +76,15 @@ public:
virtual int count() const = 0;
virtual bool isValid() const = 0;
- virtual QObject *object(int index, bool asynchronous=false) = 0;
+ virtual QObject *object(int index, QQmlIncubator::IncubationMode incubationMode = QQmlIncubator::AsynchronousIfNested) = 0;
virtual ReleaseFlags release(QObject *object) = 0;
virtual void cancel(int) {}
virtual QString stringValue(int, const QString &) = 0;
virtual void setWatchedRoles(const QList<QByteArray> &roles) = 0;
+ virtual QQmlIncubator::Status incubationStatus(int index) = 0;
virtual int indexOf(QObject *object, QObject *objectContext) const = 0;
+ virtual const QAbstractItemModel *abstractItemModel() const { return nullptr; }
Q_SIGNALS:
void countChanged();
@@ -90,7 +94,7 @@ Q_SIGNALS:
void destroyingItem(QObject *object);
protected:
- QQmlInstanceModel(QObjectPrivate &dd, QObject *parent = 0)
+ QQmlInstanceModel(QObjectPrivate &dd, QObject *parent = nullptr)
: QObject(dd, parent) {}
private:
@@ -108,15 +112,16 @@ class Q_QML_PRIVATE_EXPORT QQmlObjectModel : public QQmlInstanceModel
Q_CLASSINFO("DefaultProperty", "children")
public:
- QQmlObjectModel(QObject *parent=0);
+ QQmlObjectModel(QObject *parent=nullptr);
~QQmlObjectModel() {}
int count() const override;
bool isValid() const override;
- QObject *object(int index, bool asynchronous = false) override;
+ QObject *object(int index, QQmlIncubator::IncubationMode incubationMode = QQmlIncubator::AsynchronousIfNested) override;
ReleaseFlags release(QObject *object) override;
QString stringValue(int index, const QString &role) override;
void setWatchedRoles(const QList<QByteArray> &) override {}
+ QQmlIncubator::Status incubationStatus(int index) override;
int indexOf(QObject *object, QObject *objectContext) const override;
diff --git a/src/qml/types/qqmltableinstancemodel.cpp b/src/qml/types/qqmltableinstancemodel.cpp
new file mode 100644
index 0000000000..2170e2daec
--- /dev/null
+++ b/src/qml/types/qqmltableinstancemodel.cpp
@@ -0,0 +1,547 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQml module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE: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$
+**
+****************************************************************************/
+
+#include "qqmltableinstancemodel_p.h"
+#include "qqmldelegatecomponent_p.h"
+
+#include <QtCore/QTimer>
+
+#include <QtQml/private/qqmlincubator_p.h>
+#include <QtQml/private/qqmlchangeset_p.h>
+#include <QtQml/private/qqmlcomponent_p.h>
+
+QT_BEGIN_NAMESPACE
+
+const char* kModelItemTag = "_tableinstancemodel_modelItem";
+
+bool QQmlTableInstanceModel::isDoneIncubating(QQmlDelegateModelItem *modelItem)
+{
+ if (!modelItem->incubationTask)
+ return true;
+
+ const auto status = modelItem->incubationTask->status();
+ return (status == QQmlIncubator::Ready) || (status == QQmlIncubator::Error);
+}
+
+void QQmlTableInstanceModel::deleteModelItemLater(QQmlDelegateModelItem *modelItem)
+{
+ Q_ASSERT(modelItem);
+
+ delete modelItem->object;
+ modelItem->object = nullptr;
+
+ if (modelItem->contextData) {
+ modelItem->contextData->invalidate();
+ Q_ASSERT(modelItem->contextData->refCount == 1);
+ modelItem->contextData = nullptr;
+ }
+
+ modelItem->deleteLater();
+}
+
+QQmlTableInstanceModel::QQmlTableInstanceModel(QQmlContext *qmlContext, QObject *parent)
+ : QQmlInstanceModel(*(new QObjectPrivate()), parent)
+ , m_qmlContext(qmlContext)
+ , m_metaType(new QQmlDelegateModelItemMetaType(m_qmlContext->engine()->handle(), nullptr, QStringList()))
+{
+}
+
+void QQmlTableInstanceModel::useImportVersion(int minorVersion)
+{
+ m_adaptorModel.useImportVersion(minorVersion);
+}
+
+QQmlTableInstanceModel::~QQmlTableInstanceModel()
+{
+ for (const auto modelItem : m_modelItems) {
+ // No item in m_modelItems should be referenced at this point. The view
+ // should release all its items before it deletes this model. Only model items
+ // that are still being incubated should be left for us to delete.
+ Q_ASSERT(modelItem->objectRef == 0);
+ Q_ASSERT(modelItem->incubationTask);
+ // Check that we are not being deleted while we're
+ // in the process of e.g emitting a created signal.
+ Q_ASSERT(modelItem->scriptRef == 0);
+
+ if (modelItem->object) {
+ delete modelItem->object;
+ modelItem->object = nullptr;
+ modelItem->contextData->invalidate();
+ modelItem->contextData = nullptr;
+ }
+ }
+
+ deleteAllFinishedIncubationTasks();
+ qDeleteAll(m_modelItems);
+ drainReusableItemsPool(0);
+}
+
+QQmlComponent *QQmlTableInstanceModel::resolveDelegate(int index)
+{
+ if (m_delegateChooser) {
+ const int row = m_adaptorModel.rowAt(index);
+ const int column = m_adaptorModel.columnAt(index);
+ QQmlComponent *delegate = nullptr;
+ QQmlAbstractDelegateComponent *chooser = m_delegateChooser;
+ do {
+ delegate = chooser->delegate(&m_adaptorModel, row, column);
+ chooser = qobject_cast<QQmlAbstractDelegateComponent *>(delegate);
+ } while (chooser);
+ return delegate;
+ }
+
+ return m_delegate;
+}
+
+QQmlDelegateModelItem *QQmlTableInstanceModel::resolveModelItem(int index)
+{
+ // Check if an item for the given index is already loaded and ready
+ QQmlDelegateModelItem *modelItem = m_modelItems.value(index, nullptr);
+ if (modelItem)
+ return modelItem;
+
+ QQmlComponent *delegate = resolveDelegate(index);
+ if (!delegate)
+ return nullptr;
+
+ // Check if the pool contains an item that can be reused
+ modelItem = takeFromReusableItemsPool(delegate);
+ if (modelItem) {
+ reuseItem(modelItem, index);
+ m_modelItems.insert(index, modelItem);
+ return modelItem;
+ }
+
+ // Create a new item from scratch
+ modelItem = m_adaptorModel.createItem(m_metaType, index);
+ if (modelItem) {
+ modelItem->delegate = delegate;
+ m_modelItems.insert(index, modelItem);
+ return modelItem;
+ }
+
+ qWarning() << Q_FUNC_INFO << "failed creating a model item for index: " << index;
+ return nullptr;
+}
+
+QObject *QQmlTableInstanceModel::object(int index, QQmlIncubator::IncubationMode incubationMode)
+{
+ Q_ASSERT(m_delegate);
+ Q_ASSERT(index >= 0 && index < m_adaptorModel.count());
+ Q_ASSERT(m_qmlContext && m_qmlContext->isValid());
+
+ QQmlDelegateModelItem *modelItem = resolveModelItem(index);
+ if (!modelItem)
+ return nullptr;
+
+ if (modelItem->object) {
+ // The model item has already been incubated. So
+ // just bump the ref-count and return it.
+ modelItem->referenceObject();
+ return modelItem->object;
+ }
+
+ // The object is not ready, and needs to be incubated
+ incubateModelItem(modelItem, incubationMode);
+ if (!isDoneIncubating(modelItem))
+ return nullptr;
+
+ // Incubation is done, so the task should be removed
+ Q_ASSERT(!modelItem->incubationTask);
+
+ if (!modelItem->object) {
+ // The object was incubated synchronously (otherwise we would return above). But since
+ // we have no object, the incubation must have failed. And when we have no object, there
+ // should be no object references either. And there should also not be any internal script
+ // refs at this point. So we delete the model item.
+ Q_ASSERT(!modelItem->isObjectReferenced());
+ Q_ASSERT(!modelItem->isReferenced());
+ m_modelItems.remove(modelItem->index);
+ delete modelItem;
+ return nullptr;
+ }
+
+ // Incubation was completed sync and successful
+ modelItem->referenceObject();
+ return modelItem->object;
+}
+
+QQmlInstanceModel::ReleaseFlags QQmlTableInstanceModel::release(QObject *object, ReusableFlag reusable)
+{
+ Q_ASSERT(object);
+ auto modelItem = qvariant_cast<QQmlDelegateModelItem *>(object->property(kModelItemTag));
+ Q_ASSERT(modelItem);
+
+ if (!modelItem->releaseObject())
+ return QQmlDelegateModel::Referenced;
+
+ if (modelItem->isReferenced()) {
+ // We still have an internal reference to this object, which means that we are told to release an
+ // object while the createdItem signal for it is still on the stack. This can happen when objects
+ // are e.g delivered async, and the user flicks back and forth quicker than the loading can catch
+ // up with. The view might then find that the object is no longer visible and should be released.
+ // We detect this case in incubatorStatusChanged(), and delete it there instead. But from the callers
+ // point of view, it should consider it destroyed.
+ return QQmlDelegateModel::Destroyed;
+ }
+
+ // The item is not referenced by anyone
+ m_modelItems.remove(modelItem->index);
+
+ if (reusable == Reusable) {
+ insertIntoReusableItemsPool(modelItem);
+ return QQmlInstanceModel::Referenced;
+ }
+
+ // The item is not reused or referenced by anyone, so just delete it
+ modelItem->destroyObject();
+ emit destroyingItem(object);
+
+ delete modelItem;
+ return QQmlInstanceModel::Destroyed;
+}
+
+void QQmlTableInstanceModel::cancel(int index)
+{
+ auto modelItem = m_modelItems.value(index);
+ Q_ASSERT(modelItem);
+
+ // Since the view expects the item to be incubating, there should be
+ // an incubation task. And since the incubation is not done, no-one
+ // should yet have received, and therfore hold a reference to, the object.
+ Q_ASSERT(modelItem->incubationTask);
+ Q_ASSERT(!modelItem->isObjectReferenced());
+
+ m_modelItems.remove(index);
+
+ if (modelItem->object)
+ delete modelItem->object;
+
+ // modelItem->incubationTask will be deleted from the modelItems destructor
+ delete modelItem;
+}
+
+void QQmlTableInstanceModel::insertIntoReusableItemsPool(QQmlDelegateModelItem *modelItem)
+{
+ // Currently, the only way for a view to reuse items is to call QQmlTableInstanceModel::release()
+ // with the second argument explicitly set to QQmlTableInstanceModel::Reusable. If the released
+ // item is no longer referenced, it will be added to the pool. Reusing of items can be specified
+ // per item, in case certain items cannot be recycled.
+ // A QQmlDelegateModelItem knows which delegate its object was created from. So when we are
+ // about to create a new item, we first check if the pool contains an item based on the same
+ // delegate from before. If so, we take it out of the pool (instead of creating a new item), and
+ // update all its context-, and attached properties.
+ // When a view is recycling items, it should call QQmlTableInstanceModel::drainReusableItemsPool()
+ // regularly. As there is currently no logic to 'hibernate' items in the pool, they are only
+ // meant to rest there for a short while, ideally only from the time e.g a row is unloaded
+ // on one side of the view, and until a new row is loaded on the opposite side. In-between
+ // this time, the application will see the item as fully functional and 'alive' (just not
+ // visible on screen). Since this time is supposed to be short, we don't take any action to
+ // notify the application about it, since we don't want to trigger any bindings that can
+ // disturb performance.
+ // A recommended time for calling drainReusableItemsPool() is each time a view has finished
+ // loading e.g a new row or column. If there are more items in the pool after that, it means
+ // that the view most likely doesn't need them anytime soon. Those items should be destroyed to
+ // not consume resources.
+ // Depending on if a view is a list or a table, it can sometimes be performant to keep
+ // items in the pool for a bit longer than one "row out/row in" cycle. E.g for a table, if the
+ // number of visible rows in a view is much larger than the number of visible columns.
+ // In that case, if you flick out a row, and then flick in a column, you would throw away a lot
+ // of items in the pool if completely draining it. The reason is that unloading a row places more
+ // items in the pool than what ends up being recycled when loading a new column. And then, when you
+ // next flick in a new row, you would need to load all those drained items again from scratch. For
+ // that reason, you can specify a maxPoolTime to the drainReusableItemsPool() that allows you to keep
+ // items in the pool for a bit longer, effectively keeping more items in circulation.
+ // A recommended maxPoolTime would be equal to the number of dimenstions in the view, which
+ // means 1 for a list view and 2 for a table view. If you specify 0, all items will be drained.
+ Q_ASSERT(!modelItem->incubationTask);
+ Q_ASSERT(!modelItem->isObjectReferenced());
+ Q_ASSERT(!modelItem->isReferenced());
+ Q_ASSERT(modelItem->object);
+
+ modelItem->poolTime = 0;
+ m_reusableItemsPool.append(modelItem);
+ emit itemPooled(modelItem->index, modelItem->object);
+}
+
+QQmlDelegateModelItem *QQmlTableInstanceModel::takeFromReusableItemsPool(const QQmlComponent *delegate)
+{
+ // Find the oldest item in the pool that was made from the same delegate as
+ // the given argument, remove it from the pool, and return it.
+ if (m_reusableItemsPool.isEmpty())
+ return nullptr;
+
+ for (auto it = m_reusableItemsPool.begin(); it != m_reusableItemsPool.end(); ++it) {
+ if ((*it)->delegate != delegate)
+ continue;
+ auto modelItem = *it;
+ m_reusableItemsPool.erase(it);
+ return modelItem;
+ }
+
+ return nullptr;
+}
+
+void QQmlTableInstanceModel::drainReusableItemsPool(int maxPoolTime)
+{
+ // Rather than releasing all pooled items upon a call to this function, each
+ // item has a poolTime. The poolTime specifies for how many loading cycles an item
+ // has been resting in the pool. And for each invocation of this function, poolTime
+ // will increase. If poolTime is equal to, or exceeds, maxPoolTime, it will be removed
+ // from the pool and released. This way, the view can tweak a bit for how long
+ // items should stay in "circulation", even if they are not recycled right away.
+ for (auto it = m_reusableItemsPool.begin(); it != m_reusableItemsPool.end();) {
+ auto modelItem = *it;
+ modelItem->poolTime++;
+ if (modelItem->poolTime <= maxPoolTime) {
+ ++it;
+ } else {
+ it = m_reusableItemsPool.erase(it);
+ release(modelItem->object, NotReusable);
+ }
+ }
+}
+
+void QQmlTableInstanceModel::reuseItem(QQmlDelegateModelItem *item, int newModelIndex)
+{
+ // Update the context properties index, row and column on
+ // the delegate item, and inform the application about it.
+ const int newRow = m_adaptorModel.rowAt(newModelIndex);
+ const int newColumn = m_adaptorModel.columnAt(newModelIndex);
+ item->setModelIndex(newModelIndex, newRow, newColumn);
+
+ // Notify the application that all 'dynamic'/role-based context data has
+ // changed as well (their getter function will use the updated index).
+ auto const itemAsList = QList<QQmlDelegateModelItem *>() << item;
+ auto const updateAllRoles = QVector<int>();
+ m_adaptorModel.notify(itemAsList, newModelIndex, 1, updateAllRoles);
+
+ // Inform the view that the item is recycled. This will typically result
+ // in the view updating its own attached delegate item properties.
+ emit itemReused(newModelIndex, item->object);
+}
+
+void QQmlTableInstanceModel::incubateModelItem(QQmlDelegateModelItem *modelItem, QQmlIncubator::IncubationMode incubationMode)
+{
+ // Guard the model item temporarily so that it's not deleted from
+ // incubatorStatusChanged(), in case the incubation is done synchronously.
+ modelItem->scriptRef++;
+
+ if (modelItem->incubationTask) {
+ // We're already incubating the model item from a previous request. If the previous call requested
+ // the item async, but the current request needs it sync, we need to force-complete the incubation.
+ const bool sync = (incubationMode == QQmlIncubator::Synchronous || incubationMode == QQmlIncubator::AsynchronousIfNested);
+ if (sync && modelItem->incubationTask->incubationMode() == QQmlIncubator::Asynchronous)
+ modelItem->incubationTask->forceCompletion();
+ } else {
+ modelItem->incubationTask = new QQmlTableInstanceModelIncubationTask(this, modelItem, incubationMode);
+
+ QQmlContextData *ctxt = new QQmlContextData;
+ QQmlContext *creationContext = modelItem->delegate->creationContext();
+ ctxt->setParent(QQmlContextData::get(creationContext ? creationContext : m_qmlContext.data()));
+ ctxt->contextObject = modelItem;
+ modelItem->contextData = ctxt;
+
+ QQmlComponentPrivate::get(modelItem->delegate)->incubateObject(
+ modelItem->incubationTask,
+ modelItem->delegate,
+ m_qmlContext->engine(),
+ ctxt,
+ QQmlContextData::get(m_qmlContext));
+ }
+
+ // Remove the temporary guard
+ modelItem->scriptRef--;
+}
+
+void QQmlTableInstanceModel::incubatorStatusChanged(QQmlTableInstanceModelIncubationTask *incubationTask, QQmlIncubator::Status status)
+{
+ QQmlDelegateModelItem *modelItem = incubationTask->modelItemToIncubate;
+ Q_ASSERT(modelItem->incubationTask);
+
+ modelItem->incubationTask = nullptr;
+ incubationTask->modelItemToIncubate = nullptr;
+
+ if (status == QQmlIncubator::Ready) {
+ // Tag the incubated object with the model item for easy retrieval upon release etc.
+ modelItem->object->setProperty(kModelItemTag, QVariant::fromValue(modelItem));
+
+ // Emit that the item has been created. What normally happens next is that the view
+ // upon receiving the signal asks for the model item once more. And since the item is
+ // now in the map, it will be returned directly.
+ Q_ASSERT(modelItem->object);
+ modelItem->scriptRef++;
+ emit createdItem(modelItem->index, modelItem->object);
+ modelItem->scriptRef--;
+ } else if (status == QQmlIncubator::Error) {
+ qWarning() << "Error incubating delegate:" << incubationTask->errors();
+ }
+
+ if (!modelItem->isReferenced() && !modelItem->isObjectReferenced()) {
+ // We have no internal reference to the model item, and the view has no
+ // reference to the incubated object. So just delete the model item.
+ // Note that being here means that the object was incubated _async_
+ // (otherwise modelItem->isReferenced() would be true).
+ m_modelItems.remove(modelItem->index);
+
+ if (modelItem->object) {
+ modelItem->scriptRef++;
+ emit destroyingItem(modelItem->object);
+ modelItem->scriptRef--;
+ Q_ASSERT(!modelItem->isReferenced());
+ }
+
+ deleteModelItemLater(modelItem);
+ }
+
+ deleteIncubationTaskLater(incubationTask);
+}
+
+QQmlIncubator::Status QQmlTableInstanceModel::incubationStatus(int index) {
+ const auto modelItem = m_modelItems.value(index, nullptr);
+ if (!modelItem)
+ return QQmlIncubator::Null;
+
+ if (modelItem->incubationTask)
+ return modelItem->incubationTask->status();
+
+ // Since we clear the incubation task when we're done
+ // incubating, it means that the status is Ready.
+ return QQmlIncubator::Ready;
+}
+
+void QQmlTableInstanceModel::deleteIncubationTaskLater(QQmlIncubator *incubationTask)
+{
+ // We often need to post-delete incubation tasks, since we cannot
+ // delete them while we're in the middle of an incubation change callback.
+ Q_ASSERT(!m_finishedIncubationTasks.contains(incubationTask));
+ m_finishedIncubationTasks.append(incubationTask);
+ if (m_finishedIncubationTasks.count() == 1)
+ QTimer::singleShot(1, this, &QQmlTableInstanceModel::deleteAllFinishedIncubationTasks);
+}
+
+void QQmlTableInstanceModel::deleteAllFinishedIncubationTasks()
+{
+ qDeleteAll(m_finishedIncubationTasks);
+ m_finishedIncubationTasks.clear();
+}
+
+QVariant QQmlTableInstanceModel::model() const
+{
+ return m_adaptorModel.model();
+}
+
+void QQmlTableInstanceModel::setModel(const QVariant &model)
+{
+ // Pooled items are still accessible/alive for the application, and
+ // needs to stay in sync with the model. So we need to drain the pool
+ // completely when the model changes.
+ drainReusableItemsPool(0);
+ if (auto const aim = abstractItemModel())
+ disconnect(aim, &QAbstractItemModel::dataChanged, this, &QQmlTableInstanceModel::dataChangedCallback);
+ m_adaptorModel.setModel(model, this, m_qmlContext->engine());
+ if (auto const aim = abstractItemModel())
+ connect(aim, &QAbstractItemModel::dataChanged, this, &QQmlTableInstanceModel::dataChangedCallback);
+}
+
+void QQmlTableInstanceModel::dataChangedCallback(const QModelIndex &begin, const QModelIndex &end, const QVector<int> &roles)
+{
+ // This function is called when model data has changed. In that case, we tell the adaptor model
+ // to go through all the items we have created, find the ones that are affected, and notify that
+ // their model data has changed. This will in turn update QML bindings inside the delegate items.
+ int numberOfRowsChanged = end.row() - begin.row() + 1;
+ int numberOfColumnsChanged = end.column() - begin.column() + 1;
+
+ for (int column = 0; column < numberOfColumnsChanged; ++column) {
+ const int columnIndex = begin.column() + column;
+ const int rowIndex = begin.row() + (columnIndex * rows());
+ m_adaptorModel.notify(m_modelItems.values(), rowIndex, numberOfRowsChanged, roles);
+ }
+}
+
+QQmlComponent *QQmlTableInstanceModel::delegate() const
+{
+ return m_delegate;
+}
+
+void QQmlTableInstanceModel::setDelegate(QQmlComponent *delegate)
+{
+ if (m_delegate == delegate)
+ return;
+
+ m_delegateChooser = nullptr;
+ if (delegate) {
+ QQmlAbstractDelegateComponent *adc =
+ qobject_cast<QQmlAbstractDelegateComponent *>(delegate);
+ if (adc)
+ m_delegateChooser = adc;
+ }
+
+ m_delegate = delegate;
+}
+
+const QAbstractItemModel *QQmlTableInstanceModel::abstractItemModel() const
+{
+ return m_adaptorModel.adaptsAim() ? m_adaptorModel.aim() : nullptr;
+}
+
+// --------------------------------------------------------
+
+void QQmlTableInstanceModelIncubationTask::setInitialState(QObject *object)
+{
+ modelItemToIncubate->object = object;
+ emit tableInstanceModel->initItem(modelItemToIncubate->index, object);
+}
+
+void QQmlTableInstanceModelIncubationTask::statusChanged(QQmlIncubator::Status status)
+{
+ if (!QQmlTableInstanceModel::isDoneIncubating(modelItemToIncubate))
+ return;
+
+ // We require the view to cancel any ongoing load
+ // requests before the tableInstanceModel is destructed.
+ Q_ASSERT(tableInstanceModel);
+
+ tableInstanceModel->incubatorStatusChanged(this, status);
+}
+
+#include "moc_qqmltableinstancemodel_p.cpp"
+
+QT_END_NAMESPACE
+
diff --git a/src/qml/types/qqmltableinstancemodel_p.h b/src/qml/types/qqmltableinstancemodel_p.h
new file mode 100644
index 0000000000..3dd5c4e4ce
--- /dev/null
+++ b/src/qml/types/qqmltableinstancemodel_p.h
@@ -0,0 +1,162 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQml module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE: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$
+**
+****************************************************************************/
+
+#ifndef QQMLTABLEINSTANCEMODEL_P_H
+#define QQMLTABLEINSTANCEMODEL_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtQml/private/qqmldelegatemodel_p.h>
+#include <QtQml/private/qqmldelegatemodel_p_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class QQmlTableInstanceModel;
+class QQmlAbstractDelegateComponent;
+
+class QQmlTableInstanceModelIncubationTask : public QQDMIncubationTask
+{
+public:
+ QQmlTableInstanceModelIncubationTask(
+ QQmlTableInstanceModel *tableInstanceModel
+ , QQmlDelegateModelItem* modelItemToIncubate
+ , IncubationMode mode)
+ : QQDMIncubationTask(nullptr, mode)
+ , modelItemToIncubate(modelItemToIncubate)
+ , tableInstanceModel(tableInstanceModel) {
+ clear();
+ }
+
+ void statusChanged(Status status) override;
+ void setInitialState(QObject *object) override;
+
+ QQmlDelegateModelItem *modelItemToIncubate = nullptr;
+ QQmlTableInstanceModel *tableInstanceModel = nullptr;
+};
+
+class Q_QML_PRIVATE_EXPORT QQmlTableInstanceModel : public QQmlInstanceModel
+{
+ Q_OBJECT
+
+public:
+
+ enum ReusableFlag {
+ NotReusable,
+ Reusable
+ };
+
+ QQmlTableInstanceModel(QQmlContext *qmlContext, QObject *parent = nullptr);
+ ~QQmlTableInstanceModel() override;
+
+ void useImportVersion(int minorVersion);
+
+ int count() const override { return m_adaptorModel.count(); }
+ int rows() const { return m_adaptorModel.rowCount(); }
+ int columns() const { return m_adaptorModel.columnCount(); }
+
+ bool isValid() const override { return true; }
+
+ QVariant model() const;
+ void setModel(const QVariant &model);
+
+ QQmlComponent *delegate() const;
+ void setDelegate(QQmlComponent *);
+
+ const QAbstractItemModel *abstractItemModel() const override;
+
+ QObject *object(int index, QQmlIncubator::IncubationMode incubationMode = QQmlIncubator::AsynchronousIfNested) override;
+ ReleaseFlags release(QObject *object) override { return release(object, NotReusable); }
+ ReleaseFlags release(QObject *object, ReusableFlag reusable);
+ void cancel(int) override;
+
+ void insertIntoReusableItemsPool(QQmlDelegateModelItem *modelItem);
+ QQmlDelegateModelItem *takeFromReusableItemsPool(const QQmlComponent *delegate);
+ void drainReusableItemsPool(int maxPoolTime);
+ int poolSize() { return m_reusableItemsPool.size(); }
+ void reuseItem(QQmlDelegateModelItem *item, int newModelIndex);
+
+ QQmlIncubator::Status incubationStatus(int index) override;
+
+ QString stringValue(int, const QString &) override { Q_UNREACHABLE(); return QString(); }
+ void setWatchedRoles(const QList<QByteArray> &) override { Q_UNREACHABLE(); }
+ int indexOf(QObject *, QObject *) const override { Q_UNREACHABLE(); return 0; }
+
+Q_SIGNALS:
+ void itemPooled(int index, QObject *object);
+ void itemReused(int index, QObject *object);
+
+private:
+ QQmlComponent *resolveDelegate(int index);
+
+ QQmlAdaptorModel m_adaptorModel;
+ QQmlAbstractDelegateComponent *m_delegateChooser = nullptr;
+ QQmlComponent *m_delegate = nullptr;
+ QPointer<QQmlContext> m_qmlContext;
+ QQmlDelegateModelItemMetaType *m_metaType;
+
+ QHash<int, QQmlDelegateModelItem *> m_modelItems;
+ QList<QQmlDelegateModelItem *> m_reusableItemsPool;
+ QList<QQmlIncubator *> m_finishedIncubationTasks;
+
+ void incubateModelItem(QQmlDelegateModelItem *modelItem, QQmlIncubator::IncubationMode incubationMode);
+ void incubatorStatusChanged(QQmlTableInstanceModelIncubationTask *dmIncubationTask, QQmlIncubator::Status status);
+ void deleteIncubationTaskLater(QQmlIncubator *incubationTask);
+ void deleteAllFinishedIncubationTasks();
+ QQmlDelegateModelItem *resolveModelItem(int index);
+
+ void dataChangedCallback(const QModelIndex &begin, const QModelIndex &end, const QVector<int> &roles);
+
+ static bool isDoneIncubating(QQmlDelegateModelItem *modelItem);
+ static void deleteModelItemLater(QQmlDelegateModelItem *modelItem);
+
+ friend class QQmlTableInstanceModelIncubationTask;
+};
+
+QT_END_NAMESPACE
+
+#endif // QQMLTABLEINSTANCEMODEL_P_H
diff --git a/src/qml/types/qqmltablemodel.cpp b/src/qml/types/qqmltablemodel.cpp
new file mode 100644
index 0000000000..6068155f5a
--- /dev/null
+++ b/src/qml/types/qqmltablemodel.cpp
@@ -0,0 +1,950 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQml module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE: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$
+**
+****************************************************************************/
+
+#include "qqmltablemodel_p.h"
+
+#include <QtCore/qloggingcategory.h>
+#include <QtQml/qqmlinfo.h>
+#include <QtQml/qqmlengine.h>
+
+QT_BEGIN_NAMESPACE
+
+Q_LOGGING_CATEGORY(lcTableModel, "qt.qml.tablemodel")
+
+static const QString lengthPropertyName = QStringLiteral("length");
+static const QString displayRoleName = QStringLiteral("display");
+
+/*!
+ \qmltype TableModel
+ \instantiates QQmlTableModel
+ \inqmlmodule Qt.labs.qmlmodels
+ \brief Encapsulates a simple table model.
+ \since 5.12
+
+ The TableModel type stores JavaScript objects as data for a table model
+ that can be used with \l TableView.
+
+ The following snippet shows the simplest use case for TableModel:
+
+ \snippet qml/tablemodel/fruit-example-simpledelegate.qml file
+
+ The model's initial data is set with either the \l rows property or by
+ calling \l appendRow(). Once the first row has been added to the table, the
+ columns and roles are established and will be fixed for the lifetime of the
+ model.
+
+ To access a specific row, the \l getRow() function can be used.
+ It's also possible to access the model's JavaScript data
+ directly via the \l rows property, but it is not possible to
+ modify the model data this way.
+
+ To add new rows, use \l appendRow() and \l insertRow(). To modify
+ existing rows, use \l setRow(), \l moveRow(), \l removeRow(), and
+ \l clear().
+
+ It is also possible to modify the model's data via the delegate,
+ as shown in the example above:
+
+ \snippet qml/tablemodel/fruit-example-simpledelegate.qml delegate
+
+ If the type of the data at the modified role does not match the type of the
+ data that is set, it will be automatically converted via
+ \l {QVariant::canConvert()}{QVariant}.
+
+ For convenience, TableModel provides the \c display role if it is not
+ explicitly specified in any column. When a column only has one role
+ declared, that role will be used used as the display role. However, when
+ there is more than one role in a column, which role will be used is
+ undefined. This is because JavaScript does not guarantee that properties
+ within an object can be accessed according to the order in which they were
+ declared. This is why \c checkable may be used as the display role for the
+ first column even though \c checked is declared before it, for example.
+
+ \section1 Using DelegateChooser with TableModel
+
+ For most real world use cases, it is recommended to use DelegateChooser
+ as the delegate of a TableView that uses TableModel. This allows you to
+ use specific roles in the relevant delegates. For example, the snippet
+ above can be rewritten to use DelegateChooser like so:
+
+ \snippet qml/tablemodel/fruit-example-delegatechooser.qml file
+
+ The most specific delegates are declared first: the columns at index \c 0
+ and \c 1 have \c bool and \c integer data types, so they use a
+ \l [QtQuickControls2]{CheckBox} and \l [QtQuickControls2]{SpinBox},
+ respectively. The remaining columns can simply use a
+ \l [QtQuickControls2]{TextField}, and so that delegate is declared
+ last as a fallback.
+
+ \sa QAbstractTableModel, TableView
+*/
+
+QQmlTableModel::QQmlTableModel(QObject *parent)
+ : QAbstractTableModel(parent)
+{
+ mRoleNames = QAbstractTableModel::roleNames();
+}
+
+QQmlTableModel::~QQmlTableModel()
+{
+}
+
+/*!
+ \qmlproperty object TableModel::rows
+
+ This property holds the model data in the form of an array of rows:
+
+ \snippet qml/tablemodel/fruit-example-simpledelegate.qml rows
+
+ \sa getRow(), setRow(), moveRow(), appendRow(), insertRow(), clear(), rowCount, columnCount
+*/
+QVariant QQmlTableModel::rows() const
+{
+ return mRows;
+}
+
+void QQmlTableModel::setRows(const QVariant &rows)
+{
+ if (rows.userType() != qMetaTypeId<QJSValue>()) {
+ qmlWarning(this) << "setRows(): \"rows\" must be an array; actual type is " << rows.typeName();
+ return;
+ }
+
+ const QJSValue rowsAsJSValue = rows.value<QJSValue>();
+ const QVariantList rowsAsVariantList = rowsAsJSValue.toVariant().toList();
+ if (rowsAsVariantList == mRows) {
+ // No change.
+ return;
+ }
+
+ QVariant firstRowAsVariant;
+ QVariantList firstRow;
+ if (!rowsAsVariantList.isEmpty()) {
+ // There are rows to validate. If they're not valid,
+ // we'll return early without changing anything.
+ firstRowAsVariant = rowsAsVariantList.first();
+ firstRow = firstRowAsVariant.toList();
+
+ if (firstRowAsVariant.type() != QVariant::List) {
+ qmlWarning(this) << "setRows(): each row in \"rows\" must be an array of objects";
+ return;
+ }
+
+ if (mColumnCount > 0) {
+ qCDebug(lcTableModel) << "validating" << rowsAsVariantList.size()
+ << "rows against existing metadata";
+
+ // This is not the first time the rows have been set; validate the new columns.
+ for (int i = 0; i < rowsAsVariantList.size(); ++i) {
+ // validateNewRow() expects a QVariant wrapping a QJSValue, so to
+ // simplify the code, just create one here.
+ const QVariant row = QVariant::fromValue(rowsAsJSValue.property(i));
+ if (!validateNewRow("setRows()", row, i))
+ return;
+ }
+ }
+ }
+
+ const int oldRowCount = mRowCount;
+ const int oldColumnCount = mColumnCount;
+
+ beginResetModel();
+
+ // We don't clear the column or role data, because a TableModel should not be reused in that way.
+ // Once it has valid data, its columns and roles are fixed.
+ mRows = rowsAsVariantList;
+ mRowCount = mRows.size();
+
+ const bool isFirstTimeSet = mColumnCount == 0;
+ if (isFirstTimeSet && mRowCount > 0) {
+ // This is the first time the rows have been set, so establish
+ // the column count and gather column metadata.
+ mColumnCount = firstRow.size();
+ qCDebug(lcTableModel) << "gathering metadata for" << mColumnCount << "columns from first row:";
+
+ // Go through each property of each cell in the first row
+ // and make a role name from it.
+ int userRoleKey = Qt::UserRole;
+ for (int columnIndex = 0; columnIndex < mColumnCount; ++columnIndex) {
+ // We need it as a QVariantMap because we need to get
+ // the name of the property, which we can't do with QJSValue's API.
+ const QVariantMap column = firstRow.at(columnIndex).toMap();
+ const QStringList columnPropertyNames = column.keys();
+ ColumnProperties properties;
+ int propertyInfoIndex = 0;
+
+ qCDebug(lcTableModel).nospace() << "- column " << columnIndex << ":";
+
+ for (const QString &roleName : columnPropertyNames) {
+ // QML/JS supports utf8.
+ const QByteArray roleNameUtf8 = roleName.toUtf8();
+ if (!mRoleNames.values().contains(roleNameUtf8)) {
+ // We don't already have this role name, so it's a user role.
+ mRoleNames[userRoleKey] = roleName.toUtf8().constData();
+ qCDebug(lcTableModel) << " - added new user role" << roleName << "with key" << userRoleKey;
+ ++userRoleKey;
+ } else {
+ qCDebug(lcTableModel) << " - found existing role" << roleName;
+ }
+
+ if (properties.explicitDisplayRoleIndex == -1 && roleName == displayRoleName) {
+ // The user explicitly declared a "display" role,
+ // so now we don't need to make it the first role in the column for them.
+ properties.explicitDisplayRoleIndex = propertyInfoIndex;
+ }
+
+ // Keep track of the type of property so we can use it to validate new rows later on.
+ const QVariant roleValue = column.value(roleName);
+ const auto propertyInfo = ColumnPropertyInfo(roleName, roleValue.type(),
+ QString::fromLatin1(roleValue.typeName()));
+ properties.infoForProperties.append(propertyInfo);
+
+ qCDebug(lcTableModel) << " - column property" << propertyInfo.name
+ << "has type" << propertyInfo.typeName;
+
+ ++propertyInfoIndex;
+ }
+
+ mColumnProperties.append(properties);
+ }
+ }
+
+ endResetModel();
+
+ emit rowsChanged();
+
+ if (mRowCount != oldRowCount)
+ emit rowCountChanged();
+ if (mColumnCount != oldColumnCount)
+ emit columnCountChanged();
+}
+
+/*!
+ \qmlmethod TableModel::appendRow(object row)
+
+ Adds a new row to the end of the model, with the
+ values (cells) in \a row.
+
+ \code
+ model.appendRow([
+ { checkable: true, checked: false },
+ { amount: 1 },
+ { fruitType: "Pear" },
+ { fruitName: "Williams" },
+ { fruitPrice: 1.50 },
+ ])
+ \endcode
+
+ \sa insertRow(), setRow(), removeRow()
+*/
+void QQmlTableModel::appendRow(const QVariant &row)
+{
+ if (!validateNewRow("appendRow()", row, -1, AppendOperation))
+ return;
+
+ doInsert(mRowCount, row);
+}
+
+/*!
+ \qmlmethod TableModel::clear()
+
+ Removes all rows from the model.
+
+ \sa removeRow()
+*/
+void QQmlTableModel::clear()
+{
+ QQmlEngine *engine = qmlEngine(this);
+ Q_ASSERT(engine);
+ setRows(QVariant::fromValue(engine->newArray()));
+}
+
+/*!
+ \qmlmethod object TableModel::getRow(int rowIndex)
+
+ Returns the row at \a rowIndex in the model.
+
+ Note that this equivalent to accessing the row directly
+ through the \l rows property:
+
+ \code
+ Component.onCompleted: {
+ // These two lines are equivalent.
+ console.log(model.getRow(0).fruitName);
+ console.log(model.rows[0].fruitName);
+ }
+ \endcode
+
+ \note the returned object cannot be used to modify the contents of the
+ model; use setRow() instead.
+
+ \sa setRow(), appendRow(), insertRow(), removeRow(), moveRow()
+*/
+QVariant QQmlTableModel::getRow(int rowIndex)
+{
+ if (!validateRowIndex("getRow()", "rowIndex", rowIndex))
+ return QVariant();
+
+ return mRows.at(rowIndex);
+}
+
+/*!
+ \qmlmethod TableModel::insertRow(int rowIndex, object row)
+
+ Adds a new row to the list model at position \a rowIndex, with the
+ values (cells) in \a row.
+
+ \code
+ model.insertRow(2, [
+ { checkable: true, checked: false },
+ { amount: 1 },
+ { fruitType: "Pear" },
+ { fruitName: "Williams" },
+ { fruitPrice: 1.50 },
+ ])
+ \endcode
+
+ The \a rowIndex must be to an existing item in the list, or one past
+ the end of the list (equivalent to \l appendRow()).
+
+ \sa appendRow(), setRow(), removeRow(), rowCount
+*/
+void QQmlTableModel::insertRow(int rowIndex, const QVariant &row)
+{
+ if (!validateNewRow("insertRow()", row, rowIndex))
+ return;
+
+ doInsert(rowIndex, row);
+}
+
+void QQmlTableModel::doInsert(int rowIndex, const QVariant &row)
+{
+ beginInsertRows(QModelIndex(), rowIndex, rowIndex);
+
+ // Adding rowAsVariant.toList() will add each invidual variant in the list,
+ // which is definitely not what we want.
+ const QVariant rowAsVariant = row.value<QJSValue>().toVariant();
+ mRows.insert(rowIndex, rowAsVariant);
+ ++mRowCount;
+
+ qCDebug(lcTableModel).nospace() << "inserted the following row to the model at index"
+ << rowIndex << ":\n" << rowAsVariant.toList();
+
+ endInsertRows();
+ emit rowCountChanged();
+}
+
+/*!
+ \qmlmethod TableModel::moveRow(int fromRowIndex, int toRowIndex, int rows)
+
+ Moves \a rows from the index at \a fromRowIndex to the index at
+ \a toRowIndex.
+
+ The from and to ranges must exist; for example, to move the first 3 items
+ to the end of the list:
+
+ \code
+ model.moveRow(0, model.rowCount - 3, 3)
+ \endcode
+
+ \sa appendRow(), insertRow(), removeRow(), rowCount
+*/
+void QQmlTableModel::moveRow(int fromRowIndex, int toRowIndex, int rows)
+{
+ if (fromRowIndex == toRowIndex) {
+ qmlWarning(this) << "moveRow(): \"fromRowIndex\" cannot be equal to \"toRowIndex\"";
+ return;
+ }
+
+ if (rows <= 0) {
+ qmlWarning(this) << "moveRow(): \"rows\" is less than or equal to 0";
+ return;
+ }
+
+ if (!validateRowIndex("moveRow()", "fromRowIndex", fromRowIndex))
+ return;
+
+ if (!validateRowIndex("moveRow()", "toRowIndex", toRowIndex))
+ return;
+
+ if (fromRowIndex + rows > mRowCount) {
+ qmlWarning(this) << "moveRow(): \"fromRowIndex\" (" << fromRowIndex
+ << ") + \"rows\" (" << rows << ") = " << (fromRowIndex + rows)
+ << ", which is greater than rowCount() of " << mRowCount;
+ return;
+ }
+
+ if (toRowIndex + rows > mRowCount) {
+ qmlWarning(this) << "moveRow(): \"toRowIndex\" (" << toRowIndex
+ << ") + \"rows\" (" << rows << ") = " << (toRowIndex + rows)
+ << ", which is greater than rowCount() of " << mRowCount;
+ return;
+ }
+
+ qCDebug(lcTableModel).nospace() << "moving " << rows
+ << " row(s) from index " << fromRowIndex
+ << " to index " << toRowIndex;
+
+ // Based on the same call in QQmlListModel::moveRow().
+ beginMoveRows(QModelIndex(), fromRowIndex, fromRowIndex + rows - 1, QModelIndex(),
+ toRowIndex > fromRowIndex ? toRowIndex + rows : toRowIndex);
+
+ // Based on ListModel::moveRow().
+ if (fromRowIndex > toRowIndex) {
+ // Only move forwards - flip if moving backwards.
+ const int from = fromRowIndex;
+ const int to = toRowIndex;
+ fromRowIndex = to;
+ toRowIndex = to + rows;
+ rows = from - to;
+ }
+
+ QVector<QVariant> store;
+ store.reserve(rows);
+ for (int i = 0; i < (toRowIndex - fromRowIndex); ++i)
+ store.append(mRows.at(fromRowIndex + rows + i));
+ for (int i = 0; i < rows; ++i)
+ store.append(mRows.at(fromRowIndex + i));
+ for (int i = 0; i < store.size(); ++i)
+ mRows[fromRowIndex + i] = store[i];
+
+ qCDebug(lcTableModel).nospace() << "after moving, rows are:\n" << mRows;
+
+ endMoveRows();
+}
+
+/*!
+ \qmlmethod TableModel::removeRow(int rowIndex, int rows = 1)
+
+ Removes the row at \a rowIndex from the model.
+
+ \sa clear(), rowCount
+*/
+void QQmlTableModel::removeRow(int rowIndex, int rows)
+{
+ if (!validateRowIndex("removeRow()", "rowIndex", rowIndex))
+ return;
+
+ if (rows <= 0) {
+ qmlWarning(this) << "removeRow(): \"rows\" is less than or equal to zero";
+ return;
+ }
+
+ if (rowIndex + rows - 1 >= mRowCount) {
+ qmlWarning(this) << "removeRow(): \"rows\" " << rows
+ << " exceeds available rowCount() of " << mRowCount
+ << " when removing from \"rowIndex\" " << rowIndex;
+ return;
+ }
+
+ beginRemoveRows(QModelIndex(), rowIndex, rowIndex + rows - 1);
+
+ auto firstIterator = mRows.begin() + rowIndex;
+ // The "last" argument to erase() is exclusive, so we go one past the last item.
+ auto lastIterator = firstIterator + rows;
+ mRows.erase(firstIterator, lastIterator);
+ mRowCount -= rows;
+
+ endRemoveRows();
+ emit rowCountChanged();
+
+ qCDebug(lcTableModel).nospace() << "removed " << rows
+ << " items from the model, starting at index " << rowIndex;
+}
+
+/*!
+ \qmlmethod TableModel::setRow(int rowIndex, object row)
+
+ Changes the row at \a rowIndex in the model with \a row.
+
+ All columns/cells must be present in \c row, and in the correct order.
+
+ \code
+ model.setRow(0, [
+ { checkable: true, checked: false },
+ { amount: 1 },
+ { fruitType: "Pear" },
+ { fruitName: "Williams" },
+ { fruitPrice: 1.50 },
+ ])
+ \endcode
+
+ If \a rowIndex is equal to \c rowCount(), then a new row is appended to the
+ model. Otherwise, \a rowIndex must point to an existing row in the model.
+
+ \sa appendRow(), insertRow(), rowCount
+*/
+void QQmlTableModel::setRow(int rowIndex, const QVariant &row)
+{
+ if (!validateNewRow("setRow()", row, rowIndex))
+ return;
+
+ if (rowIndex != mRowCount) {
+ // Setting an existing row.
+ mRows[rowIndex] = row;
+
+ // For now we just assume the whole row changed, as it's simpler.
+ const QModelIndex topLeftModelIndex(createIndex(rowIndex, 0));
+ const QModelIndex bottomRightModelIndex(createIndex(rowIndex, mColumnCount - 1));
+ emit dataChanged(topLeftModelIndex, bottomRightModelIndex);
+ } else {
+ // Appending a row.
+ doInsert(rowIndex, row);
+ }
+}
+
+/*!
+ \qmlproperty var TableModel::roleDataProvider
+
+ This property can hold a function that will map roles to values.
+
+ When assigned, it will be called each time data() is called, to enable
+ extracting arbitrary values, converting the data in arbitrary ways, or even
+ doing calculations. It takes 3 arguments: \c index (\l QModelIndex),
+ \c role (string), and \c cellData (object), which is the complete data that
+ is stored in the given cell. (If the cell contains a JS object with
+ multiple named values, the entire object will be given in \c cellData.)
+ The function that you define must return the value to be used; for example
+ a typical delegate will display the value returned for the \c display role,
+ so you can check whether that is the role and return data in a form that is
+ suitable for the delegate to show:
+
+ \snippet qml/tablemodel/roleDataProvider.qml 0
+*/
+QJSValue QQmlTableModel::roleDataProvider() const
+{
+ return mRoleDataProvider;
+}
+
+void QQmlTableModel::setRoleDataProvider(QJSValue roleDataProvider)
+{
+ if (roleDataProvider.strictlyEquals(mRoleDataProvider))
+ return;
+
+ mRoleDataProvider = roleDataProvider;
+ emit roleDataProviderChanged();
+}
+
+/*!
+ \qmlmethod QModelIndex TableModel::index(int row, int column)
+
+ Returns a \l QModelIndex object referencing the given \a row and \a column,
+ which can be passed to the data() function to get the data from that cell,
+ or to setData() to edit the contents of that cell.
+
+ \code
+ import QtQml 2.14
+ import Qt.labs.qmlmodels 1.0
+
+ TableModel {
+ id: model
+ rows: [
+ [{ fruitType: "Apple" }, { fruitPrice: 1.50 }],
+ [{ fruitType: "Orange" }, { fruitPrice: 2.50 }]
+ ]
+ Component.onCompleted: {
+ for (var r = 0; r < model.rowCount; ++r) {
+ console.log("An " + model.data(model.index(r, 0)).fruitType +
+ " costs " + model.data(model.index(r, 1)).fruitPrice.toFixed(2))
+ }
+ }
+ }
+ \endcode
+
+ \sa {QModelIndex and related Classes in QML}, data()
+*/
+// Note: we don't document the parent argument, because you never need it, because
+// cells in a TableModel don't have parents. But it is there because this function is an override.
+QModelIndex QQmlTableModel::index(int row, int column, const QModelIndex &parent) const
+{
+ return row >= 0 && row < rowCount() && column >= 0 && column < columnCount() && !parent.isValid()
+ ? createIndex(row, column)
+ : QModelIndex();
+}
+
+/*!
+ \qmlproperty int TableModel::rowCount
+ \readonly
+
+ This read-only property holds the number of rows in the model.
+
+ This value changes whenever rows are added or removed from the model.
+*/
+int QQmlTableModel::rowCount(const QModelIndex &parent) const
+{
+ if (parent.isValid())
+ return 0;
+
+ return mRowCount;
+}
+
+/*!
+ \qmlproperty int TableModel::columnCount
+ \readonly
+
+ This read-only property holds the number of columns in the model.
+
+ The number of columns is fixed for the lifetime of the model
+ after the \l rows property is set or \l appendRow() is called for the first
+ time.
+*/
+int QQmlTableModel::columnCount(const QModelIndex &parent) const
+{
+ if (parent.isValid())
+ return 0;
+
+ return mColumnCount;
+}
+
+/*!
+ \qmlmethod variant TableModel::data(QModelIndex index, string role)
+
+ Returns the data from the table cell at the given \a index belonging to the
+ given \a role.
+
+ \sa index()
+*/
+QVariant QQmlTableModel::data(const QModelIndex &index, const QString &role) const
+{
+ const int iRole = mRoleNames.key(role.toUtf8(), -1);
+ if (iRole >= 0)
+ return data(index, iRole);
+ return QVariant();
+}
+
+QVariant QQmlTableModel::data(const QModelIndex &index, int role) const
+{
+ const int row = index.row();
+ if (row < 0 || row >= rowCount())
+ return QVariant();
+
+ const int column = index.column();
+ if (column < 0 || column >= columnCount())
+ return QVariant();
+
+ if (!mRoleNames.contains(role))
+ return QVariant();
+
+ const QVariantList rowData = mRows.at(row).toList();
+
+ if (mRoleDataProvider.isCallable()) {
+ auto engine = qmlEngine(this);
+ const auto args = QJSValueList() <<
+ engine->toScriptValue(index) <<
+ QString::fromUtf8(mRoleNames.value(role)) <<
+ engine->toScriptValue(rowData.at(column));
+ return const_cast<QQmlTableModel*>(this)->mRoleDataProvider.call(args).toVariant();
+ }
+
+ // TODO: should we also allow this code to be executed if roleDataProvider doesn't
+ // handle the role/column, so that it only has to handle the case where there is
+ // more than one role in a column?
+ const QVariantMap columnData = rowData.at(column).toMap();
+ const QString propertyName = columnPropertyNameFromRole(column, role);
+ const QVariant value = columnData.value(propertyName);
+ return value;
+}
+
+/*!
+ \qmlmethod bool TableModel::setData(QModelIndex index, string role, variant value)
+
+ Inserts or updates the data field named by \a role in the table cell at the
+ given \a index with \a value. Returns true if sucessful, false if not.
+
+ \sa index()
+*/
+bool QQmlTableModel::setData(const QModelIndex &index, const QString &role, const QVariant &value)
+{
+ const int iRole = mRoleNames.key(role.toUtf8(), -1);
+ if (iRole >= 0)
+ return setData(index, value, iRole);
+ return false;
+}
+
+bool QQmlTableModel::setData(const QModelIndex &index, const QVariant &value, int role)
+{
+ const int row = index.row();
+ if (row < 0 || row >= rowCount())
+ return false;
+
+ const int column = index.column();
+ if (column < 0 || column >= columnCount())
+ return false;
+
+ if (!mRoleNames.contains(role))
+ return false;
+
+ const QVariantList rowData = mRows.at(row).toList();
+ const QString propertyName = columnPropertyNameFromRole(column, role);
+
+ qCDebug(lcTableModel).nospace() << "setData() called with index "
+ << index << ", value " << value << " and role " << propertyName;
+
+ // Verify that the role exists for this column.
+ const ColumnPropertyInfo propertyInfo = findColumnPropertyInfo(column, propertyName);
+ if (!propertyInfo.isValid()) {
+ QString message;
+ QDebug stream(&message);
+ stream.nospace() << "setData(): no role named " << propertyName
+ << " at column index " << column << ". The available roles for that column are:\n";
+
+ const QVector<ColumnPropertyInfo> availableProperties = mColumnProperties.at(column).infoForProperties;
+ for (auto propertyInfo : availableProperties)
+ stream << " - " << propertyInfo.name << " (" << qPrintable(propertyInfo.typeName) << ")";
+
+ qmlWarning(this) << message;
+ return false;
+ }
+
+ // Verify that the type of the value is what we expect.
+ // If the value set is not of the expected type, we can try to convert it automatically.
+ QVariant effectiveValue = value;
+ if (value.type() != propertyInfo.type) {
+ if (!value.canConvert(int(propertyInfo.type))) {
+ qmlWarning(this).nospace() << "setData(): the value " << value
+ << " set at row " << row << " column " << column << " with role " << propertyName
+ << " cannot be converted to " << propertyInfo.typeName;
+ return false;
+ }
+
+ if (!effectiveValue.convert(int(propertyInfo.type))) {
+ qmlWarning(this).nospace() << "setData(): failed converting value " << value
+ << " set at row " << row << " column " << column << " with role " << propertyName
+ << " to " << propertyInfo.typeName;
+ return false;
+ }
+ }
+
+ QVariantMap modifiedColumn = rowData.at(column).toMap();
+ modifiedColumn[propertyName] = value;
+
+ QVariantList modifiedRow = rowData;
+ modifiedRow[column] = modifiedColumn;
+ mRows[row] = modifiedRow;
+
+ QVector<int> rolesChanged;
+ rolesChanged.append(role);
+ emit dataChanged(index, index, rolesChanged);
+
+ return true;
+}
+
+QHash<int, QByteArray> QQmlTableModel::roleNames() const
+{
+ return mRoleNames;
+}
+
+QQmlTableModel::ColumnPropertyInfo::ColumnPropertyInfo()
+{
+}
+
+QQmlTableModel::ColumnPropertyInfo::ColumnPropertyInfo(
+ const QString &name, QVariant::Type type, const QString &typeName) :
+ name(name),
+ type(type),
+ typeName(typeName)
+{
+}
+
+bool QQmlTableModel::ColumnPropertyInfo::isValid() const
+{
+ return !name.isEmpty();
+}
+
+bool QQmlTableModel::validateRowType(const char *functionName, const QVariant &row) const
+{
+ if (row.userType() != qMetaTypeId<QJSValue>()) {
+ qmlWarning(this) << functionName << ": expected \"row\" argument to be an array,"
+ << " but got " << row.typeName() << " instead";
+ return false;
+ }
+
+ const QVariant rowAsVariant = row.value<QJSValue>().toVariant();
+ if (rowAsVariant.type() != QVariant::List) {
+ qmlWarning(this) << functionName << ": expected \"row\" argument to be an array,"
+ << " but got " << row.typeName() << " instead";
+ return false;
+ }
+
+ return true;
+}
+
+bool QQmlTableModel::validateNewRow(const char *functionName, const QVariant &row,
+ int rowIndex, NewRowOperationFlag appendFlag) const
+{
+ if (!validateRowType(functionName, row))
+ return false;
+
+ if (appendFlag == OtherOperation) {
+ // Inserting/setting.
+ if (rowIndex < 0) {
+ qmlWarning(this) << functionName << ": \"rowIndex\" cannot be negative";
+ return false;
+ }
+
+ if (rowIndex > mRowCount) {
+ qmlWarning(this) << functionName << ": \"rowIndex\" " << rowIndex
+ << " is greater than rowCount() of " << mRowCount;
+ return false;
+ }
+ }
+
+ const QVariant rowAsVariant = row.value<QJSValue>().toVariant();
+ const QVariantList rowAsList = rowAsVariant.toList();
+
+ const int columnCount = rowAsList.size();
+ if (columnCount != mColumnCount) {
+ qmlWarning(this) << functionName << ": expected " << mColumnCount
+ << " columns, but got " << columnCount;
+ return false;
+ }
+
+ // Verify that the row's columns and their roles match the name and type of existing data.
+ // This iterates across the columns in the row. For example:
+ // [
+ // { checkable: true, checked: false }, // columnIndex == 0
+ // { amount: 1 }, // columnIndex == 1
+ // { fruitType: "Orange" }, // etc.
+ // { fruitName: "Navel" },
+ // { fruitPrice: 2.50 }
+ // ],
+ for (int columnIndex = 0; columnIndex < mColumnCount; ++columnIndex) {
+ const QVariantMap column = rowAsList.at(columnIndex).toMap();
+ if (!validateColumnPropertyTypes(functionName, column, columnIndex))
+ return false;
+ }
+
+ return true;
+}
+
+bool QQmlTableModel::validateRowIndex(const char *functionName, const char *argumentName, int rowIndex) const
+{
+ if (rowIndex < 0) {
+ qmlWarning(this) << functionName << ": \"" << argumentName << "\" cannot be negative";
+ return false;
+ }
+
+ if (rowIndex >= mRowCount) {
+ qmlWarning(this) << functionName << ": \"" << argumentName
+ << "\" " << rowIndex << " is greater than or equal to rowCount() of " << mRowCount;
+ return false;
+ }
+
+ return true;
+}
+
+bool QQmlTableModel::validateColumnPropertyTypes(const char *functionName,
+ const QVariantMap &column, int columnIndex) const
+{
+ // Actual
+ const QVariantList columnProperties = column.values();
+ const QStringList propertyNames = column.keys();
+ // Expected
+ const QVector<ColumnPropertyInfo> properties = mColumnProperties.at(columnIndex).infoForProperties;
+
+ // This iterates across the properties in the column. For example:
+ // 0 1 2
+ // { foo: "A", bar: 1, baz: true },
+ for (int propertyIndex = 0; propertyIndex < properties.size(); ++propertyIndex) {
+ const QString propertyName = propertyNames.at(propertyIndex);
+ const QVariant propertyValue = columnProperties.at(propertyIndex);
+ const ColumnPropertyInfo expectedPropertyFormat = properties.at(propertyIndex);
+
+ if (!validateColumnPropertyType(functionName, propertyName,
+ propertyValue, expectedPropertyFormat, columnIndex)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool QQmlTableModel::validateColumnPropertyType(const char *functionName, const QString &propertyName,
+ const QVariant &propertyValue, const ColumnPropertyInfo &expectedPropertyFormat, int columnIndex) const
+{
+ if (propertyName != expectedPropertyFormat.name) {
+ qmlWarning(this) << functionName
+ << ": expected property named " << expectedPropertyFormat.name
+ << " at column index " << columnIndex
+ << ", but got " << propertyName << " instead";
+ return false;
+ }
+
+ if (propertyValue.type() != expectedPropertyFormat.type) {
+ qmlWarning(this) << functionName
+ << ": expected property with type " << expectedPropertyFormat.typeName
+ << " at column index " << columnIndex
+ << ", but got " << propertyValue.typeName() << " instead";
+ return false;
+ }
+
+ return true;
+}
+
+QQmlTableModel::ColumnPropertyInfo QQmlTableModel::findColumnPropertyInfo(
+ int columnIndex, const QString &columnPropertyName) const
+{
+ // TODO: check if a hash with its string-based lookup is faster,
+ // keeping in mind that we may be doing index-based lookups too.
+ const QVector<ColumnPropertyInfo> properties = mColumnProperties.at(columnIndex).infoForProperties;
+ for (int i = 0; i < properties.size(); ++i) {
+ const ColumnPropertyInfo &info = properties.at(i);
+ if (info.name == columnPropertyName)
+ return info;
+ }
+
+ return ColumnPropertyInfo();
+}
+
+QString QQmlTableModel::columnPropertyNameFromRole(int columnIndex, int role) const
+{
+ QString propertyName;
+ if (role == Qt::DisplayRole && mColumnProperties.at(columnIndex).explicitDisplayRoleIndex == -1) {
+ // The user is getting or setting data for the display role,
+ // but didn't specify any role with the name "display" in this column.
+ // So, we give them the implicit display role, aka the first property we find.
+ propertyName = mColumnProperties.at(columnIndex).infoForProperties.first().name;
+ } else {
+ // QML/JS supports utf8.
+ propertyName = QString::fromUtf8(mRoleNames.value(role));
+ }
+ return propertyName;
+}
+
+QT_END_NAMESPACE
diff --git a/src/qml/types/qqmltablemodel_p.h b/src/qml/types/qqmltablemodel_p.h
new file mode 100644
index 0000000000..33b2189fcd
--- /dev/null
+++ b/src/qml/types/qqmltablemodel_p.h
@@ -0,0 +1,158 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQml module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE: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$
+**
+****************************************************************************/
+
+#ifndef QQMLTABLEMODEL_P_H
+#define QQMLTABLEMODEL_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtCore/QObject>
+#include <QtCore/QAbstractTableModel>
+#include <QtQml/qqml.h>
+#include <QtQml/private/qtqmlglobal_p.h>
+#include <QtQml/QJSValue>
+
+QT_BEGIN_NAMESPACE
+
+class Q_QML_PRIVATE_EXPORT QQmlTableModel : public QAbstractTableModel
+{
+ Q_OBJECT
+ Q_PROPERTY(int columnCount READ columnCount NOTIFY columnCountChanged FINAL)
+ Q_PROPERTY(int rowCount READ rowCount NOTIFY rowCountChanged FINAL)
+ Q_PROPERTY(QVariant rows READ rows WRITE setRows NOTIFY rowsChanged FINAL)
+ Q_PROPERTY(QJSValue roleDataProvider READ roleDataProvider WRITE setRoleDataProvider NOTIFY roleDataProviderChanged)
+
+public:
+ QQmlTableModel(QObject *parent = nullptr);
+ ~QQmlTableModel() override;
+
+ QVariant rows() const;
+ void setRows(const QVariant &rows);
+
+ Q_INVOKABLE void appendRow(const QVariant &row);
+ Q_INVOKABLE void clear();
+ Q_INVOKABLE QVariant getRow(int rowIndex);
+ Q_INVOKABLE void insertRow(int rowIndex, const QVariant &row);
+ Q_INVOKABLE void moveRow(int fromRowIndex, int toRowIndex, int rows = 1);
+ Q_INVOKABLE void removeRow(int rowIndex, int rows = 1);
+ Q_INVOKABLE void setRow(int rowIndex, const QVariant &row);
+
+ QJSValue roleDataProvider() const;
+ void setRoleDataProvider(QJSValue roleDataProvider);
+
+ QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
+ int rowCount(const QModelIndex &parent = QModelIndex()) const override;
+ int columnCount(const QModelIndex &parent = QModelIndex()) const override;
+ Q_INVOKABLE QVariant data(const QModelIndex &index, const QString &role) const;
+ QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
+ Q_INVOKABLE bool setData(const QModelIndex &index, const QString &role, const QVariant &value);
+ bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::DisplayRole) override;
+ QHash<int, QByteArray> roleNames() const override;
+
+Q_SIGNALS:
+ void columnCountChanged();
+ void rowCountChanged();
+ void rowsChanged();
+ void roleDataProviderChanged();
+
+private:
+ class ColumnPropertyInfo
+ {
+ public:
+ ColumnPropertyInfo();
+ ColumnPropertyInfo(const QString &name, QVariant::Type type, const QString &typeName);
+
+ bool isValid() const;
+
+ QString name;
+ QVariant::Type type = QVariant::Invalid;
+ QString typeName;
+ };
+
+ struct ColumnProperties
+ {
+ QVector<ColumnPropertyInfo> infoForProperties;
+ // If there was a display role found in this column, it'll be stored here.
+ // The index is into infoForProperties.
+ int explicitDisplayRoleIndex = -1;
+ };
+
+ enum NewRowOperationFlag {
+ OtherOperation, // insert(), set(), etc.
+ AppendOperation
+ };
+
+ bool validateRowType(const char *functionName, const QVariant &row) const;
+ bool validateNewRow(const char *functionName, const QVariant &row,
+ int rowIndex, NewRowOperationFlag appendFlag = OtherOperation) const;
+ bool validateRowIndex(const char *functionName, const char *argumentName, int rowIndex) const;
+ bool validateColumnPropertyTypes(const char *functionName, const QVariantMap &column, int columnIndex) const;
+ bool validateColumnPropertyType(const char *functionName, const QString &propertyName,
+ const QVariant &propertyValue, const ColumnPropertyInfo &expectedPropertyFormat, int columnIndex) const;
+
+ ColumnPropertyInfo findColumnPropertyInfo(int columnIndex, const QString &columnPropertyNameFromRole) const;
+ QString columnPropertyNameFromRole(int columnIndex, int role) const;
+
+ void doInsert(int rowIndex, const QVariant &row);
+
+ QVariantList mRows;
+ int mRowCount = 0;
+ int mColumnCount = 0;
+ // Each entry contains information about the properties of the column at that index.
+ QVector<ColumnProperties> mColumnProperties;
+ // key = property index (0 to number of properties across all columns)
+ // value = role name
+ QHash<int, QByteArray> mRoleNames;
+ QJSValue mRoleDataProvider;
+};
+
+QT_END_NAMESPACE
+
+QML_DECLARE_TYPE(QQmlTableModel)
+
+#endif // QQMLTABLEMODEL_P_H
diff --git a/src/qml/types/qqmltimer.cpp b/src/qml/types/qqmltimer.cpp
index 2037c4f6cd..af2ff56f2a 100644
--- a/src/qml/types/qqmltimer.cpp
+++ b/src/qml/types/qqmltimer.cpp
@@ -57,7 +57,7 @@ class QQmlTimerPrivate : public QObjectPrivate, public QAnimationJobChangeListen
Q_DECLARE_PUBLIC(QQmlTimer)
public:
QQmlTimerPrivate()
- : interval(1000), running(false), repeating(false), triggeredOnStart(false)
+ : running(false), repeating(false), triggeredOnStart(false)
, classBegun(false), componentComplete(false), firstTick(true), awaitingTick(false) {}
void animationFinished(QAbstractAnimationJob *) override;
@@ -71,7 +71,7 @@ public:
}
}
- int interval;
+ int interval = 1000;
QPauseAnimationJob pause;
bool running : 1;
bool repeating : 1;
@@ -87,7 +87,7 @@ public:
\instantiates QQmlTimer
\inqmlmodule QtQml
\ingroup qtquick-interceptors
- \brief Triggers a handler at a specified interval
+ \brief Triggers a handler at a specified interval.
A Timer can be used to trigger an action either once, or repeatedly
at a given interval.
diff --git a/src/qml/types/qqmltimer_p.h b/src/qml/types/qqmltimer_p.h
index 7739dad2a6..0160e97a2f 100644
--- a/src/qml/types/qqmltimer_p.h
+++ b/src/qml/types/qqmltimer_p.h
@@ -57,6 +57,8 @@
#include <private/qtqmlglobal_p.h>
+QT_REQUIRE_CONFIG(qml_animation);
+
QT_BEGIN_NAMESPACE
class QQmlTimerPrivate;
@@ -72,7 +74,7 @@ class Q_QML_PRIVATE_EXPORT QQmlTimer : public QObject, public QQmlParserStatus
Q_PROPERTY(QObject *parent READ parent CONSTANT)
public:
- QQmlTimer(QObject *parent=0);
+ QQmlTimer(QObject *parent=nullptr);
void setInterval(int interval);
int interval() const;
diff --git a/src/qml/types/qquickpackage.cpp b/src/qml/types/qquickpackage.cpp
index e0d1888f33..03539d8737 100644
--- a/src/qml/types/qquickpackage.cpp
+++ b/src/qml/types/qquickpackage.cpp
@@ -49,7 +49,7 @@ QT_BEGIN_NAMESPACE
\instantiates QQuickPackage
\inqmlmodule QtQuick
\ingroup qtquick-views
- \brief Specifies a collection of named items
+ \brief Specifies a collection of named items.
The Package type is used in conjunction with
DelegateModel to enable delegates with a shared context
@@ -183,7 +183,7 @@ QObject *QQuickPackage::part(const QString &name)
if (name == QLatin1String("default") && !d->dataList.isEmpty())
return d->dataList.at(0);
- return 0;
+ return nullptr;
}
QQuickPackageAttached *QQuickPackage::qmlAttachedProperties(QObject *o)
diff --git a/src/qml/types/qquickpackage_p.h b/src/qml/types/qquickpackage_p.h
index ca383bfdcb..122c7fcb30 100644
--- a/src/qml/types/qquickpackage_p.h
+++ b/src/qml/types/qquickpackage_p.h
@@ -66,7 +66,7 @@ class Q_AUTOTEST_EXPORT QQuickPackage : public QObject
Q_PROPERTY(QQmlListProperty<QObject> data READ data)
public:
- QQuickPackage(QObject *parent=0);
+ QQuickPackage(QObject *parent=nullptr);
virtual ~QQuickPackage();
QQmlListProperty<QObject> data();
diff --git a/src/qml/types/qquickworkerscript.cpp b/src/qml/types/qquickworkerscript.cpp
index 6159355afc..edb112276c 100644
--- a/src/qml/types/qquickworkerscript.cpp
+++ b/src/qml/types/qquickworkerscript.cpp
@@ -37,9 +37,12 @@
**
****************************************************************************/
+#include "qtqmlglobal_p.h"
#include "qquickworkerscript_p.h"
+#if QT_CONFIG(qml_list_model)
#include "qqmllistmodel_p.h"
#include "qqmllistmodelworkeragent_p.h"
+#endif
#include <private/qqmlengine_p.h>
#include <private/qqmlexpression_p.h>
@@ -65,6 +68,7 @@
#include <private/qv4functionobject_p.h>
#include <private/qv4script_p.h>
#include <private/qv4scopedvalue_p.h>
+#include <private/qv4jscall_p.h>
QT_BEGIN_NAMESPACE
@@ -135,49 +139,26 @@ public:
QQuickWorkerScriptEnginePrivate(QQmlEngine *eng);
- class WorkerEngine : public QV8Engine
- {
- public:
- WorkerEngine(QQuickWorkerScriptEnginePrivate *parent);
- ~WorkerEngine();
+ QQmlEngine *qmlengine;
+
+ QMutex m_lock;
+ QWaitCondition m_wait;
- void init();
+ struct WorkerScript : public QV8Engine {
+ WorkerScript(int id, QQuickWorkerScriptEnginePrivate *parent);
+ ~WorkerScript() override;
#if QT_CONFIG(qml_network)
QNetworkAccessManager *networkAccessManager() override;
#endif
- QQuickWorkerScriptEnginePrivate *p;
-
- QV4::ReturnedValue sendFunction(int id);
-
- QV4::PersistentValue onmessage;
- private:
- QV4::PersistentValue createsend;
+ QQuickWorkerScriptEnginePrivate *p = nullptr;
+ QUrl source;
+ QQuickWorkerScript *owner = nullptr;
#if QT_CONFIG(qml_network)
- QNetworkAccessManager *accessManager;
+ QScopedPointer<QNetworkAccessManager> accessManager;
#endif
- };
-
- WorkerEngine *workerEngine;
- static QQuickWorkerScriptEnginePrivate *get(QV8Engine *e) {
- return static_cast<WorkerEngine *>(e)->p;
- }
-
- QQmlEngine *qmlengine;
-
- QMutex m_lock;
- QWaitCondition m_wait;
-
- struct WorkerScript {
- WorkerScript();
- ~WorkerScript();
-
- int id;
- QUrl source;
- bool initialized;
- QQuickWorkerScript *owner;
- QV4::PersistentValue qmlContext;
+ int id = -1;
};
QHash<int, WorkerScript *> workers;
@@ -185,7 +166,7 @@ public:
int m_nextId;
- static void method_sendMessage(const QV4::BuiltinFunction *, QV4::Scope &scope, QV4::CallData *callData);
+ static QV4::ReturnedValue method_sendMessage(const QV4::FunctionObject *, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
signals:
void stopThread();
@@ -199,128 +180,25 @@ private:
void reportScriptException(WorkerScript *, const QQmlError &error);
};
-QQuickWorkerScriptEnginePrivate::WorkerEngine::WorkerEngine(QQuickWorkerScriptEnginePrivate *parent)
-: QV8Engine(0), p(parent)
-#if QT_CONFIG(qml_network)
-, accessManager(0)
-#endif
-{
- m_v4Engine->v8Engine = this;
-}
-
-QQuickWorkerScriptEnginePrivate::WorkerEngine::~WorkerEngine()
-{
-#if QT_CONFIG(qml_network)
- delete accessManager;
-#endif
-}
-
-void QQuickWorkerScriptEnginePrivate::WorkerEngine::init()
-{
- initQmlGlobalObject();
-#define CALL_ONMESSAGE_SCRIPT \
- "(function(object, message) { "\
- "var isfunction = false; "\
- "try { "\
- "isfunction = object.WorkerScript.onMessage instanceof Function; "\
- "} catch (e) {}" \
- "if (isfunction) "\
- "object.WorkerScript.onMessage(message); "\
- "})"
-
-#define SEND_MESSAGE_CREATE_SCRIPT \
- "(function(method, engine) { "\
- "return (function(id) { "\
- "return (function(message) { "\
- "if (arguments.length) method(engine, id, message); "\
- "}); "\
- "}); "\
- "})"
-
- QV4::Scope scope(m_v4Engine);
- QV4::ExecutionContext *globalContext = scope.engine->rootContext();
- onmessage.set(scope.engine, QV4::Script(globalContext, QString::fromUtf8(CALL_ONMESSAGE_SCRIPT)).run()); // do not use QStringLiteral here, MSVC2012 cannot apply this cleanly to the macro
- Q_ASSERT(!scope.engine->hasException);
- QV4::Script createsendscript(globalContext, QString::fromUtf8(SEND_MESSAGE_CREATE_SCRIPT)); // do not use QStringLiteral here, MSVC2012 cannot apply this cleanly to the macro
- QV4::ScopedFunctionObject createsendconstructor(scope, createsendscript.run());
- Q_ASSERT(!scope.engine->hasException);
- QV4::ScopedString name(scope, m_v4Engine->newString(QStringLiteral("sendMessage")));
- QV4::ScopedValue function(scope, QV4::BuiltinFunction::create(globalContext, name,
- QQuickWorkerScriptEnginePrivate::method_sendMessage));
- QV4::ScopedCallData callData(scope, 1);
- callData->args[0] = function;
- callData->thisObject = global();
- createsendconstructor->call(scope, callData);
- createsend.set(scope.engine, scope.result.asReturnedValue());
-}
-
-// Requires handle and context scope
-QV4::ReturnedValue QQuickWorkerScriptEnginePrivate::WorkerEngine::sendFunction(int id)
-{
- QV4::ExecutionEngine *v4 = createsend.engine();
- if (!v4)
- return QV4::Encode::undefined();
-
- QV4::Scope scope(v4);
- QV4::ScopedFunctionObject f(scope, createsend.value());
-
- QV4::ScopedCallData callData(scope, 1);
- callData->args[0] = QV4::Primitive::fromInt32(id);
- callData->thisObject = global();
- f->call(scope, callData);
- if (scope.hasException())
- scope.result = scope.engine->catchException();
- return scope.result.asReturnedValue();
-}
-
-#if QT_CONFIG(qml_network)
-QNetworkAccessManager *QQuickWorkerScriptEnginePrivate::WorkerEngine::networkAccessManager()
-{
- if (!accessManager) {
- if (p->qmlengine && p->qmlengine->networkAccessManagerFactory()) {
- accessManager = p->qmlengine->networkAccessManagerFactory()->create(p);
- } else {
- accessManager = new QNetworkAccessManager(p);
- }
- }
- return accessManager;
-}
-#endif
-
QQuickWorkerScriptEnginePrivate::QQuickWorkerScriptEnginePrivate(QQmlEngine *engine)
-: workerEngine(0), qmlengine(engine), m_nextId(0)
+: qmlengine(engine), m_nextId(0)
{
}
-void QQuickWorkerScriptEnginePrivate::method_sendMessage(const QV4::BuiltinFunction *, QV4::Scope &scope, QV4::CallData *callData)
+QV4::ReturnedValue QQuickWorkerScriptEnginePrivate::method_sendMessage(const QV4::FunctionObject *b,
+ const QV4::Value *, const QV4::Value *argv, int argc)
{
- WorkerEngine *engine = (WorkerEngine*)scope.engine->v8Engine;
-
- int id = callData->argc > 1 ? callData->args[1].toInt32() : 0;
+ QV4::Scope scope(b);
+ WorkerScript *script = static_cast<WorkerScript *>(scope.engine->v8Engine);
- QV4::ScopedValue v(scope, callData->argument(2));
+ QV4::ScopedValue v(scope, argc > 0 ? argv[0] : QV4::Value::undefinedValue());
QByteArray data = QV4::Serialize::serialize(v, scope.engine);
- QMutexLocker locker(&engine->p->m_lock);
- WorkerScript *script = engine->p->workers.value(id);
+ QMutexLocker locker(&script->p->m_lock);
if (script && script->owner)
QCoreApplication::postEvent(script->owner, new WorkerDataEvent(0, data));
- scope.result = QV4::Encode::undefined();
-}
-
-QV4::ReturnedValue QQuickWorkerScriptEnginePrivate::getWorker(WorkerScript *script)
-{
- if (!script->initialized) {
- script->initialized = true;
-
- QV4::ExecutionEngine *v4 = QV8Engine::getV4(workerEngine);
- QV4::Scope scope(v4);
- QV4::ScopedValue v(scope, workerEngine->sendFunction(script->id));
- script->qmlContext.set(v4, QV4::QmlContext::createWorkerContext(v4->rootContext(), script->source, v));
- }
-
- return script->qmlContext.value();
+ return QV4::Encode::undefined();
}
bool QQuickWorkerScriptEnginePrivate::event(QEvent *event)
@@ -337,6 +215,7 @@ bool QQuickWorkerScriptEnginePrivate::event(QEvent *event)
emit stopThread();
return true;
} else if (event->type() == (QEvent::Type)WorkerRemoveEvent::WorkerRemove) {
+ QMutexLocker locker(&m_lock);
WorkerRemoveEvent *workerEvent = static_cast<WorkerRemoveEvent *>(event);
QHash<int, WorkerScript *>::iterator itr = workers.find(workerEvent->workerId());
if (itr != workers.end()) {
@@ -355,19 +234,23 @@ void QQuickWorkerScriptEnginePrivate::processMessage(int id, const QByteArray &d
if (!script)
return;
- QV4::ExecutionEngine *v4 = QV8Engine::getV4(workerEngine);
+ QV4::ExecutionEngine *v4 = QV8Engine::getV4(script);
QV4::Scope scope(v4);
- QV4::ScopedFunctionObject f(scope, workerEngine->onmessage.value());
+ QV4::ScopedString v(scope);
+ QV4::ScopedObject worker(scope, v4->globalObject->get((v = v4->newString(QStringLiteral("WorkerScript")))));
+ QV4::ScopedFunctionObject onmessage(scope);
+ if (worker)
+ onmessage = worker->get((v = v4->newString(QStringLiteral("onMessage"))));
+
+ if (!onmessage)
+ return;
QV4::ScopedValue value(scope, QV4::Serialize::deserialize(data, v4));
- QV4::Scoped<QV4::QmlContext> qmlContext(scope, script->qmlContext.value());
- Q_ASSERT(!!qmlContext);
-
- QV4::ScopedCallData callData(scope, 2);
- callData->thisObject = workerEngine->global();
- callData->args[0] = qmlContext->d()->qml; // ###
- callData->args[1] = value;
- f->call(scope, callData);
+
+ QV4::JSCallData jsCallData(scope, 1);
+ *jsCallData->thisObject = v4->global();
+ jsCallData->args[0] = value;
+ onmessage->call(jsCallData);
if (scope.hasException()) {
QQmlError error = scope.engine->catchExceptionAsQmlError();
reportScriptException(script, error);
@@ -381,39 +264,37 @@ void QQuickWorkerScriptEnginePrivate::processLoad(int id, const QUrl &url)
QString fileName = QQmlFile::urlToLocalFileOrQrc(url);
- QV4::ExecutionEngine *v4 = QV8Engine::getV4(workerEngine);
- QV4::Scope scope(v4);
- QScopedPointer<QV4::Script> program;
-
WorkerScript *script = workers.value(id);
if (!script)
return;
- script->source = url;
- QV4::Scoped<QV4::QmlContext> qmlContext(scope, getWorker(script));
- Q_ASSERT(!!qmlContext);
+ QV4::ExecutionEngine *v4 = QV8Engine::getV4(script);
+
+ script->source = url;
- if (const QQmlPrivate::CachedQmlUnit *cachedUnit = QQmlMetaType::findCachedCompilationUnit(url)) {
- QV4::CompiledData::CompilationUnit *jsUnit = cachedUnit->createCompilationUnit();
- program.reset(new QV4::Script(v4, qmlContext, jsUnit));
+ if (fileName.endsWith(QLatin1String(".mjs"))) {
+ auto moduleUnit = v4->loadModule(url);
+ if (moduleUnit) {
+ if (moduleUnit->instantiate(v4))
+ moduleUnit->evaluate();
+ } else {
+ v4->throwError(QStringLiteral("Could not load module file"));
+ }
} else {
- QFile f(fileName);
- if (!f.open(QIODevice::ReadOnly)) {
- qWarning().nospace() << "WorkerScript: Cannot find source file " << url.toString();
+ QString error;
+ QV4::Scope scope(v4);
+ QScopedPointer<QV4::Script> program;
+ program.reset(QV4::Script::createFromFileOrCache(v4, /*qmlContext*/nullptr, fileName, url, &error));
+ if (program.isNull()) {
+ if (!error.isEmpty())
+ qWarning().nospace() << error;
return;
}
- QByteArray data = f.readAll();
- QString sourceCode = QString::fromUtf8(data);
- QmlIR::Document::removeScriptPragmas(sourceCode);
-
- program.reset(new QV4::Script(v4, qmlContext, sourceCode, url.toString()));
- program->parse();
+ if (!v4->hasException)
+ program->run();
}
- if (!v4->hasException)
- program->run();
-
if (v4->hasException) {
QQmlError error = v4->catchExceptionAsQmlError();
reportScriptException(script, error);
@@ -423,9 +304,7 @@ void QQuickWorkerScriptEnginePrivate::processLoad(int id, const QUrl &url)
void QQuickWorkerScriptEnginePrivate::reportScriptException(WorkerScript *script,
const QQmlError &error)
{
- QQuickWorkerScriptEnginePrivate *p = QQuickWorkerScriptEnginePrivate::get(workerEngine);
-
- QMutexLocker locker(&p->m_lock);
+ QMutexLocker locker(&script->p->m_lock);
if (script->owner)
QCoreApplication::postEvent(script->owner, new WorkerErrorEvent(error));
}
@@ -514,21 +393,47 @@ QQuickWorkerScriptEngine::~QQuickWorkerScriptEngine()
d->deleteLater();
}
-QQuickWorkerScriptEnginePrivate::WorkerScript::WorkerScript()
-: id(-1), initialized(false), owner(0)
+QQuickWorkerScriptEnginePrivate::WorkerScript::WorkerScript(int id, QQuickWorkerScriptEnginePrivate *parent)
+ : QV8Engine(new QV4::ExecutionEngine)
+ , p(parent)
+ , id(id)
{
+ m_v4Engine->v8Engine = this;
+
+ initQmlGlobalObject();
+
+ QV4::Scope scope(m_v4Engine);
+ QV4::ScopedObject api(scope, scope.engine->newObject());
+ QV4::ScopedString name(scope, m_v4Engine->newString(QStringLiteral("sendMessage")));
+ QV4::ScopedValue sendMessage(scope, QV4::FunctionObject::createBuiltinFunction(m_v4Engine, name, method_sendMessage, 1));
+ api->put(QV4::ScopedString(scope, scope.engine->newString(QStringLiteral("sendMessage"))), sendMessage);
+ m_v4Engine->globalObject->put(QV4::ScopedString(scope, scope.engine->newString(QStringLiteral("WorkerScript"))), api);
}
QQuickWorkerScriptEnginePrivate::WorkerScript::~WorkerScript()
{
+ delete m_v4Engine;
+}
+
+#if QT_CONFIG(qml_network)
+QNetworkAccessManager *QQuickWorkerScriptEnginePrivate::WorkerScript::networkAccessManager()
+{
+ if (!accessManager) {
+ if (p->qmlengine && p->qmlengine->networkAccessManagerFactory()) {
+ accessManager.reset(p->qmlengine->networkAccessManagerFactory()->create(p));
+ } else {
+ accessManager.reset(new QNetworkAccessManager(p));
+ }
+ }
+ return accessManager.data();
}
+#endif
int QQuickWorkerScriptEngine::registerWorkerScript(QQuickWorkerScript *owner)
{
typedef QQuickWorkerScriptEnginePrivate::WorkerScript WorkerScript;
- WorkerScript *script = new WorkerScript;
+ WorkerScript *script = new WorkerScript(d->m_nextId++, d);
- script->id = d->m_nextId++;
script->owner = owner;
d->m_lock.lock();
@@ -542,7 +447,7 @@ void QQuickWorkerScriptEngine::removeWorkerScript(int id)
{
QQuickWorkerScriptEnginePrivate::WorkerScript* script = d->workers.value(id);
if (script) {
- script->owner = 0;
+ script->owner = nullptr;
QCoreApplication::postEvent(d, new WorkerRemoveEvent(id));
}
}
@@ -561,9 +466,6 @@ void QQuickWorkerScriptEngine::run()
{
d->m_lock.lock();
- d->workerEngine = new QQuickWorkerScriptEnginePrivate::WorkerEngine(d);
- d->workerEngine->init();
-
d->m_wait.wakeAll();
d->m_lock.unlock();
@@ -572,8 +474,6 @@ void QQuickWorkerScriptEngine::run()
qDeleteAll(d->workers);
d->workers.clear();
-
- delete d->workerEngine; d->workerEngine = 0;
}
@@ -581,8 +481,8 @@ void QQuickWorkerScriptEngine::run()
\qmltype WorkerScript
\instantiates QQuickWorkerScript
\ingroup qtquick-threading
- \inqmlmodule QtQuick
- \brief Enables the use of threads in a Qt Quick application
+ \inqmlmodule QtQml
+ \brief Enables the use of threads in a Qt Quick application.
Use WorkerScript to run operations in a new thread.
This is useful for running operations in the background so
@@ -595,22 +495,32 @@ void QQuickWorkerScriptEngine::run()
\snippet qml/workerscript/workerscript.qml 0
- The above worker script specifies a JavaScript file, "script.js", that handles
- the operations to be performed in the new thread. Here is \c script.js:
+ The above worker script specifies a JavaScript file, "script.mjs", that handles
+ the operations to be performed in the new thread. Here is \c script.mjs:
- \quotefile qml/workerscript/script.js
+ \quotefile qml/workerscript/script.mjs
When the user clicks anywhere within the rectangle, \c sendMessage() is
called, triggering the \tt WorkerScript.onMessage() handler in
- \tt script.js. This in turn sends a reply message that is then received
+ \tt script.mjs. This in turn sends a reply message that is then received
by the \tt onMessage() handler of \tt myWorker.
+ The example uses a script that is an ECMAScript module, because it has the ".mjs" extension.
+ It can use import statements to access functionality from other modules and it is run in JavaScript
+ strict mode.
+
+ If a worker script has the extension ".js" instead, then it is considered to contain plain JavaScript
+ statements and it is run in non-strict mode.
+
+ \note Each WorkerScript element will instantiate a separate JavaScript engine to ensure perfect
+ isolation and thread-safety. If the impact of that results in a memory consumption that is too
+ high for your environment, then consider sharing a WorkerScript element.
\section3 Restrictions
Since the \c WorkerScript.onMessage() function is run in a separate thread, the
JavaScript file is evaluated in a context separate from the main QML engine. This means
- that unlike an ordinary JavaScript file that is imported into QML, the \c script.js
+ that unlike an ordinary JavaScript file that is imported into QML, the \c script.mjs
in the above example cannot access the properties, methods or other attributes
of the QML item, nor can it access any context properties set on the QML object
through QQmlContext.
@@ -618,13 +528,14 @@ void QQuickWorkerScriptEngine::run()
Additionally, there are restrictions on the types of values that can be passed to and
from the worker script. See the sendMessage() documentation for details.
- Worker script can not use \l {qtqml-javascript-imports.html}{.import} syntax.
+ Worker scripts that are plain JavaScript sources can not use \l {qtqml-javascript-imports.html}{.import} syntax.
+ Scripts that are ECMAScript modules can freely use import and export statements.
\sa {Qt Quick Examples - Threading},
{Threaded ListModel Example}
*/
QQuickWorkerScript::QQuickWorkerScript(QObject *parent)
-: QObject(parent), m_engine(0), m_scriptId(-1), m_componentComplete(true)
+: QObject(parent), m_engine(nullptr), m_scriptId(-1), m_componentComplete(true)
{
}
@@ -638,6 +549,10 @@ QQuickWorkerScript::~QQuickWorkerScript()
This holds the url of the JavaScript file that implements the
\tt WorkerScript.onMessage() handler for threaded operations.
+
+ If the file name component of the url ends with ".mjs", then the script
+ is parsed as an ECMAScript module and run in strict mode. Otherwise it is considered to be
+ plain script.
*/
QUrl QQuickWorkerScript::source() const
{
@@ -685,7 +600,7 @@ void QQuickWorkerScript::sendMessage(QQmlV4Function *args)
}
QV4::Scope scope(args->v4engine());
- QV4::ScopedValue argument(scope, QV4::Primitive::undefinedValue());
+ QV4::ScopedValue argument(scope, QV4::Value::undefinedValue());
if (args->length() != 0)
argument = (*args)[0];
@@ -704,7 +619,7 @@ QQuickWorkerScriptEngine *QQuickWorkerScript::engine()
QQmlEngine *engine = qmlEngine(this);
if (!engine) {
qWarning("QQuickWorkerScript: engine() called without qmlEngine() set");
- return 0;
+ return nullptr;
}
m_engine = QQmlEnginePrivate::get(engine)->getWorkerScriptEngine();
@@ -715,7 +630,7 @@ QQuickWorkerScriptEngine *QQuickWorkerScript::engine()
return m_engine;
}
- return 0;
+ return nullptr;
}
void QQuickWorkerScript::componentComplete()
@@ -739,8 +654,7 @@ bool QQuickWorkerScript::event(QEvent *event)
QQmlEngine *engine = qmlEngine(this);
if (engine) {
WorkerDataEvent *workerEvent = static_cast<WorkerDataEvent *>(event);
- QV8Engine *v8engine = QQmlEnginePrivate::get(engine)->v8engine();
- QV4::Scope scope(QV8Engine::getV4(v8engine));
+ QV4::Scope scope(engine->handle());
QV4::ScopedValue value(scope, QV4::Serialize::deserialize(workerEvent->data(), scope.engine));
emit message(QQmlV4Handle(value));
}
diff --git a/src/qml/types/qquickworkerscript_p.h b/src/qml/types/qquickworkerscript_p.h
index dce3acc3e1..1a8d2ab076 100644
--- a/src/qml/types/qquickworkerscript_p.h
+++ b/src/qml/types/qquickworkerscript_p.h
@@ -51,7 +51,7 @@
// We mean it.
//
-#include "qqml.h"
+#include <qqml.h>
#include <QtQml/qqmlparserstatus.h>
#include <QtCore/qthread.h>
@@ -67,7 +67,7 @@ class QQuickWorkerScriptEngine : public QThread
{
Q_OBJECT
public:
- QQuickWorkerScriptEngine(QQmlEngine *parent = 0);
+ QQuickWorkerScriptEngine(QQmlEngine *parent = nullptr);
~QQuickWorkerScriptEngine();
int registerWorkerScript(QQuickWorkerScript *);
@@ -91,7 +91,7 @@ class Q_AUTOTEST_EXPORT QQuickWorkerScript : public QObject, public QQmlParserSt
Q_INTERFACES(QQmlParserStatus)
public:
- QQuickWorkerScript(QObject *parent = 0);
+ QQuickWorkerScript(QObject *parent = nullptr);
~QQuickWorkerScript();
QUrl source() const;
diff --git a/src/qml/types/types.pri b/src/qml/types/types.pri
index e85ab5982b..1765beb09e 100644
--- a/src/qml/types/types.pri
+++ b/src/qml/types/types.pri
@@ -1,33 +1,56 @@
SOURCES += \
$$PWD/qqmlbind.cpp \
$$PWD/qqmlconnections.cpp \
- $$PWD/qqmldelegatemodel.cpp \
- $$PWD/qqmllistmodel.cpp \
- $$PWD/qqmllistmodelworkeragent.cpp \
$$PWD/qqmlmodelsmodule.cpp \
$$PWD/qqmlmodelindexvaluetype.cpp \
$$PWD/qqmlobjectmodel.cpp \
$$PWD/qquickpackage.cpp \
- $$PWD/qquickworkerscript.cpp \
- $$PWD/qqmlinstantiator.cpp
+ $$PWD/qqmlinstantiator.cpp \
+ $$PWD/qqmltableinstancemodel.cpp \
+ $$PWD/qqmltablemodel.cpp
HEADERS += \
$$PWD/qqmlbind_p.h \
$$PWD/qqmlconnections_p.h \
- $$PWD/qqmldelegatemodel_p.h \
- $$PWD/qqmldelegatemodel_p_p.h \
- $$PWD/qqmllistmodel_p.h \
- $$PWD/qqmllistmodel_p_p.h \
- $$PWD/qqmllistmodelworkeragent_p.h \
$$PWD/qqmlmodelsmodule_p.h \
$$PWD/qqmlmodelindexvaluetype_p.h \
$$PWD/qqmlobjectmodel_p.h \
$$PWD/qquickpackage_p.h \
- $$PWD/qquickworkerscript_p.h \
$$PWD/qqmlinstantiator_p.h \
- $$PWD/qqmlinstantiator_p_p.h
+ $$PWD/qqmlinstantiator_p_p.h \
+ $$PWD/qqmltableinstancemodel_p.h \
+ $$PWD/qqmltablemodel_p.h
-qtConfig(animation) {
+qtConfig(qml-worker-script) {
+ SOURCES += \
+ $$PWD/qquickworkerscript.cpp
+ HEADERS += \
+ $$PWD/qquickworkerscript_p.h
+}
+
+qtConfig(qml-list-model) {
+ SOURCES += \
+ $$PWD/qqmllistmodel.cpp \
+ $$PWD/qqmllistmodelworkeragent.cpp
+
+ HEADERS += \
+ $$PWD/qqmllistmodel_p.h \
+ $$PWD/qqmllistmodel_p_p.h \
+ $$PWD/qqmllistmodelworkeragent_p.h
+}
+
+qtConfig(qml-delegate-model) {
+ SOURCES += \
+ $$PWD/qqmldelegatemodel.cpp \
+ $$PWD/qqmldelegatecomponent.cpp
+
+ HEADERS += \
+ $$PWD/qqmldelegatemodel_p.h \
+ $$PWD/qqmldelegatemodel_p_p.h \
+ $$PWD/qqmldelegatecomponent_p.h
+}
+
+qtConfig(qml-animation) {
SOURCES += \
$$PWD/qqmltimer.cpp