diff options
author | Alan Alpert <aalpert@rim.com> | 2013-01-23 15:17:27 -0800 |
---|---|---|
committer | The Qt Project <gerrit-noreply@qt-project.org> | 2013-03-14 22:44:11 +0100 |
commit | 78b3aed3264439cd95ca23a47e53e15b769a58ae (patch) | |
tree | 4228a117f0b48b8e62fc03c03ca7e3bf82efaff3 /src/qml/types | |
parent | 2050601125eeb72950a7d718b5136d16c898f8e6 (diff) |
Move QML types to types folder
The QtQml module has gain a bunch of QML types cluttering up the qml
folder. Moving them to types organizes them a bit better.
Change-Id: I570884c00f4abc48f4f1aea048bf002bc70223f3
Reviewed-by: Gabriel de Dietrich <gabriel.dedietrich@digia.com>
Reviewed-by: Lars Knoll <lars.knoll@digia.com>
Diffstat (limited to 'src/qml/types')
23 files changed, 10405 insertions, 0 deletions
diff --git a/src/qml/types/qqmlbind.cpp b/src/qml/types/qqmlbind.cpp new file mode 100644 index 0000000000..fcb3079891 --- /dev/null +++ b/src/qml/types/qqmlbind.cpp @@ -0,0 +1,310 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** 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 Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/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 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qqmlbind_p.h" + +#include <private/qqmlnullablevalue_p_p.h> +#include <private/qqmlproperty_p.h> +#include <private/qqmlbinding_p.h> +#include <private/qqmlguard_p.h> + +#include <qqmlengine.h> +#include <qqmlcontext.h> +#include <qqmlproperty.h> +#include <qqmlinfo.h> + +#include <QtCore/qfile.h> +#include <QtCore/qdebug.h> + +#include <private/qobject_p.h> + +QT_BEGIN_NAMESPACE + +class QQmlBindPrivate : public QObjectPrivate +{ +public: + QQmlBindPrivate() : componentComplete(true), obj(0), prevBind(0) {} + ~QQmlBindPrivate() { if (prevBind) prevBind->destroy(); } + + QQmlNullableValue<bool> when; + bool componentComplete; + QQmlGuard<QObject> obj; + QString propName; + QQmlNullableValue<QVariant> value; + QQmlProperty prop; + QQmlAbstractBinding *prevBind; +}; + + +/*! + \qmltype Binding + \instantiates QQmlBind + \inqmlmodule QtQml 2 + \ingroup qtquick-interceptors + \brief Enables the arbitrary creation of property bindings + + \section1 Binding to an inaccessible property + + Sometimes it is necessary to bind to a property of an object that wasn't + directly instantiated by QML - generally a property of a class exported + to QML by C++. In these cases, regular property binding doesn't work. Binding + allows you to bind any value to any property. + + For example, imagine a C++ application that maps an "app.enteredText" + property into QML. You could use Binding to update the enteredText property + like this. + \code + TextEdit { id: myTextField; text: "Please type here..." } + Binding { target: app; property: "enteredText"; value: myTextField.text } + \endcode + Whenever the text in the TextEdit is updated, the C++ property will be + updated also. + + \section1 "Single-branch" conditional binding + + In some circumstances you may want to control the value of a property + only when a certain condition is true (and relinquish control in all + other circumstances). This often isn't possible to accomplish with a direct + binding, as you need to supply values for all possible branches. + + \code + // produces warning: "Unable to assign [undefined] to double value" + value: if (mouse.pressed) mouse.mouseX + \endcode + + The above example will produce a warning whenever we release the mouse, as the value + of the binding is undefined when the mouse isn't pressed. We can use the Binding + type to rewrite the above code and avoid the warning. + + \qml + Binding on value { + when: mouse.pressed + value: mouse.mouseX + } + \endqml + + The Binding type will also restore any previously set direct bindings on + the property. In that sense, it functions much like a simplified State. + + \qml + // this is equivalent to the above Binding + State { + name: "pressed" + when: mouse.pressed + PropertyChanges { + target: obj + value: mouse.mouseX + } + } + \endqml + + If the binding target or binding property is changed, the bound value is + immediately pushed onto the new target. + + \sa QtQml +*/ +QQmlBind::QQmlBind(QObject *parent) + : QObject(*(new QQmlBindPrivate), parent) +{ +} + +QQmlBind::~QQmlBind() +{ +} + +/*! + \qmlproperty bool QtQml2::Binding::when + + This property holds when the binding is active. + This should be set to an expression that evaluates to true when you want the binding to be active. + + \code + Binding { + target: contactName; property: 'text' + value: name; when: list.ListView.isCurrentItem + } + \endcode + + When the binding becomes inactive again, any direct bindings that were previously + set on the property will be restored. +*/ +bool QQmlBind::when() const +{ + Q_D(const QQmlBind); + return d->when; +} + +void QQmlBind::setWhen(bool v) +{ + Q_D(QQmlBind); + if (!d->when.isNull && d->when == v) + return; + + d->when = v; + eval(); +} + +/*! + \qmlproperty Object QtQml2::Binding::target + + The object to be updated. +*/ +QObject *QQmlBind::object() +{ + Q_D(const QQmlBind); + return d->obj; +} + +void QQmlBind::setObject(QObject *obj) +{ + Q_D(QQmlBind); + if (d->obj && d->when.isValid() && d->when) { + /* if we switch the object at runtime, we need to restore the + previous binding on the old object before continuing */ + d->when = false; + eval(); + d->when = true; + } + d->obj = obj; + if (d->componentComplete) + d->prop = QQmlProperty(d->obj, d->propName); + eval(); +} + +/*! + \qmlproperty string QtQml2::Binding::property + + The property to be updated. +*/ +QString QQmlBind::property() const +{ + Q_D(const QQmlBind); + return d->propName; +} + +void QQmlBind::setProperty(const QString &p) +{ + Q_D(QQmlBind); + if (!d->propName.isEmpty() && d->when.isValid() && d->when) { + /* if we switch the property name at runtime, we need to restore the + previous binding on the old object before continuing */ + d->when = false; + eval(); + d->when = true; + } + d->propName = p; + if (d->componentComplete) + d->prop = QQmlProperty(d->obj, d->propName); + eval(); +} + +/*! + \qmlproperty any QtQml2::Binding::value + + The value to be set on the target object and property. This can be a + constant (which isn't very useful), or a bound expression. +*/ +QVariant QQmlBind::value() const +{ + Q_D(const QQmlBind); + return d->value.value; +} + +void QQmlBind::setValue(const QVariant &v) +{ + Q_D(QQmlBind); + d->value = v; + eval(); +} + +void QQmlBind::setTarget(const QQmlProperty &p) +{ + Q_D(QQmlBind); + d->prop = p; +} + +void QQmlBind::classBegin() +{ + Q_D(QQmlBind); + d->componentComplete = false; +} + +void QQmlBind::componentComplete() +{ + Q_D(QQmlBind); + d->componentComplete = true; + if (!d->prop.isValid()) + d->prop = QQmlProperty(d->obj, d->propName); + eval(); +} + +void QQmlBind::eval() +{ + Q_D(QQmlBind); + if (!d->prop.isValid() || d->value.isNull || !d->componentComplete) + return; + + if (d->when.isValid()) { + if (!d->when) { + //restore any previous binding + if (d->prevBind) { + QQmlAbstractBinding *tmp = d->prevBind; + d->prevBind = 0; + tmp = QQmlPropertyPrivate::setBinding(d->prop, tmp); + if (tmp) //should this ever be true? + tmp->destroy(); + } + return; + } + + //save any set binding for restoration + QQmlAbstractBinding *tmp; + tmp = QQmlPropertyPrivate::setBinding(d->prop, 0); + if (tmp && d->prevBind) + tmp->destroy(); + else if (!d->prevBind) + d->prevBind = tmp; + } + + d->prop.write(d->value.value); +} + +QT_END_NAMESPACE diff --git a/src/qml/types/qqmlbind_p.h b/src/qml/types/qqmlbind_p.h new file mode 100644 index 0000000000..1e29c257f0 --- /dev/null +++ b/src/qml/types/qqmlbind_p.h @@ -0,0 +1,92 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** 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 Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/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 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQMLBIND_H +#define QQMLBIND_H + +#include <qqml.h> + +#include <QtCore/qobject.h> + +QT_BEGIN_NAMESPACE + +class QQmlBindPrivate; +class Q_AUTOTEST_EXPORT QQmlBind : public QObject, public QQmlPropertyValueSource, public QQmlParserStatus +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QQmlBind) + Q_INTERFACES(QQmlParserStatus) + Q_INTERFACES(QQmlPropertyValueSource) + Q_PROPERTY(QObject *target READ object WRITE setObject) + Q_PROPERTY(QString property READ property WRITE setProperty) + Q_PROPERTY(QVariant value READ value WRITE setValue) + Q_PROPERTY(bool when READ when WRITE setWhen) + +public: + QQmlBind(QObject *parent=0); + ~QQmlBind(); + + bool when() const; + void setWhen(bool); + + QObject *object(); + void setObject(QObject *); + + QString property() const; + void setProperty(const QString &); + + QVariant value() const; + void setValue(const QVariant &); + +protected: + virtual void setTarget(const QQmlProperty &); + virtual void classBegin(); + virtual void componentComplete(); + +private: + void eval(); +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQmlBind) + +#endif diff --git a/src/qml/types/qqmlconnections.cpp b/src/qml/types/qqmlconnections.cpp new file mode 100644 index 0000000000..286933e557 --- /dev/null +++ b/src/qml/types/qqmlconnections.cpp @@ -0,0 +1,321 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** 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 Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/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 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qqmlconnections_p.h" + +#include <private/qqmlexpression_p.h> +#include <private/qqmlproperty_p.h> +#include <private/qqmlboundsignal_p.h> +#include <qqmlcontext.h> +#include <private/qqmlcontext_p.h> +#include <qqmlinfo.h> + +#include <QtCore/qdebug.h> +#include <QtCore/qstringlist.h> + +#include <private/qobject_p.h> + +QT_BEGIN_NAMESPACE + +class QQmlConnectionsPrivate : public QObjectPrivate +{ +public: + QQmlConnectionsPrivate() : target(0), targetSet(false), ignoreUnknownSignals(false), componentcomplete(true) {} + + QList<QQmlBoundSignal*> boundsignals; + QObject *target; + + bool targetSet; + bool ignoreUnknownSignals; + bool componentcomplete; + + QByteArray data; +}; + +/*! + \qmltype Connections + \instantiates QQmlConnections + \inqmlmodule QtQml 2 + \ingroup qtquick-interceptors + \brief Describes generalized connections to signals + + A Connections object creates a connection to a QML signal. + + When connecting to signals in QML, the usual way is to create an + "on<Signal>" handler that reacts when a signal is received, like this: + + \qml + MouseArea { + onClicked: { foo(parameters) } + } + \endqml + + However, it is not possible to connect to a signal in this way in some + cases, such as when: + + \list + \li Multiple connections to the same signal are required + \li Creating connections outside the scope of the signal sender + \li Connecting to targets not defined in QML + \endlist + + When any of these are needed, the Connections type can be used instead. + + For example, the above code can be changed to use a Connections object, + like this: + + \qml + MouseArea { + Connections { + onClicked: foo(parameters) + } + } + \endqml + + More generally, the Connections object can be a child of some object other than + the sender of the signal: + + \qml + MouseArea { + id: area + } + // ... + \endqml + \qml + Connections { + target: area + onClicked: foo(parameters) + } + \endqml + + \sa QtQml +*/ +QQmlConnections::QQmlConnections(QObject *parent) : + QObject(*(new QQmlConnectionsPrivate), parent) +{ +} + +QQmlConnections::~QQmlConnections() +{ +} + +/*! + \qmlproperty Object QtQml2::Connections::target + This property holds the object that sends the signal. + + If this property is not set, the \c target defaults to the parent of the Connection. + + If set to null, no connection is made and any signal handlers are ignored + until the target is not null. +*/ +QObject *QQmlConnections::target() const +{ + Q_D(const QQmlConnections); + return d->targetSet ? d->target : parent(); +} + +class QQmlBoundSignalDeleter : public QObject +{ +public: + QQmlBoundSignalDeleter(QQmlBoundSignal *signal) : m_signal(signal) { m_signal->removeFromObject(); } + ~QQmlBoundSignalDeleter() { delete m_signal; } + +private: + QQmlBoundSignal *m_signal; +}; + +void QQmlConnections::setTarget(QObject *obj) +{ + Q_D(QQmlConnections); + d->targetSet = true; // even if setting to 0, it is *set* + if (d->target == obj) + return; + foreach (QQmlBoundSignal *s, d->boundsignals) { + // It is possible that target is being changed due to one of our signal + // handlers -> use deleteLater(). + if (s->isEvaluating()) + (new QQmlBoundSignalDeleter(s))->deleteLater(); + else + delete s; + } + d->boundsignals.clear(); + d->target = obj; + connectSignals(); + emit targetChanged(); +} + +/*! + \qmlproperty bool QtQml2::Connections::ignoreUnknownSignals + + Normally, a connection to a non-existent signal produces runtime errors. + + If this property is set to \c true, such errors are ignored. + This is useful if you intend to connect to different types of objects, handling + a different set of signals for each object. +*/ +bool QQmlConnections::ignoreUnknownSignals() const +{ + Q_D(const QQmlConnections); + return d->ignoreUnknownSignals; +} + +void QQmlConnections::setIgnoreUnknownSignals(bool ignore) +{ + Q_D(QQmlConnections); + d->ignoreUnknownSignals = ignore; +} + + + +QByteArray +QQmlConnectionsParser::compile(const QList<QQmlCustomParserProperty> &props) +{ + QByteArray rv; + QDataStream ds(&rv, QIODevice::WriteOnly); + + for(int ii = 0; ii < props.count(); ++ii) + { + QString propName = props.at(ii).name(); + int propLine = props.at(ii).location().line; + int propColumn = props.at(ii).location().column; + + if (!propName.startsWith(QLatin1String("on")) || !propName.at(2).isUpper()) { + error(props.at(ii), QQmlConnections::tr("Cannot assign to non-existent property \"%1\"").arg(propName)); + return QByteArray(); + } + + QList<QVariant> values = props.at(ii).assignedValues(); + + for (int i = 0; i < values.count(); ++i) { + const QVariant &value = values.at(i); + + if (value.userType() == qMetaTypeId<QQmlCustomParserNode>()) { + error(props.at(ii), QQmlConnections::tr("Connections: nested objects not allowed")); + return QByteArray(); + } else if (value.userType() == qMetaTypeId<QQmlCustomParserProperty>()) { + error(props.at(ii), QQmlConnections::tr("Connections: syntax error")); + return QByteArray(); + } else { + QQmlScript::Variant v = qvariant_cast<QQmlScript::Variant>(value); + if (v.isScript()) { + ds << propName; + ds << rewriteSignalHandler(v, propName).toUtf8(); + ds << propLine; + ds << propColumn; + } else { + error(props.at(ii), QQmlConnections::tr("Connections: script expected")); + return QByteArray(); + } + } + } + } + + return rv; +} + +void QQmlConnectionsParser::setCustomData(QObject *object, + const QByteArray &data) +{ + QQmlConnectionsPrivate *p = + static_cast<QQmlConnectionsPrivate *>(QObjectPrivate::get(object)); + p->data = data; +} + + +void QQmlConnections::connectSignals() +{ + Q_D(QQmlConnections); + if (!d->componentcomplete || (d->targetSet && !target())) + return; + + QDataStream ds(d->data); + while (!ds.atEnd()) { + QString propName; + ds >> propName; + QByteArray script; + ds >> script; + int line; + ds >> line; + int column; + ds >> column; + + QQmlProperty prop(target(), propName); + if (prop.isValid() && (prop.type() & QQmlProperty::SignalProperty)) { + int signalIndex = QQmlPropertyPrivate::get(prop)->signalIndex(); + QQmlBoundSignal *signal = + new QQmlBoundSignal(target(), signalIndex, this, qmlEngine(this)); + + QString location; + QQmlContextData *ctxtdata = 0; + QQmlData *ddata = QQmlData::get(this); + if (ddata) { + ctxtdata = ddata->outerContext; + if (ctxtdata && !ctxtdata->url.isEmpty()) + location = ddata->outerContext->urlString; + } + + QQmlBoundSignalExpression *expression = ctxtdata ? + new QQmlBoundSignalExpression(target(), signalIndex, + ctxtdata, this, script, + true, location, line, column) : 0; + signal->takeExpression(expression); + d->boundsignals += signal; + } else { + if (!d->ignoreUnknownSignals) + qmlInfo(this) << tr("Cannot assign to non-existent property \"%1\"").arg(propName); + } + } +} + +void QQmlConnections::classBegin() +{ + Q_D(QQmlConnections); + d->componentcomplete=false; +} + +void QQmlConnections::componentComplete() +{ + Q_D(QQmlConnections); + d->componentcomplete=true; + connectSignals(); +} + +QT_END_NAMESPACE diff --git a/src/qml/types/qqmlconnections_p.h b/src/qml/types/qqmlconnections_p.h new file mode 100644 index 0000000000..9bc668e5f4 --- /dev/null +++ b/src/qml/types/qqmlconnections_p.h @@ -0,0 +1,96 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** 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 Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/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 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQMLCONNECTIONS_H +#define QQMLCONNECTIONS_H + +#include <qqml.h> +#include <private/qqmlcustomparser_p.h> + +#include <QtCore/qobject.h> +#include <QtCore/qstring.h> + +QT_BEGIN_NAMESPACE + +class QQmlBoundSignal; +class QQmlContext; +class QQmlConnectionsPrivate; +class Q_AUTOTEST_EXPORT QQmlConnections : public QObject, public QQmlParserStatus +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QQmlConnections) + + Q_INTERFACES(QQmlParserStatus) + Q_PROPERTY(QObject *target READ target WRITE setTarget NOTIFY targetChanged) + Q_PROPERTY(bool ignoreUnknownSignals READ ignoreUnknownSignals WRITE setIgnoreUnknownSignals) + +public: + QQmlConnections(QObject *parent=0); + ~QQmlConnections(); + + QObject *target() const; + void setTarget(QObject *); + + bool ignoreUnknownSignals() const; + void setIgnoreUnknownSignals(bool ignore); + +Q_SIGNALS: + void targetChanged(); + +private: + void connectSignals(); + void classBegin(); + void componentComplete(); +}; + +class QQmlConnectionsParser : public QQmlCustomParser +{ +public: + virtual QByteArray compile(const QList<QQmlCustomParserProperty> &); + virtual void setCustomData(QObject *, const QByteArray &); +}; + + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQmlConnections) + +#endif diff --git a/src/qml/types/qqmldelegatemodel.cpp b/src/qml/types/qqmldelegatemodel.cpp new file mode 100644 index 0000000000..efbd98bdbc --- /dev/null +++ b/src/qml/types/qqmldelegatemodel.cpp @@ -0,0 +1,3189 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** 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 Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/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 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qqmldelegatemodel_p_p.h" + +#include <QtQml/qqmlinfo.h> + +#include <private/qquickpackage_p.h> +#include <private/qmetaobjectbuilder_p.h> +#include <private/qqmladaptormodel_p.h> +#include <private/qqmlchangeset_p.h> +#include <private/qqmlengine_p.h> +#include <private/qqmlcomponent_p.h> +#include <private/qqmlincubator_p.h> +#include <private/qqmlcompiler_p.h> + +QT_BEGIN_NAMESPACE + +class QQmlDelegateModelEngineData : public QV8Engine::Deletable +{ +public: + enum + { + Model, + Groups, + IsUnresolved, + ItemsIndex, + PersistedItemsIndex, + InItems, + InPersistedItems, + StringCount + }; + + QQmlDelegateModelEngineData(QV8Engine *engine); + ~QQmlDelegateModelEngineData(); + + v8::Local<v8::Object> array( + QV8Engine *engine, const QVector<QQmlChangeSet::Remove> &changes); + v8::Local<v8::Object> array( + QV8Engine *engine, const QVector<QQmlChangeSet::Insert> &changes); + v8::Local<v8::Object> array( + QV8Engine *engine, const QVector<QQmlChangeSet::Change> &changes); + + + inline v8::Local<v8::String> model() { return strings->Get(Model)->ToString(); } + inline v8::Local<v8::String> groups() { return strings->Get(Groups)->ToString(); } + inline v8::Local<v8::String> isUnresolved() { return strings->Get(IsUnresolved)->ToString(); } + inline v8::Local<v8::String> itemsIndex() { return strings->Get(ItemsIndex)->ToString(); } + inline v8::Local<v8::String> persistedItemsIndex() { return strings->Get(PersistedItemsIndex)->ToString(); } + inline v8::Local<v8::String> inItems() { return strings->Get(InItems)->ToString(); } + inline v8::Local<v8::String> inPersistedItems() { return strings->Get(InPersistedItems)->ToString(); } + + v8::Persistent<v8::Array> strings; + v8::Persistent<v8::Function> constructorChange; + v8::Persistent<v8::Function> constructorChangeArray; +}; + +V8_DEFINE_EXTENSION(QQmlDelegateModelEngineData, engineData) + + +void QQmlDelegateModelPartsMetaObject::propertyCreated(int, QMetaPropertyBuilder &prop) +{ + prop.setWritable(false); +} + +QVariant QQmlDelegateModelPartsMetaObject::initialValue(int id) +{ + QQmlDelegateModelParts *parts = static_cast<QQmlDelegateModelParts *>(object()); + QQmlPartsModel *m = new QQmlPartsModel( + parts->model, QString::fromUtf8(name(id)), parts); + parts->models.append(m); + return QVariant::fromValue(static_cast<QObject *>(m)); +} + +QQmlDelegateModelParts::QQmlDelegateModelParts(QQmlDelegateModel *parent) +: QObject(parent), model(parent) +{ + new QQmlDelegateModelPartsMetaObject(this); +} + +//--------------------------------------------------------------------------- + +/*! + \qmltype VisualDataModel + \instantiates QQmlDelegateModel + \inqmlmodule QtQuick 2 + \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 QtQuick 2 for compatibility reasons. The same implementation + is now primarily available as DelegateModel in the QtQml.Models module. + + \sa {QtQml.Models2::DelegateModel} +*/ +/*! + \qmltype DelegateModel + \instantiates QQmlDelegateModel + \inqmlmodule QtQml.Models 2 + \brief Encapsulates a model and delegate + + The DelegateModel type encapsulates a model and the delegate that will + be instantiated for items in the model. + + This element is also available as DelegateModel in the QtQuick module. For full details, + see the \l DelegateModel documentation. + + The DelegateModel type encapsulates a model and the delegate that will + be instantiated for items in the model. + + It is usually not necessary to create a DelegateModel. + However, it can be useful for manipulating and accessing the \l modelIndex + when a QAbstractItemModel subclass is used as the + model. Also, DelegateModel is used together with \l Package to + provide delegates to multiple views, and with DelegateModelGroup to sort and filter + delegate items. + + The example below illustrates using a DelegateModel with a ListView. + + \snippet qml/visualdatamodel.qml 0 +*/ + +QQmlDelegateModelPrivate::QQmlDelegateModelPrivate(QQmlContext *ctxt) + : m_delegate(0) + , m_cacheMetaType(0) + , m_context(ctxt) + , m_parts(0) + , m_filterGroup(QStringLiteral("items")) + , m_count(0) + , m_groupCount(Compositor::MinimumGroupCount) + , m_compositorGroup(Compositor::Cache) + , m_complete(false) + , m_delegateValidated(false) + , m_reset(false) + , m_transaction(false) + , m_incubatorCleanupScheduled(false) + , m_cacheItems(0) + , m_items(0) + , m_persistedItems(0) +{ +} + +QQmlDelegateModelPrivate::~QQmlDelegateModelPrivate() +{ + qDeleteAll(m_finishedIncubating); + + if (m_cacheMetaType) + m_cacheMetaType->release(); +} + +void QQmlDelegateModelPrivate::init() +{ + Q_Q(QQmlDelegateModel); + m_compositor.setRemoveGroups(Compositor::GroupMask & ~Compositor::PersistedFlag); + + m_items = new QQmlDelegateModelGroup(QStringLiteral("items"), q, Compositor::Default, q); + m_items->setDefaultInclude(true); + m_persistedItems = new QQmlDelegateModelGroup(QStringLiteral("persistedItems"), q, Compositor::Persisted, q); + QQmlDelegateModelGroupPrivate::get(m_items)->emitters.insert(this); +} + +QQmlDelegateModel::QQmlDelegateModel() +: QQmlInstanceModel(*(new QQmlDelegateModelPrivate(0))) +{ + Q_D(QQmlDelegateModel); + d->init(); +} + +QQmlDelegateModel::QQmlDelegateModel(QQmlContext *ctxt, QObject *parent) +: QQmlInstanceModel(*(new QQmlDelegateModelPrivate(ctxt)), parent) +{ + Q_D(QQmlDelegateModel); + d->init(); +} + +QQmlDelegateModel::~QQmlDelegateModel() +{ + Q_D(QQmlDelegateModel); + + foreach (QQmlDelegateModelItem *cacheItem, d->m_cache) { + if (cacheItem->object) { + delete cacheItem->object; + + cacheItem->object = 0; + cacheItem->contextData->destroy(); + cacheItem->contextData = 0; + cacheItem->scriptRef -= 1; + } + cacheItem->groups &= ~Compositor::UnresolvedFlag; + cacheItem->objectRef = 0; + if (!cacheItem->isReferenced()) + delete cacheItem; + } +} + + +void QQmlDelegateModel::classBegin() +{ + Q_D(QQmlDelegateModel); + if (!d->m_context) + d->m_context = qmlContext(this); +} + +void QQmlDelegateModel::componentComplete() +{ + Q_D(QQmlDelegateModel); + d->m_complete = true; + + int defaultGroups = 0; + QStringList groupNames; + groupNames.append(QStringLiteral("items")); + groupNames.append(QStringLiteral("persistedItems")); + if (QQmlDelegateModelGroupPrivate::get(d->m_items)->defaultInclude) + defaultGroups |= Compositor::DefaultFlag; + if (QQmlDelegateModelGroupPrivate::get(d->m_persistedItems)->defaultInclude) + defaultGroups |= Compositor::PersistedFlag; + for (int i = Compositor::MinimumGroupCount; i < d->m_groupCount; ++i) { + QString name = d->m_groups[i]->name(); + if (name.isEmpty()) { + d->m_groups[i] = d->m_groups[d->m_groupCount - 1]; + --d->m_groupCount; + --i; + } else if (name.at(0).isUpper()) { + qmlInfo(d->m_groups[i]) << QQmlDelegateModelGroup::tr("Group names must start with a lower case letter"); + d->m_groups[i] = d->m_groups[d->m_groupCount - 1]; + --d->m_groupCount; + --i; + } else { + groupNames.append(name); + + QQmlDelegateModelGroupPrivate *group = QQmlDelegateModelGroupPrivate::get(d->m_groups[i]); + group->setModel(this, Compositor::Group(i)); + if (group->defaultInclude) + defaultGroups |= (1 << i); + } + } + + d->m_cacheMetaType = new QQmlDelegateModelItemMetaType( + QQmlEnginePrivate::getV8Engine(d->m_context->engine()), this, groupNames); + + d->m_compositor.setGroupCount(d->m_groupCount); + d->m_compositor.setDefaultGroups(defaultGroups); + d->updateFilterGroup(); + + while (!d->m_pendingParts.isEmpty()) + static_cast<QQmlPartsModel *>(d->m_pendingParts.first())->updateFilterGroup(); + + QVector<Compositor::Insert> inserts; + d->m_count = d->m_adaptorModel.count(); + d->m_compositor.append( + &d->m_adaptorModel, + 0, + d->m_count, + defaultGroups | Compositor::AppendFlag | Compositor::PrependFlag, + &inserts); + d->itemsInserted(inserts); + d->emitChanges(); + + if (d->m_adaptorModel.canFetchMore()) + QCoreApplication::postEvent(this, new QEvent(QEvent::UpdateRequest)); +} + +/*! + \qmlproperty model QtQml.Models2::DelegateModel::model + This property holds the model providing data for the DelegateModel. + + The model provides a set of data that is used to create the items + for a view. For large or dynamic datasets the model is usually + provided by a C++ model object. The C++ model object must be a \l + {QAbstractItemModel} subclass or a simple list. + + Models can also be created directly in QML, using a \l{ListModel} or + \l{XmlListModel}. + + \sa {qml-data-models}{Data Models} +*/ +QVariant QQmlDelegateModel::model() const +{ + Q_D(const QQmlDelegateModel); + return d->m_adaptorModel.model(); +} + +void QQmlDelegateModel::setModel(const QVariant &model) +{ + Q_D(QQmlDelegateModel); + + if (d->m_complete) + _q_itemsRemoved(0, d->m_count); + + d->m_adaptorModel.setModel(model, this, d->m_context->engine()); + 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( + QList<QByteArray>(), d->m_parts->models.at(i)->watchedRoles()); + } + + if (d->m_complete) { + _q_itemsInserted(0, d->m_adaptorModel.count()); + if (d->m_adaptorModel.canFetchMore()) + QCoreApplication::postEvent(this, new QEvent(QEvent::UpdateRequest)); + } +} + +/*! + \qmlproperty Component QtQml.Models2::DelegateModel::delegate + + The delegate provides a template defining each item instantiated by a view. + The index is exposed as an accessible \c index property. Properties of the + model are also available depending upon the type of \l {qml-data-models}{Data Model}. +*/ +QQmlComponent *QQmlDelegateModel::delegate() const +{ + Q_D(const QQmlDelegateModel); + return d->m_delegate; +} + +void QQmlDelegateModel::setDelegate(QQmlComponent *delegate) +{ + Q_D(QQmlDelegateModel); + if (d->m_transaction) { + qmlInfo(this) << tr("The delegate of a DelegateModel cannot be changed within onUpdated."); + return; + } + bool wasValid = d->m_delegate != 0; + d->m_delegate = delegate; + 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))); + } + } + d->emitChanges(); +} + +/*! + \qmlproperty QModelIndex QtQml.Models2::DelegateModel::rootIndex + + QAbstractItemModel provides a hierarchical tree of data, whereas + QML only operates on list data. \c rootIndex allows the children of + any node in a QAbstractItemModel to be provided by this model. + + This property only affects models of type QAbstractItemModel that + are hierarchical (e.g, a tree model). + + For example, here is a simple interactive file system browser. + When a directory name is clicked, the view's \c rootIndex is set to the + QModelIndex node of the clicked directory, thus updating the view to show + the new directory's contents. + + \c main.cpp: + \snippet qml/visualdatamodel_rootindex/main.cpp 0 + + \c view.qml: + \snippet qml/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. + + + \sa modelIndex(), parentModelIndex() +*/ +QVariant QQmlDelegateModel::rootIndex() const +{ + Q_D(const QQmlDelegateModel); + return QVariant::fromValue(QModelIndex(d->m_adaptorModel.rootIndex)); +} + +void QQmlDelegateModel::setRootIndex(const QVariant &root) +{ + Q_D(QQmlDelegateModel); + + QModelIndex modelIndex = qvariant_cast<QModelIndex>(root); + const bool changed = d->m_adaptorModel.rootIndex != modelIndex; + 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. + d->m_adaptorModel.setModel(d->m_adaptorModel.list.list(), this, d->m_context->engine()); + if (d->m_adaptorModel.canFetchMore()) + d->m_adaptorModel.fetchMore(); + if (d->m_complete) { + const int newCount = d->m_adaptorModel.count(); + if (oldCount) + _q_itemsRemoved(0, oldCount); + if (newCount) + _q_itemsInserted(0, newCount); + } + if (changed) + emit rootIndexChanged(); + } +} + +/*! + \qmlmethod QModelIndex QtQml.Models2::DelegateModel::modelIndex(int index) + + QAbstractItemModel provides a hierarchical tree of data, whereas + QML only operates on list data. This function assists in using + tree models in QML. + + Returns a QModelIndex for the specified index. + This value can be assigned to rootIndex. + + \sa rootIndex +*/ +QVariant QQmlDelegateModel::modelIndex(int idx) const +{ + Q_D(const QQmlDelegateModel); + return d->m_adaptorModel.modelIndex(idx); +} + +/*! + \qmlmethod QModelIndex QtQml.Models2::DelegateModel::parentModelIndex() + + QAbstractItemModel provides a hierarchical tree of data, whereas + QML only operates on list data. This function assists in using + tree models in QML. + + Returns a QModelIndex for the parent of the current rootIndex. + This value can be assigned to rootIndex. + + \sa rootIndex +*/ +QVariant QQmlDelegateModel::parentModelIndex() const +{ + Q_D(const QQmlDelegateModel); + return d->m_adaptorModel.parentModelIndex(); +} + +/*! + \qmlproperty int QtQml.Models2::DelegateModel::count +*/ + +int QQmlDelegateModel::count() const +{ + Q_D(const QQmlDelegateModel); + if (!d->m_delegate) + return 0; + return d->m_compositor.count(d->m_compositorGroup); +} + +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 stat; +} + +/* + Returns ReleaseStatus flags. +*/ + +QQmlDelegateModel::ReleaseFlags QQmlDelegateModel::release(QObject *item) +{ + Q_D(QQmlDelegateModel); + QQmlInstanceModel::ReleaseFlags stat = d->release(item); + return stat; +} + +// Cancel a requested async item +void QQmlDelegateModel::cancel(int index) +{ + Q_D(QQmlDelegateModel); + if (!d->m_delegate || index < 0 || index >= d->m_compositor.count(d->m_compositorGroup)) { + qWarning() << "DelegateModel::cancel: index out range" << index << d->m_compositor.count(d->m_compositorGroup); + return; + } + + Compositor::iterator it = d->m_compositor.find(d->m_compositorGroup, index); + QQmlDelegateModelItem *cacheItem = it->inCache() ? d->m_cache.at(it.cacheIndex) : 0; + if (cacheItem) { + if (cacheItem->incubationTask && !cacheItem->isObjectReferenced()) { + d->releaseIncubator(cacheItem->incubationTask); + cacheItem->incubationTask = 0; + + if (cacheItem->object) { + QObject *object = cacheItem->object; + cacheItem->destroyObject(); + if (QQuickPackage *package = qmlobject_cast<QQuickPackage *>(object)) + d->emitDestroyingPackage(package); + else + d->emitDestroyingItem(object); + } + + cacheItem->scriptRef -= 1; + } + if (!cacheItem->isReferenced()) { + d->m_compositor.clearFlags(Compositor::Cache, it.cacheIndex, 1, Compositor::CacheFlag); + d->m_cache.removeAt(it.cacheIndex); + delete cacheItem; + Q_ASSERT(d->m_cache.count() == d->m_compositor.count(Compositor::Cache)); + } + } +} + +void QQmlDelegateModelPrivate::group_append( + QQmlListProperty<QQmlDelegateModelGroup> *property, QQmlDelegateModelGroup *group) +{ + QQmlDelegateModelPrivate *d = static_cast<QQmlDelegateModelPrivate *>(property->data); + if (d->m_complete) + return; + if (d->m_groupCount == Compositor::MaximumGroupCount) { + qmlInfo(d->q_func()) << QQmlDelegateModel::tr("The maximum number of supported DelegateModelGroups is 8"); + return; + } + d->m_groups[d->m_groupCount] = group; + d->m_groupCount += 1; +} + +int QQmlDelegateModelPrivate::group_count( + QQmlListProperty<QQmlDelegateModelGroup> *property) +{ + QQmlDelegateModelPrivate *d = static_cast<QQmlDelegateModelPrivate *>(property->data); + return d->m_groupCount - 1; +} + +QQmlDelegateModelGroup *QQmlDelegateModelPrivate::group_at( + QQmlListProperty<QQmlDelegateModelGroup> *property, int index) +{ + QQmlDelegateModelPrivate *d = static_cast<QQmlDelegateModelPrivate *>(property->data); + return index >= 0 && index < d->m_groupCount - 1 + ? d->m_groups[index + 1] + : 0; +} + +/*! + \qmlproperty list<DelegateModelGroup> QtQml.Models2::DelegateModel::groups + + This property holds a visual data model's group definitions. + + Groups define a sub-set of the items in a visual data model and can be used to filter + a model. + + For every group defined in a DelegateModel two attached properties are added to each + delegate item. The first of the form DelegateModel.in\e{GroupName} holds whether the + item belongs to the group and the second DelegateModel.\e{groupName}Index holds the + index of the item in that group. + + The following example illustrates using groups to select items in a model. + + \snippet qml/visualdatagroup.qml 0 +*/ + +QQmlListProperty<QQmlDelegateModelGroup> QQmlDelegateModel::groups() +{ + Q_D(QQmlDelegateModel); + return QQmlListProperty<QQmlDelegateModelGroup>( + this, + d, + QQmlDelegateModelPrivate::group_append, + QQmlDelegateModelPrivate::group_count, + QQmlDelegateModelPrivate::group_at, + 0); +} + +/*! + \qmlproperty DelegateModelGroup QtQml.Models2::DelegateModel::items + + This property holds visual data model's default group to which all new items are added. +*/ + +QQmlDelegateModelGroup *QQmlDelegateModel::items() +{ + Q_D(QQmlDelegateModel); + return d->m_items; +} + +/*! + \qmlproperty DelegateModelGroup QtQml.Models2::DelegateModel::persistedItems + + This property holds visual data 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. + + An item can be removed from the persistedItems group by setting the + DelegateModel.inPersistedItems property to false. If the item is not referenced by a view + at that time it will be destroyed. Adding an item to this group will not create a new + instance. + + Items returned by the \l QtQml.Models2::DelegateModelGroup::create() function are automatically added + to this group. +*/ + +QQmlDelegateModelGroup *QQmlDelegateModel::persistedItems() +{ + Q_D(QQmlDelegateModel); + return d->m_persistedItems; +} + +/*! + \qmlproperty string QtQml.Models2::DelegateModel::filterOnGroup + + This property holds the name of the group used to filter the visual data model. + + Only items which belong to this group are visible to a view. + + By default this is the \l items group. +*/ + +QString QQmlDelegateModel::filterGroup() const +{ + Q_D(const QQmlDelegateModel); + return d->m_filterGroup; +} + +void QQmlDelegateModel::setFilterGroup(const QString &group) +{ + Q_D(QQmlDelegateModel); + + if (d->m_transaction) { + qmlInfo(this) << tr("The group of a DelegateModel cannot be changed within onChanged"); + return; + } + + if (d->m_filterGroup != group) { + d->m_filterGroup = group; + d->updateFilterGroup(); + emit filterGroupChanged(); + } +} + +void QQmlDelegateModel::resetFilterGroup() +{ + setFilterGroup(QStringLiteral("items")); +} + +void QQmlDelegateModelPrivate::updateFilterGroup() +{ + Q_Q(QQmlDelegateModel); + if (!m_cacheMetaType) + return; + + QQmlListCompositor::Group previousGroup = m_compositorGroup; + m_compositorGroup = Compositor::Default; + for (int i = 1; i < m_groupCount; ++i) { + if (m_filterGroup == m_cacheMetaType->groupNames.at(i - 1)) { + m_compositorGroup = Compositor::Group(i); + break; + } + } + + QQmlDelegateModelGroupPrivate::get(m_groups[m_compositorGroup])->emitters.insert(this); + if (m_compositorGroup != previousGroup) { + QVector<QQmlChangeSet::Remove> removes; + QVector<QQmlChangeSet::Insert> inserts; + m_compositor.transition(previousGroup, m_compositorGroup, &removes, &inserts); + + QQmlChangeSet changeSet; + changeSet.move(removes, inserts); + emit q->modelUpdated(changeSet, false); + + if (changeSet.difference() != 0) + emit q->countChanged(); + + if (m_parts) { + foreach (QQmlPartsModel *model, m_parts->models) + model->updateFilterGroup(m_compositorGroup, changeSet); + } + } +} + +/*! + \qmlproperty object QtQml.Models2::DelegateModel::parts + + The \a parts property selects a DelegateModel which creates + delegates from the part named. This is used in conjunction with + the \l Package type. + + For example, the code below selects a model which creates + delegates named \e list from a \l Package: + + \code + DelegateModel { + id: visualModel + delegate: Package { + Item { Package.name: "list" } + } + model: myModel + } + + ListView { + width: 200; height:200 + model: visualModel.parts.list + } + \endcode + + \sa Package +*/ + +QObject *QQmlDelegateModel::parts() +{ + Q_D(QQmlDelegateModel); + if (!d->m_parts) + d->m_parts = new QQmlDelegateModelParts(this); + return d->m_parts; +} + +void QQmlDelegateModelPrivate::emitCreatedPackage(QQDMIncubationTask *incubationTask, QQuickPackage *package) +{ + for (int i = 1; i < m_groupCount; ++i) + QQmlDelegateModelGroupPrivate::get(m_groups[i])->createdPackage(incubationTask->index[i], package); +} + +void QQmlDelegateModelPrivate::emitInitPackage(QQDMIncubationTask *incubationTask, QQuickPackage *package) +{ + for (int i = 1; i < m_groupCount; ++i) + QQmlDelegateModelGroupPrivate::get(m_groups[i])->initPackage(incubationTask->index[i], package); +} + +void QQmlDelegateModelPrivate::emitDestroyingPackage(QQuickPackage *package) +{ + for (int i = 1; i < m_groupCount; ++i) + QQmlDelegateModelGroupPrivate::get(m_groups[i])->destroyingPackage(package); +} + +void QQDMIncubationTask::statusChanged(Status status) +{ + vdm->incubatorStatusChanged(this, status); +} + +void QQmlDelegateModelPrivate::releaseIncubator(QQDMIncubationTask *incubationTask) +{ + Q_Q(QQmlDelegateModel); + if (!incubationTask->isError()) + incubationTask->clear(); + m_finishedIncubating.append(incubationTask); + if (!m_incubatorCleanupScheduled) { + m_incubatorCleanupScheduled = true; + QCoreApplication::postEvent(q, new QEvent(QEvent::User)); + } +} + +void QQmlDelegateModelPrivate::removeCacheItem(QQmlDelegateModelItem *cacheItem) +{ + int cidx = m_cache.indexOf(cacheItem); + if (cidx >= 0) { + m_compositor.clearFlags(Compositor::Cache, cidx, 1, Compositor::CacheFlag); + m_cache.removeAt(cidx); + } + Q_ASSERT(m_cache.count() == m_compositor.count(Compositor::Cache)); +} + +void QQmlDelegateModelPrivate::incubatorStatusChanged(QQDMIncubationTask *incubationTask, QQmlIncubator::Status status) +{ + Q_Q(QQmlDelegateModel); + if (status != QQmlIncubator::Ready && status != QQmlIncubator::Error) + return; + + QQmlDelegateModelItem *cacheItem = incubationTask->incubating; + cacheItem->incubationTask = 0; + incubationTask->incubating = 0; + releaseIncubator(incubationTask); + + if (status == QQmlIncubator::Ready) { + if (QQuickPackage *package = qmlobject_cast<QQuickPackage *>(cacheItem->object)) + emitCreatedPackage(incubationTask, package); + else + emitCreatedItem(incubationTask, cacheItem->object); + } else if (status == QQmlIncubator::Error) { + qmlInfo(q, m_delegate->errors()) << "Error creating delegate"; + } + + if (!cacheItem->isObjectReferenced()) { + if (QQuickPackage *package = qmlobject_cast<QQuickPackage *>(cacheItem->object)) + emitDestroyingPackage(package); + else + emitDestroyingItem(cacheItem->object); + delete cacheItem->object; + cacheItem->object = 0; + cacheItem->scriptRef -= 1; + cacheItem->contextData->destroy(); + cacheItem->contextData = 0; + if (!cacheItem->isReferenced()) { + removeCacheItem(cacheItem); + delete cacheItem; + } + } +} + +void QQDMIncubationTask::setInitialState(QObject *o) +{ + vdm->setInitialState(this, o); +} + +void QQmlDelegateModelPrivate::setInitialState(QQDMIncubationTask *incubationTask, QObject *o) +{ + QQmlDelegateModelItem *cacheItem = incubationTask->incubating; + cacheItem->object = o; + + if (QQuickPackage *package = qmlobject_cast<QQuickPackage *>(cacheItem->object)) + emitInitPackage(incubationTask, package); + else + emitInitItem(incubationTask, cacheItem->object); +} + +QObject *QQmlDelegateModelPrivate::object(Compositor::Group group, int index, bool asynchronous) +{ + Q_Q(QQmlDelegateModel); + if (!m_delegate || index < 0 || index >= m_compositor.count(group)) { + qWarning() << "DelegateModel::item: index out range" << index << m_compositor.count(group); + return 0; + } else if (!m_context->isValid()) { + return 0; + } + + Compositor::iterator it = m_compositor.find(group, index); + + QQmlDelegateModelItem *cacheItem = it->inCache() ? m_cache.at(it.cacheIndex) : 0; + + if (!cacheItem) { + cacheItem = m_adaptorModel.createItem(m_cacheMetaType, m_context->engine(), it.modelIndex()); + if (!cacheItem) + return 0; + + 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)); + } + + // Bump the reference counts temporarily so neither the content data or the delegate object + // are deleted if incubatorStatusChanged() is called synchronously. + cacheItem->scriptRef += 1; + cacheItem->referenceObject(); + + if (cacheItem->incubationTask) { + if (!asynchronous && cacheItem->incubationTask->incubationMode() == QQmlIncubator::Asynchronous) { + // previously requested async - now needed immediately + cacheItem->incubationTask->forceCompletion(); + } + } else if (!cacheItem->object) { + QQmlContext *creationContext = m_delegate->creationContext(); + + cacheItem->scriptRef += 1; + + cacheItem->incubationTask = new QQDMIncubationTask(this, asynchronous ? QQmlIncubator::Asynchronous : QQmlIncubator::AsynchronousIfNested); + cacheItem->incubationTask->incubating = cacheItem; + cacheItem->incubationTask->clear(); + + for (int i = 1; i < m_groupCount; ++i) + cacheItem->incubationTask->index[i] = it.index[i]; + + QQmlContextData *ctxt = new QQmlContextData; + ctxt->setParent(QQmlContextData::get(creationContext ? creationContext : m_context)); + ctxt->contextObject = cacheItem; + cacheItem->contextData = ctxt; + + if (m_adaptorModel.hasProxyObject()) { + if (QQmlAdaptorModelProxyInterface *proxy + = qobject_cast<QQmlAdaptorModelProxyInterface *>(cacheItem)) { + ctxt = new QQmlContextData; + ctxt->setParent(cacheItem->contextData, true); + ctxt->contextObject = proxy->proxiedObject(); + } + } + + cacheItem->incubateObject( + m_delegate, + m_context->engine(), + ctxt, + QQmlContextData::get(m_context)); + } + + if (index == m_compositor.count(group) - 1 && m_adaptorModel.canFetchMore()) + QCoreApplication::postEvent(q, new QEvent(QEvent::UpdateRequest)); + + // Remove the temporary reference count. + cacheItem->scriptRef -= 1; + if (cacheItem->object) + return cacheItem->object; + + cacheItem->releaseObject(); + if (!cacheItem->isReferenced()) { + removeCacheItem(cacheItem); + delete cacheItem; + } + + return 0; +} + +/* + 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 itemCreated() 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. +*/ +QObject *QQmlDelegateModel::object(int index, bool asynchronous) +{ + 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; + } + + QObject *object = d->object(d->m_compositorGroup, index, asynchronous); + if (!object) + return 0; + + return object; +} + +QString QQmlDelegateModelPrivate::stringValue(Compositor::Group group, int index, const QString &name) +{ + Compositor::iterator it = m_compositor.find(group, index); + if (QQmlAdaptorModel *model = it.list<QQmlAdaptorModel>()) { + QString role = name; + int dot = name.indexOf(QLatin1Char('.')); + if (dot > 0) + role = name.left(dot); + QVariant value = model->value(it.modelIndex(), role); + while (dot > 0) { + QObject *obj = qvariant_cast<QObject*>(value); + if (!obj) + return QString(); + int from = dot+1; + dot = name.indexOf(QLatin1Char('.'), from); + value = obj->property(name.mid(from, dot-from).toUtf8()); + } + return value.toString(); + } + return QString(); +} + +QString QQmlDelegateModel::stringValue(int index, const QString &name) +{ + Q_D(QQmlDelegateModel); + return d->stringValue(d->m_compositorGroup, index, name); +} + +int QQmlDelegateModel::indexOf(QObject *item, QObject *) const +{ + Q_D(const QQmlDelegateModel); + if (QQmlDelegateModelItem *cacheItem = QQmlDelegateModelItem::dataForObject(item)) + return cacheItem->groupIndex(d->m_compositorGroup); + return -1; +} + +void QQmlDelegateModel::setWatchedRoles(QList<QByteArray> roles) +{ + Q_D(QQmlDelegateModel); + d->m_adaptorModel.replaceWatchedRoles(d->m_watchedRoles, roles); + d->m_watchedRoles = roles; +} + +void QQmlDelegateModelPrivate::addGroups( + Compositor::iterator from, int count, Compositor::Group group, int groupFlags) +{ + QVector<Compositor::Insert> inserts; + m_compositor.setFlags(from, count, group, groupFlags, &inserts); + itemsInserted(inserts); + emitChanges(); +} + +void QQmlDelegateModelPrivate::removeGroups( + Compositor::iterator from, int count, Compositor::Group group, int groupFlags) +{ + QVector<Compositor::Remove> removes; + m_compositor.clearFlags(from, count, group, groupFlags, &removes); + itemsRemoved(removes); + emitChanges(); +} + +void QQmlDelegateModelPrivate::setGroups( + Compositor::iterator from, int count, Compositor::Group group, int groupFlags) +{ + QVector<Compositor::Remove> removes; + QVector<Compositor::Insert> inserts; + + m_compositor.setFlags(from, count, group, groupFlags, &inserts); + itemsInserted(inserts); + const int removeFlags = ~groupFlags & Compositor::GroupMask; + + from = m_compositor.find(from.group, from.index[from.group]); + m_compositor.clearFlags(from, count, group, removeFlags, &removes); + itemsRemoved(removes); + emitChanges(); +} + +bool QQmlDelegateModel::event(QEvent *e) +{ + Q_D(QQmlDelegateModel); + if (e->type() == QEvent::UpdateRequest) { + d->m_adaptorModel.fetchMore(); + } else if (e->type() == QEvent::User) { + d->m_incubatorCleanupScheduled = false; + qDeleteAll(d->m_finishedIncubating); + d->m_finishedIncubating.clear(); + } + return QQmlInstanceModel::event(e); +} + +void QQmlDelegateModelPrivate::itemsChanged(const QVector<Compositor::Change> &changes) +{ + if (!m_delegate) + return; + + QVarLengthArray<QVector<QQmlChangeSet::Change>, Compositor::MaximumGroupCount> translatedChanges(m_groupCount); + + foreach (const Compositor::Change &change, changes) { + for (int i = 1; i < m_groupCount; ++i) { + if (change.inGroup(i)) { + translatedChanges[i].append(QQmlChangeSet::Change(change.index[i], change.count)); + } + } + } + + for (int i = 1; i < m_groupCount; ++i) + QQmlDelegateModelGroupPrivate::get(m_groups[i])->changeSet.change(translatedChanges.at(i)); +} + +void QQmlDelegateModel::_q_itemsChanged(int index, int count, const QVector<int> &roles) +{ + Q_D(QQmlDelegateModel); + if (count <= 0 || !d->m_complete) + return; + + if (d->m_adaptorModel.notify(d->m_cache, index, count, roles)) { + QVector<Compositor::Change> changes; + d->m_compositor.listItemsChanged(&d->m_adaptorModel, index, count, &changes); + d->itemsChanged(changes); + d->emitChanges(); + } +} + +static void incrementIndexes(QQmlDelegateModelItem *cacheItem, int count, const int *deltas) +{ + if (QQDMIncubationTask *incubationTask = cacheItem->incubationTask) { + for (int i = 1; i < count; ++i) + incubationTask->index[i] += deltas[i]; + } + if (QQmlDelegateModelAttached *attached = cacheItem->attached) { + for (int i = 1; i < count; ++i) + attached->m_currentIndex[i] += deltas[i]; + } +} + +void QQmlDelegateModelPrivate::itemsInserted( + const QVector<Compositor::Insert> &inserts, + QVarLengthArray<QVector<QQmlChangeSet::Insert>, Compositor::MaximumGroupCount> *translatedInserts, + QHash<int, QList<QQmlDelegateModelItem *> > *movedItems) +{ + int cacheIndex = 0; + + int inserted[Compositor::MaximumGroupCount]; + for (int i = 1; i < m_groupCount; ++i) + inserted[i] = 0; + + foreach (const Compositor::Insert &insert, inserts) { + for (; cacheIndex < insert.cacheIndex; ++cacheIndex) + incrementIndexes(m_cache.at(cacheIndex), m_groupCount, inserted); + + for (int i = 1; i < m_groupCount; ++i) { + if (insert.inGroup(i)) { + (*translatedInserts)[i].append( + QQmlChangeSet::Insert(insert.index[i], insert.count, insert.moveId)); + inserted[i] += insert.count; + } + } + + if (!insert.inCache()) + continue; + + if (movedItems && insert.isMove()) { + QList<QQmlDelegateModelItem *> items = movedItems->take(insert.moveId); + Q_ASSERT(items.count() == insert.count); + m_cache = m_cache.mid(0, insert.cacheIndex) + items + m_cache.mid(insert.cacheIndex); + } + if (insert.inGroup()) { + for (int offset = 0; cacheIndex < insert.cacheIndex + insert.count; ++cacheIndex, ++offset) { + QQmlDelegateModelItem *cacheItem = m_cache.at(cacheIndex); + cacheItem->groups |= insert.flags & Compositor::GroupMask; + + if (QQDMIncubationTask *incubationTask = cacheItem->incubationTask) { + for (int i = 1; i < m_groupCount; ++i) + incubationTask->index[i] = cacheItem->groups & (1 << i) + ? insert.index[i] + offset + : insert.index[i]; + } + if (QQmlDelegateModelAttached *attached = cacheItem->attached) { + for (int i = 1; i < m_groupCount; ++i) + attached->m_currentIndex[i] = cacheItem->groups & (1 << i) + ? insert.index[i] + offset + : insert.index[i]; + } + } + } else { + cacheIndex = insert.cacheIndex + insert.count; + } + } + for (; cacheIndex < m_cache.count(); ++cacheIndex) + incrementIndexes(m_cache.at(cacheIndex), m_groupCount, inserted); +} + +void QQmlDelegateModelPrivate::itemsInserted(const QVector<Compositor::Insert> &inserts) +{ + QVarLengthArray<QVector<QQmlChangeSet::Insert>, Compositor::MaximumGroupCount> translatedInserts(m_groupCount); + itemsInserted(inserts, &translatedInserts); + Q_ASSERT(m_cache.count() == m_compositor.count(Compositor::Cache)); + if (!m_delegate) + return; + + for (int i = 1; i < m_groupCount; ++i) + QQmlDelegateModelGroupPrivate::get(m_groups[i])->changeSet.insert(translatedInserts.at(i)); +} + +void QQmlDelegateModel::_q_itemsInserted(int index, int count) +{ + + Q_D(QQmlDelegateModel); + if (count <= 0 || !d->m_complete) + return; + + d->m_count += count; + + for (int i = 0, c = d->m_cache.count(); i < c; ++i) { + QQmlDelegateModelItem *item = d->m_cache.at(i); + if (item->modelIndex() >= index) + item->setModelIndex(item->modelIndex() + count); + } + + QVector<Compositor::Insert> inserts; + d->m_compositor.listItemsInserted(&d->m_adaptorModel, index, count, &inserts); + d->itemsInserted(inserts); + d->emitChanges(); +} + +void QQmlDelegateModelPrivate::itemsRemoved( + const QVector<Compositor::Remove> &removes, + QVarLengthArray<QVector<QQmlChangeSet::Remove>, Compositor::MaximumGroupCount> *translatedRemoves, + QHash<int, QList<QQmlDelegateModelItem *> > *movedItems) +{ + int cacheIndex = 0; + int removedCache = 0; + + int removed[Compositor::MaximumGroupCount]; + for (int i = 1; i < m_groupCount; ++i) + removed[i] = 0; + + foreach (const Compositor::Remove &remove, removes) { + for (; cacheIndex < remove.cacheIndex; ++cacheIndex) + incrementIndexes(m_cache.at(cacheIndex), m_groupCount, removed); + + for (int i = 1; i < m_groupCount; ++i) { + if (remove.inGroup(i)) { + (*translatedRemoves)[i].append( + QQmlChangeSet::Remove(remove.index[i], remove.count, remove.moveId)); + removed[i] -= remove.count; + } + } + + if (!remove.inCache()) + continue; + + if (movedItems && remove.isMove()) { + movedItems->insert(remove.moveId, m_cache.mid(remove.cacheIndex, remove.count)); + QList<QQmlDelegateModelItem *>::iterator begin = m_cache.begin() + remove.cacheIndex; + QList<QQmlDelegateModelItem *>::iterator end = begin + remove.count; + m_cache.erase(begin, end); + } else { + for (; cacheIndex < remove.cacheIndex + remove.count - removedCache; ++cacheIndex) { + QQmlDelegateModelItem *cacheItem = m_cache.at(cacheIndex); + if (remove.inGroup(Compositor::Persisted) && cacheItem->objectRef == 0 && cacheItem->object) { + QObject *object = cacheItem->object; + cacheItem->destroyObject(); + if (QQuickPackage *package = qmlobject_cast<QQuickPackage *>(object)) + emitDestroyingPackage(package); + else + emitDestroyingItem(object); + cacheItem->scriptRef -= 1; + } + if (!cacheItem->isReferenced()) { + m_compositor.clearFlags(Compositor::Cache, cacheIndex, 1, Compositor::CacheFlag); + m_cache.removeAt(cacheIndex); + delete cacheItem; + --cacheIndex; + ++removedCache; + Q_ASSERT(m_cache.count() == m_compositor.count(Compositor::Cache)); + } else if (remove.groups() == cacheItem->groups) { + cacheItem->groups = 0; + if (QQDMIncubationTask *incubationTask = cacheItem->incubationTask) { + for (int i = 1; i < m_groupCount; ++i) + incubationTask->index[i] = -1; + } + if (QQmlDelegateModelAttached *attached = cacheItem->attached) { + for (int i = 1; i < m_groupCount; ++i) + attached->m_currentIndex[i] = -1; + } + } else { + if (QQDMIncubationTask *incubationTask = cacheItem->incubationTask) { + for (int i = 1; i < m_groupCount; ++i) { + if (remove.inGroup(i)) + incubationTask->index[i] = remove.index[i]; + } + } + if (QQmlDelegateModelAttached *attached = cacheItem->attached) { + for (int i = 1; i < m_groupCount; ++i) { + if (remove.inGroup(i)) + attached->m_currentIndex[i] = remove.index[i]; + } + } + cacheItem->groups &= ~remove.flags; + } + } + } + } + + for (; cacheIndex < m_cache.count(); ++cacheIndex) + incrementIndexes(m_cache.at(cacheIndex), m_groupCount, removed); +} + +void QQmlDelegateModelPrivate::itemsRemoved(const QVector<Compositor::Remove> &removes) +{ + QVarLengthArray<QVector<QQmlChangeSet::Remove>, Compositor::MaximumGroupCount> translatedRemoves(m_groupCount); + itemsRemoved(removes, &translatedRemoves); + Q_ASSERT(m_cache.count() == m_compositor.count(Compositor::Cache)); + if (!m_delegate) + return; + + for (int i = 1; i < m_groupCount; ++i) + QQmlDelegateModelGroupPrivate::get(m_groups[i])->changeSet.remove(translatedRemoves.at(i)); +} + +void QQmlDelegateModel::_q_itemsRemoved(int index, int count) +{ + Q_D(QQmlDelegateModel); + if (count <= 0|| !d->m_complete) + return; + + d->m_count -= count; + + for (int i = 0, c = d->m_cache.count(); i < c; ++i) { + QQmlDelegateModelItem *item = d->m_cache.at(i); + if (item->modelIndex() >= index + count) + item->setModelIndex(item->modelIndex() - count); + else if (item->modelIndex() >= index) + item->setModelIndex(-1); + } + + QVector<Compositor::Remove> removes; + d->m_compositor.listItemsRemoved(&d->m_adaptorModel, index, count, &removes); + d->itemsRemoved(removes); + + d->emitChanges(); +} + +void QQmlDelegateModelPrivate::itemsMoved( + const QVector<Compositor::Remove> &removes, const QVector<Compositor::Insert> &inserts) +{ + QHash<int, QList<QQmlDelegateModelItem *> > movedItems; + + QVarLengthArray<QVector<QQmlChangeSet::Remove>, Compositor::MaximumGroupCount> translatedRemoves(m_groupCount); + itemsRemoved(removes, &translatedRemoves, &movedItems); + + QVarLengthArray<QVector<QQmlChangeSet::Insert>, Compositor::MaximumGroupCount> translatedInserts(m_groupCount); + itemsInserted(inserts, &translatedInserts, &movedItems); + Q_ASSERT(m_cache.count() == m_compositor.count(Compositor::Cache)); + Q_ASSERT(movedItems.isEmpty()); + if (!m_delegate) + return; + + for (int i = 1; i < m_groupCount; ++i) { + QQmlDelegateModelGroupPrivate::get(m_groups[i])->changeSet.move( + translatedRemoves.at(i), + translatedInserts.at(i)); + } +} + +void QQmlDelegateModel::_q_itemsMoved(int from, int to, int count) +{ + Q_D(QQmlDelegateModel); + if (count <= 0 || !d->m_complete) + return; + + const int minimum = qMin(from, to); + const int maximum = qMax(from, to) + count; + const int difference = from > to ? count : -count; + + for (int i = 0, c = d->m_cache.count(); i < c; ++i) { + QQmlDelegateModelItem *item = d->m_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); + } + + QVector<Compositor::Remove> removes; + QVector<Compositor::Insert> inserts; + d->m_compositor.listItemsMoved(&d->m_adaptorModel, from, to, count, &removes, &inserts); + d->itemsMoved(removes, inserts); + d->emitChanges(); +} + +template <typename T> v8::Local<v8::Array> +QQmlDelegateModelPrivate::buildChangeList(const QVector<T> &changes) +{ + v8::Local<v8::Array> indexes = v8::Array::New(changes.count()); + v8::Local<v8::String> indexKey = v8::String::New("index"); + v8::Local<v8::String> countKey = v8::String::New("count"); + v8::Local<v8::String> moveIdKey = v8::String::New("moveId"); + + for (int i = 0; i < changes.count(); ++i) { + v8::Local<v8::Object> object = v8::Object::New(); + object->Set(indexKey, v8::Integer::New(changes.at(i).index)); + object->Set(countKey, v8::Integer::New(changes.at(i).count)); + object->Set(moveIdKey, changes.at(i).moveId != -1 ? v8::Integer::New(changes.at(i).count) : v8::Undefined()); + indexes->Set(i, object); + } + return indexes; +} + +void QQmlDelegateModelPrivate::emitModelUpdated(const QQmlChangeSet &changeSet, bool reset) +{ + Q_Q(QQmlDelegateModel); + emit q->modelUpdated(changeSet, reset); + if (changeSet.difference() != 0) + emit q->countChanged(); +} + +void QQmlDelegateModelPrivate::emitChanges() +{ + if (m_transaction || !m_complete || !m_context->isValid()) + return; + + m_transaction = true; + QV8Engine *engine = QQmlEnginePrivate::getV8Engine(m_context->engine()); + for (int i = 1; i < m_groupCount; ++i) + QQmlDelegateModelGroupPrivate::get(m_groups[i])->emitChanges(engine); + m_transaction = false; + + const bool reset = m_reset; + m_reset = false; + for (int i = 1; i < m_groupCount; ++i) + QQmlDelegateModelGroupPrivate::get(m_groups[i])->emitModelUpdated(reset); + + foreach (QQmlDelegateModelItem *cacheItem, m_cache) { + if (cacheItem->attached) + cacheItem->attached->emitChanges(); + } +} + +void QQmlDelegateModel::_q_modelReset() +{ + Q_D(QQmlDelegateModel); + if (!d->m_delegate) + return; + + int oldCount = d->m_count; + d->m_adaptorModel.rootIndex = QModelIndex(); + + if (d->m_complete) { + d->m_count = d->m_adaptorModel.count(); + + for (int i = 0, c = d->m_cache.count(); i < c; ++i) { + QQmlDelegateModelItem *item = d->m_cache.at(i); + if (item->modelIndex() != -1) + item->setModelIndex(-1); + } + + QVector<Compositor::Remove> removes; + QVector<Compositor::Insert> inserts; + if (oldCount) + d->m_compositor.listItemsRemoved(&d->m_adaptorModel, 0, oldCount, &removes); + if (d->m_count) + d->m_compositor.listItemsInserted(&d->m_adaptorModel, 0, d->m_count, &inserts); + d->itemsMoved(removes, inserts); + d->m_reset = true; + + if (d->m_adaptorModel.canFetchMore()) + d->m_adaptorModel.fetchMore(); + + d->emitChanges(); + } + emit rootIndexChanged(); +} + +void QQmlDelegateModel::_q_rowsInserted(const QModelIndex &parent, int begin, int end) +{ + Q_D(QQmlDelegateModel); + if (parent == d->m_adaptorModel.rootIndex) + _q_itemsInserted(begin, end - begin + 1); +} + +void QQmlDelegateModel::_q_rowsAboutToBeRemoved(const QModelIndex &parent, int begin, int end) +{ + Q_D(QQmlDelegateModel); + if (!d->m_adaptorModel.rootIndex.isValid()) + return; + const QModelIndex index = d->m_adaptorModel.rootIndex; + 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); + + if (d->m_complete && oldCount > 0) { + QVector<Compositor::Remove> removes; + d->m_compositor.listItemsRemoved(&d->m_adaptorModel, 0, oldCount, &removes); + d->itemsRemoved(removes); + d->emitChanges(); + } + } +} + +void QQmlDelegateModel::_q_rowsRemoved(const QModelIndex &parent, int begin, int end) +{ + Q_D(QQmlDelegateModel); + if (parent == d->m_adaptorModel.rootIndex) + _q_itemsRemoved(begin, end - begin + 1); +} + +void QQmlDelegateModel::_q_rowsMoved( + const QModelIndex &sourceParent, int sourceStart, int sourceEnd, + const QModelIndex &destinationParent, int destinationRow) +{ + Q_D(QQmlDelegateModel); + const int count = sourceEnd - sourceStart + 1; + if (destinationParent == d->m_adaptorModel.rootIndex && sourceParent == d->m_adaptorModel.rootIndex) { + _q_itemsMoved(sourceStart, sourceStart > destinationRow ? destinationRow : destinationRow - count, count); + } else if (sourceParent == d->m_adaptorModel.rootIndex) { + _q_itemsRemoved(sourceStart, count); + } else if (destinationParent == d->m_adaptorModel.rootIndex) { + _q_itemsInserted(destinationRow, count); + } +} + +void QQmlDelegateModel::_q_dataChanged(const QModelIndex &begin, const QModelIndex &end, const QVector<int> &roles) +{ + Q_D(QQmlDelegateModel); + if (begin.parent() == d->m_adaptorModel.rootIndex) + _q_itemsChanged(begin.row(), end.row() - begin.row() + 1, roles); +} + +void QQmlDelegateModel::_q_layoutChanged() +{ + Q_D(QQmlDelegateModel); + _q_itemsChanged(0, d->m_count, QVector<int>()); +} + +QQmlDelegateModelAttached *QQmlDelegateModel::qmlAttachedProperties(QObject *obj) +{ + if (QQmlDelegateModelItem *cacheItem = QQmlDelegateModelItem::dataForObject(obj)) { + if (cacheItem->object == obj) { // Don't create attached item for child objects. + cacheItem->attached = new QQmlDelegateModelAttached(cacheItem, obj); + return cacheItem->attached; + } + } + return new QQmlDelegateModelAttached(obj); +} + +bool QQmlDelegateModelPrivate::insert( + Compositor::insert_iterator &before, const v8::Local<v8::Object> &object, int groups) +{ + if (!m_context->isValid()) + return false; + + QQmlDelegateModelItem *cacheItem = m_adaptorModel.createItem(m_cacheMetaType, m_context->engine(), -1); + if (!cacheItem) + return false; + + v8::Local<v8::Array> propertyNames = object->GetPropertyNames(); + for (uint i = 0; i < propertyNames->Length(); ++i) { + v8::Local<v8::String> propertyName = propertyNames->Get(i)->ToString(); + cacheItem->setValue( + m_cacheMetaType->v8Engine->toString(propertyName), + m_cacheMetaType->v8Engine->toVariant(object->Get(propertyName), QVariant::Invalid)); + } + + cacheItem->groups = groups | Compositor::UnresolvedFlag | Compositor::CacheFlag; + + // Must be before the new object is inserted into the cache or its indexes will be adjusted too. + itemsInserted(QVector<Compositor::Insert>() << Compositor::Insert(before, 1, cacheItem->groups & ~Compositor::CacheFlag)); + + before = m_compositor.insert(before, 0, 0, 1, cacheItem->groups); + m_cache.insert(before.cacheIndex, cacheItem); + + return true; +} + +//============================================================================ + +QQmlDelegateModelItemMetaType::QQmlDelegateModelItemMetaType( + QV8Engine *engine, QQmlDelegateModel *model, const QStringList &groupNames) + : model(model) + , groupCount(groupNames.count() + 1) + , v8Engine(engine) + , metaObject(0) + , groupNames(groupNames) +{ +} + +QQmlDelegateModelItemMetaType::~QQmlDelegateModelItemMetaType() +{ + if (metaObject) + metaObject->release(); + qPersistentDispose(constructor); +} + +void QQmlDelegateModelItemMetaType::initializeMetaObject() +{ + QMetaObjectBuilder builder; + builder.setFlags(QMetaObjectBuilder::DynamicMetaObject); + builder.setClassName(QQmlDelegateModelAttached::staticMetaObject.className()); + builder.setSuperClass(&QQmlDelegateModelAttached::staticMetaObject); + + int notifierId = 0; + for (int i = 0; i < groupNames.count(); ++i, ++notifierId) { + QString propertyName = QStringLiteral("in") + groupNames.at(i); + propertyName.replace(2, 1, propertyName.at(2).toUpper()); + builder.addSignal("__" + propertyName.toUtf8() + "Changed()"); + QMetaPropertyBuilder propertyBuilder = builder.addProperty( + propertyName.toUtf8(), "bool", notifierId); + propertyBuilder.setWritable(true); + } + for (int i = 0; i < groupNames.count(); ++i, ++notifierId) { + const QString propertyName = groupNames.at(i) + QStringLiteral("Index"); + builder.addSignal("__" + propertyName.toUtf8() + "Changed()"); + QMetaPropertyBuilder propertyBuilder = builder.addProperty( + propertyName.toUtf8(), "int", notifierId); + propertyBuilder.setWritable(true); + } + + metaObject = new QQmlDelegateModelAttachedMetaObject(this, builder.toMetaObject()); +} + +void QQmlDelegateModelItemMetaType::initializeConstructor() +{ + v8::HandleScope handleScope; + v8::Context::Scope contextScope(v8Engine->context()); + + QQmlDelegateModelEngineData *data = engineData(v8Engine); + + constructor = qPersistentNew(v8::ObjectTemplate::New()); + + constructor->SetHasExternalResource(true); + constructor->SetAccessor(data->model(), get_model); + constructor->SetAccessor(data->groups(), get_groups, set_groups); + constructor->SetAccessor(data->isUnresolved(), get_member, 0, v8::Int32::New(30)); + constructor->SetAccessor(data->inItems(), get_member, set_member, v8::Int32::New(1)); + constructor->SetAccessor(data->inPersistedItems(), get_member, set_member, v8::Int32::New(2)); + constructor->SetAccessor(data->itemsIndex(), get_index, 0, v8::Int32::New(1)); + constructor->SetAccessor(data->persistedItemsIndex(), get_index, 0, v8::Int32::New(2)); + + for (int i = 2; i < groupNames.count(); ++i) { + QString propertyName = QStringLiteral("in") + groupNames.at(i); + propertyName.replace(2, 1, propertyName.at(2).toUpper()); + constructor->SetAccessor( + v8Engine->toString(propertyName), get_member, set_member, v8::Int32::New(i + 1)); + } + for (int i = 2; i < groupNames.count(); ++i) { + const QString propertyName = groupNames.at(i) + QStringLiteral("Index"); + constructor->SetAccessor( + v8Engine->toString(propertyName), get_index, 0, v8::Int32::New(i + 1)); + } +} + +int QQmlDelegateModelItemMetaType::parseGroups(const QStringList &groups) const +{ + int groupFlags = 0; + foreach (const QString &groupName, groups) { + int index = groupNames.indexOf(groupName); + if (index != -1) + groupFlags |= 2 << index; + } + return groupFlags; +} + +int QQmlDelegateModelItemMetaType::parseGroups(const v8::Local<v8::Value> &groups) const +{ + int groupFlags = 0; + if (groups->IsString()) { + const QString groupName = v8Engine->toString(groups); + int index = groupNames.indexOf(groupName); + if (index != -1) + groupFlags |= 2 << index; + } else if (groups->IsArray()) { + v8::Local<v8::Array> array = v8::Local<v8::Array>::Cast(groups); + for (uint i = 0; i < array->Length(); ++i) { + const QString groupName = v8Engine->toString(array->Get(i)); + int index = groupNames.indexOf(groupName); + if (index != -1) + groupFlags |= 2 << index; + } + } + return groupFlags; +} + +v8::Handle<v8::Value> QQmlDelegateModelItemMetaType::get_model( + v8::Local<v8::String>, const v8::AccessorInfo &info) +{ + QQmlDelegateModelItem *cacheItem = v8_resource_cast<QQmlDelegateModelItem>(info.This()); + V8ASSERT_TYPE(cacheItem, "Not a valid VisualData object"); + if (!cacheItem->metaType->model) + return v8::Undefined(); + + return cacheItem->get(); +} + +v8::Handle<v8::Value> QQmlDelegateModelItemMetaType::get_groups( + v8::Local<v8::String>, const v8::AccessorInfo &info) +{ + QQmlDelegateModelItem *cacheItem = v8_resource_cast<QQmlDelegateModelItem>(info.This()); + V8ASSERT_TYPE(cacheItem, "Not a valid VisualData object"); + + QStringList groups; + for (int i = 1; i < cacheItem->metaType->groupCount; ++i) { + if (cacheItem->groups & (1 << i)) + groups.append(cacheItem->metaType->groupNames.at(i - 1)); + } + + return cacheItem->engine->fromVariant(groups); +} + +void QQmlDelegateModelItemMetaType::set_groups( + v8::Local<v8::String>, v8::Local<v8::Value> value, const v8::AccessorInfo &info) +{ + QQmlDelegateModelItem *cacheItem = v8_resource_cast<QQmlDelegateModelItem>(info.This()); + V8ASSERT_TYPE_SETTER(cacheItem, "Not a valid VisualData object"); + + if (!cacheItem->metaType->model) + return; + QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(cacheItem->metaType->model); + + const int groupFlags = model->m_cacheMetaType->parseGroups(value); + const int cacheIndex = model->m_cache.indexOf(cacheItem); + Compositor::iterator it = model->m_compositor.find(Compositor::Cache, cacheIndex); + model->setGroups(it, 1, Compositor::Cache, groupFlags); +} + +v8::Handle<v8::Value> QQmlDelegateModelItemMetaType::get_member( + v8::Local<v8::String>, const v8::AccessorInfo &info) +{ + QQmlDelegateModelItem *cacheItem = v8_resource_cast<QQmlDelegateModelItem>(info.This()); + V8ASSERT_TYPE(cacheItem, "Not a valid VisualData object"); + + return v8::Boolean::New(cacheItem->groups & (1 << info.Data()->Int32Value())); +} + +void QQmlDelegateModelItemMetaType::set_member( + v8::Local<v8::String>, v8::Local<v8::Value> value, const v8::AccessorInfo &info) +{ + QQmlDelegateModelItem *cacheItem = v8_resource_cast<QQmlDelegateModelItem>(info.This()); + V8ASSERT_TYPE_SETTER(cacheItem, "Not a valid VisualData object"); + + if (!cacheItem->metaType->model) + return; + QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(cacheItem->metaType->model); + + Compositor::Group group = Compositor::Group(info.Data()->Int32Value()); + const bool member = value->BooleanValue(); + const int groupFlag = (1 << group); + if (member == ((cacheItem->groups & groupFlag) != 0)) + return; + + const int cacheIndex = model->m_cache.indexOf(cacheItem); + Compositor::iterator it = model->m_compositor.find(Compositor::Cache, cacheIndex); + if (member) + model->addGroups(it, 1, Compositor::Cache, groupFlag); + else + model->removeGroups(it, 1, Compositor::Cache, groupFlag); +} + +v8::Handle<v8::Value> QQmlDelegateModelItemMetaType::get_index( + v8::Local<v8::String>, const v8::AccessorInfo &info) +{ + QQmlDelegateModelItem *cacheItem = v8_resource_cast<QQmlDelegateModelItem>(info.This()); + V8ASSERT_TYPE(cacheItem, "Not a valid VisualData object"); + + return v8::Integer::New(cacheItem->groupIndex(Compositor::Group(info.Data()->Int32Value()))); +} + + +//--------------------------------------------------------------------------- + +QQmlDelegateModelItem::QQmlDelegateModelItem( + QQmlDelegateModelItemMetaType *metaType, int modelIndex) + : QV8ObjectResource(metaType->v8Engine) + , metaType(metaType) + , contextData(0) + , object(0) + , attached(0) + , incubationTask(0) + , objectRef(0) + , scriptRef(0) + , groups(0) + , index(modelIndex) +{ + metaType->addref(); +} + +QQmlDelegateModelItem::~QQmlDelegateModelItem() +{ + Q_ASSERT(scriptRef == 0); + Q_ASSERT(objectRef == 0); + Q_ASSERT(!object); + + if (incubationTask && metaType->model) + QQmlDelegateModelPrivate::get(metaType->model)->releaseIncubator(incubationTask); + + metaType->release(); + +} + +void QQmlDelegateModelItem::Dispose() +{ + --scriptRef; + if (isReferenced()) + return; + + if (metaType->model) { + QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(metaType->model); + model->removeCacheItem(this); + } + delete this; +} + +/* + This is essentially a copy of QQmlComponent::create(); except it takes the QQmlContextData + arguments instead of QQmlContext which means we don't have to construct the rather weighty + wrapper class for every delegate item. +*/ +void QQmlDelegateModelItem::incubateObject( + QQmlComponent *component, + QQmlEngine *engine, + QQmlContextData *context, + QQmlContextData *forContext) +{ + QQmlIncubatorPrivate *incubatorPriv = QQmlIncubatorPrivate::get(incubationTask); + QQmlEnginePrivate *enginePriv = QQmlEnginePrivate::get(engine); + QQmlComponentPrivate *componentPriv = QQmlComponentPrivate::get(component); + + incubatorPriv->compiledData = componentPriv->cc; + incubatorPriv->compiledData->addref(); + incubatorPriv->vme.init( + context, + componentPriv->cc, + componentPriv->start, + componentPriv->creationContext); + + enginePriv->incubate(*incubationTask, forContext); +} + +void QQmlDelegateModelItem::destroyObject() +{ + Q_ASSERT(object); + Q_ASSERT(contextData); + + QObjectPrivate *p = QObjectPrivate::get(object); + Q_ASSERT(p->declarativeData); + QQmlData *data = static_cast<QQmlData*>(p->declarativeData); + if (data->ownContext && data->context) + data->context->clearContext(); + object->deleteLater(); + + if (attached) { + attached->m_cacheItem = 0; + attached = 0; + } + + contextData->destroy(); + contextData = 0; + object = 0; +} + +QQmlDelegateModelItem *QQmlDelegateModelItem::dataForObject(QObject *object) +{ + QObjectPrivate *p = QObjectPrivate::get(object); + QQmlContextData *context = p->declarativeData + ? static_cast<QQmlData *>(p->declarativeData)->context + : 0; + for (context = context ? context->parent : 0; context; context = context->parent) { + if (QQmlDelegateModelItem *cacheItem = qobject_cast<QQmlDelegateModelItem *>( + context->contextObject)) { + return cacheItem; + } + } + return 0; +} + +int QQmlDelegateModelItem::groupIndex(Compositor::Group group) +{ + if (QQmlDelegateModelPrivate * const model = metaType->model + ? QQmlDelegateModelPrivate::get(metaType->model) + : 0) { + return model->m_compositor.find(Compositor::Cache, model->m_cache.indexOf(this)).index[group]; + } + return -1; +} + +//--------------------------------------------------------------------------- + +QQmlDelegateModelAttachedMetaObject::QQmlDelegateModelAttachedMetaObject( + QQmlDelegateModelItemMetaType *metaType, QMetaObject *metaObject) + : metaType(metaType) + , metaObject(metaObject) + , memberPropertyOffset(QQmlDelegateModelAttached::staticMetaObject.propertyCount()) + , indexPropertyOffset(QQmlDelegateModelAttached::staticMetaObject.propertyCount() + metaType->groupNames.count()) +{ + // Don't reference count the meta-type here as that would create a circular reference. + // Instead we rely the fact that the meta-type's reference count can't reach 0 without first + // destroying all delegates with attached objects. + *static_cast<QMetaObject *>(this) = *metaObject; +} + +QQmlDelegateModelAttachedMetaObject::~QQmlDelegateModelAttachedMetaObject() +{ + ::free(metaObject); +} + +void QQmlDelegateModelAttachedMetaObject::objectDestroyed(QObject *) +{ + release(); +} + +int QQmlDelegateModelAttachedMetaObject::metaCall(QObject *object, QMetaObject::Call call, int _id, void **arguments) +{ + QQmlDelegateModelAttached *attached = static_cast<QQmlDelegateModelAttached *>(object); + if (call == QMetaObject::ReadProperty) { + if (_id >= indexPropertyOffset) { + Compositor::Group group = Compositor::Group(_id - indexPropertyOffset + 1); + *static_cast<int *>(arguments[0]) = attached->m_currentIndex[group]; + return -1; + } else if (_id >= memberPropertyOffset) { + Compositor::Group group = Compositor::Group(_id - memberPropertyOffset + 1); + *static_cast<bool *>(arguments[0]) = attached->m_cacheItem->groups & (1 << group); + return -1; + } + } else if (call == QMetaObject::WriteProperty) { + if (_id >= memberPropertyOffset) { + if (!metaType->model) + return -1; + QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(metaType->model); + Compositor::Group group = Compositor::Group(_id - memberPropertyOffset + 1); + const int groupFlag = 1 << group; + const bool member = attached->m_cacheItem->groups & groupFlag; + if (member && !*static_cast<bool *>(arguments[0])) { + Compositor::iterator it = model->m_compositor.find( + group, attached->m_currentIndex[group]); + model->removeGroups(it, 1, group, groupFlag); + } else if (!member && *static_cast<bool *>(arguments[0])) { + for (int i = 1; i < metaType->groupCount; ++i) { + if (attached->m_cacheItem->groups & (1 << i)) { + Compositor::iterator it = model->m_compositor.find( + Compositor::Group(i), attached->m_currentIndex[i]); + model->addGroups(it, 1, Compositor::Group(i), groupFlag); + break; + } + } + } + return -1; + } + } + return attached->qt_metacall(call, _id, arguments); +} + +QQmlDelegateModelAttached::QQmlDelegateModelAttached(QObject *parent) + : m_cacheItem(0) + , m_previousGroups(0) +{ + QQml_setParent_noEvent(this, parent); +} + +QQmlDelegateModelAttached::QQmlDelegateModelAttached( + QQmlDelegateModelItem *cacheItem, QObject *parent) + : m_cacheItem(cacheItem) + , m_previousGroups(cacheItem->groups) +{ + QQml_setParent_noEvent(this, parent); + if (QQDMIncubationTask *incubationTask = m_cacheItem->incubationTask) { + for (int i = 1; i < m_cacheItem->metaType->groupCount; ++i) + m_currentIndex[i] = m_previousIndex[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]; + } + + if (!cacheItem->metaType->metaObject) + cacheItem->metaType->initializeMetaObject(); + + QObjectPrivate::get(this)->metaObject = cacheItem->metaType->metaObject; + cacheItem->metaType->metaObject->addref(); +} + +/*! + \qmlattachedproperty int QtQml.Models2::DelegateModel::model + + This attached property holds the visual 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; +} + +/*! + \qmlattachedproperty stringlist QtQml.Models2::DelegateModel::groups + + This attached property holds the name of DelegateModelGroups the item belongs to. + + It is attached to each instance of the delegate. +*/ + +QStringList QQmlDelegateModelAttached::groups() const +{ + QStringList groups; + + if (!m_cacheItem) + return groups; + for (int i = 1; i < m_cacheItem->metaType->groupCount; ++i) { + if (m_cacheItem->groups & (1 << i)) + groups.append(m_cacheItem->metaType->groupNames.at(i - 1)); + } + return groups; +} + +void QQmlDelegateModelAttached::setGroups(const QStringList &groups) +{ + if (!m_cacheItem) + return; + + QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(m_cacheItem->metaType->model); + + const int groupFlags = model->m_cacheMetaType->parseGroups(groups); + const int cacheIndex = model->m_cache.indexOf(m_cacheItem); + Compositor::iterator it = model->m_compositor.find(Compositor::Cache, cacheIndex); + model->setGroups(it, 1, Compositor::Cache, groupFlags); +} + +/*! + \qmlattachedproperty bool QtQml.Models2::DelegateModel::isUnresolved + + This attached property holds 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() + function. + + It is attached to each instance of the delegate. +*/ + +bool QQmlDelegateModelAttached::isUnresolved() const +{ + if (!m_cacheItem) + return false; + + return m_cacheItem->groups & Compositor::UnresolvedFlag; +} + +/*! + \qmlattachedproperty int QtQml.Models2::DelegateModel::inItems + + This attached property holds whether the item belongs to the default \l items DelegateModelGroup. + + Changing this property will add or remove the item from the items group. + + It is attached to each instance of the delegate. +*/ + +/*! + \qmlattachedproperty int QtQml.Models2::DelegateModel::itemsIndex + + This attached property holds the index of the item in the default \l items DelegateModelGroup. + + It is attached to each instance of the delegate. +*/ + +/*! + \qmlattachedproperty int QtQml.Models2::DelegateModel::inPersistedItems + + This attached property holds whether the item belongs to the \l persistedItems DelegateModelGroup. + + Changing this property will add or remove the item from the items group. Change with caution + as removing an item from the persistedItems group will destroy the current instance if it is + not referenced by a model. + + It is attached to each instance of the delegate. +*/ + +/*! + \qmlattachedproperty int QtQml.Models2::DelegateModel::persistedItemsIndex + + This attached property holds the index of the item in the \l persistedItems DelegateModelGroup. + + It is attached to each instance of the delegate. +*/ + +void QQmlDelegateModelAttached::emitChanges() +{ + const int groupChanges = m_previousGroups ^ m_cacheItem->groups; + m_previousGroups = m_cacheItem->groups; + + int indexChanges = 0; + for (int i = 1; i < m_cacheItem->metaType->groupCount; ++i) { + if (m_previousIndex[i] != m_currentIndex[i]) { + m_previousIndex[i] = m_currentIndex[i]; + indexChanges |= (1 << i); + } + } + + int notifierId = 0; + 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); + } + for (int i = 1; i < m_cacheItem->metaType->groupCount; ++i, ++notifierId) { + if (indexChanges & (1 << i)) + QMetaObject::activate(this, meta, notifierId, 0); + } + + if (groupChanges) + emit groupsChanged(); +} + +//============================================================================ + +void QQmlDelegateModelGroupPrivate::setModel(QQmlDelegateModel *m, Compositor::Group g) +{ + Q_ASSERT(!model); + model = m; + group = g; +} + +bool QQmlDelegateModelGroupPrivate::isChangedConnected() +{ + Q_Q(QQmlDelegateModelGroup); + IS_SIGNAL_CONNECTED(q, QQmlDelegateModelGroup, changed, (const QQmlV8Handle &,const QQmlV8Handle &)); +} + +void QQmlDelegateModelGroupPrivate::emitChanges(QV8Engine *engine) +{ + Q_Q(QQmlDelegateModelGroup); + if (isChangedConnected() && !changeSet.isEmpty()) { + v8::HandleScope handleScope; + v8::Context::Scope contextScope(engine->context()); + v8::Local<v8::Object> removed = engineData(engine)->array(engine, changeSet.removes()); + v8::Local<v8::Object> inserted = engineData(engine)->array(engine, changeSet.inserts()); + emit q->changed(QQmlV8Handle::fromHandle(removed), QQmlV8Handle::fromHandle(inserted)); + } + if (changeSet.difference() != 0) + emit q->countChanged(); +} + +void QQmlDelegateModelGroupPrivate::emitModelUpdated(bool reset) +{ + for (QQmlDelegateModelGroupEmitterList::iterator it = emitters.begin(); it != emitters.end(); ++it) + it->emitModelUpdated(changeSet, reset); + changeSet.clear(); +} + +void QQmlDelegateModelGroupPrivate::createdPackage(int index, QQuickPackage *package) +{ + for (QQmlDelegateModelGroupEmitterList::iterator it = emitters.begin(); it != emitters.end(); ++it) + it->createdPackage(index, package); +} + +void QQmlDelegateModelGroupPrivate::initPackage(int index, QQuickPackage *package) +{ + for (QQmlDelegateModelGroupEmitterList::iterator it = emitters.begin(); it != emitters.end(); ++it) + it->initPackage(index, package); +} + +void QQmlDelegateModelGroupPrivate::destroyingPackage(QQuickPackage *package) +{ + for (QQmlDelegateModelGroupEmitterList::iterator it = emitters.begin(); it != emitters.end(); ++it) + it->destroyingPackage(package); +} + +/*! + \qmltype DelegateModelGroup + \instantiates QQmlDelegateModelGroup + \inqmlmodule QtQuick 2 + \ingroup qtquick-models + \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 items. + + The initial set of instantiable delegate items in a DelegateModel is represented + by its \l {QtQml.Models2::DelegateModel::items}{items} group, which normally directly reflects + the contents of the model assigned to DelegateModel::model. This set can be changed to + the contents of any other member of DelegateModel::groups by assigning the \l name of that + DelegateModelGroup to the DelegateModel::filterOnGroup property. + + The data of an item in a DelegateModelGroup can be accessed using the get() function, which returns + 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. + + 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 + placeholder items that are later \l {resolve()}{resolved} to real model data when it becomes + available. + + Delegate items can also be be instantiated directly from a DelegateModelGroup using the + create() function, making it possible to use DelegateModel without an accompanying view + type or to cherry-pick specific items that should be instantiated irregardless of whether + they're currently within a view's visible area. + + \sa {QML Dynamic View Ordering Tutorial} +*/ +/*! + \qmltype DelegateModelGroup + \instantiates QQmlDelegateModelGroup + \inqmlmodule QtQml.Models 2 + \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 items. + + This element is also available as DelegateModelGroup in the QtQuick module. For full details, + see the \l DelegateModelGroup documentation. + + \sa {QtQuick::DelegateModelGroup} +*/ + + +QQmlDelegateModelGroup::QQmlDelegateModelGroup(QObject *parent) + : QObject(*new QQmlDelegateModelGroupPrivate, parent) +{ +} + +QQmlDelegateModelGroup::QQmlDelegateModelGroup( + const QString &name, QQmlDelegateModel *model, int index, QObject *parent) + : QObject(*new QQmlDelegateModelGroupPrivate, parent) +{ + Q_D(QQmlDelegateModelGroup); + d->name = name; + d->setModel(model, Compositor::Group(index)); +} + +QQmlDelegateModelGroup::~QQmlDelegateModelGroup() +{ +} + +/*! + \qmlproperty string QtQml.Models2::DelegateModelGroup::name + + This property holds the name of the group. + + Each group in a model must have a unique name starting with a lower case letter. +*/ + +QString QQmlDelegateModelGroup::name() const +{ + Q_D(const QQmlDelegateModelGroup); + return d->name; +} + +void QQmlDelegateModelGroup::setName(const QString &name) +{ + Q_D(QQmlDelegateModelGroup); + if (d->model) + return; + if (d->name != name) { + d->name = name; + emit nameChanged(); + } +} + +/*! + \qmlproperty int QtQml.Models2::DelegateModelGroup::count + + This property holds the number of items in the group. +*/ + +int QQmlDelegateModelGroup::count() const +{ + Q_D(const QQmlDelegateModelGroup); + if (!d->model) + return 0; + return QQmlDelegateModelPrivate::get(d->model)->m_compositor.count(d->group); +} + +/*! + \qmlproperty bool QtQml.Models2::DelegateModelGroup::includeByDefault + + This property holds whether new items are assigned to this group by default. +*/ + +bool QQmlDelegateModelGroup::defaultInclude() const +{ + Q_D(const QQmlDelegateModelGroup); + return d->defaultInclude; +} + +void QQmlDelegateModelGroup::setDefaultInclude(bool include) +{ + Q_D(QQmlDelegateModelGroup); + if (d->defaultInclude != include) { + d->defaultInclude = include; + + if (d->model) { + if (include) + QQmlDelegateModelPrivate::get(d->model)->m_compositor.setDefaultGroup(d->group); + else + QQmlDelegateModelPrivate::get(d->model)->m_compositor.clearDefaultGroup(d->group); + } + emit defaultIncludeChanged(); + } +} + +/*! + \qmlmethod object QtQml.Models2::DelegateModelGroup::get(int index) + + Returns a javascript object describing the item at \a index in the group. + + The returned object contains the same information that is available to a delegate from the + DelegateModel attached as well as the model for that item. It has the properties: + + \list + \li \b model The model data of the item. This is the same as the model context property in + a delegate + \li \b groups A list the of names of groups the item is a member of. This property can be + written to change the item's membership. + \li \b inItems Whether the item belongs to the \l {QtQml.Models2::DelegateModel::items}{items} group. + Writing to this property will add or remove the item from the group. + \li \b itemsIndex The index of the item within the \l {QtQml.Models2::DelegateModel::items}{items} group. + \li \b {in<GroupName>} Whether the item belongs to the dynamic group \e groupName. Writing to + this property will add or remove the item from the group. + \li \b {<groupName>Index} The index of the item within the dynamic group \e groupName. + \li \b isUnresolved Whether the item is bound to an index in the model assigned to + DelegateModel::model. Returns true if the item is not bound to the model, and false if it is. + \endlist +*/ + +QQmlV8Handle QQmlDelegateModelGroup::get(int index) +{ + Q_D(QQmlDelegateModelGroup); + if (!d->model) + return QQmlV8Handle::fromHandle(v8::Undefined());; + + QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(d->model); + if (!model->m_context->isValid()) { + return QQmlV8Handle::fromHandle(v8::Undefined()); + } else if (index < 0 || index >= model->m_compositor.count(d->group)) { + qmlInfo(this) << tr("get: index out of range"); + return QQmlV8Handle::fromHandle(v8::Undefined()); + } + + Compositor::iterator it = model->m_compositor.find(d->group, index); + QQmlDelegateModelItem *cacheItem = it->inCache() + ? model->m_cache.at(it.cacheIndex) + : 0; + + if (!cacheItem) { + cacheItem = model->m_adaptorModel.createItem( + model->m_cacheMetaType, model->m_context->engine(), it.modelIndex()); + if (!cacheItem) + return QQmlV8Handle::fromHandle(v8::Undefined()); + cacheItem->groups = it->flags; + + model->m_cache.insert(it.cacheIndex, cacheItem); + model->m_compositor.setFlags(it, 1, Compositor::CacheFlag); + } + + if (model->m_cacheMetaType->constructor.IsEmpty()) + model->m_cacheMetaType->initializeConstructor(); + v8::Local<v8::Object> handle = model->m_cacheMetaType->constructor->NewInstance(); + handle->SetExternalResource(cacheItem); + ++cacheItem->scriptRef; + + return QQmlV8Handle::fromHandle(handle); +} + +bool QQmlDelegateModelGroupPrivate::parseIndex( + const v8::Local<v8::Value> &value, int *index, Compositor::Group *group) const +{ + if (value->IsInt32()) { + *index = value->Int32Value(); + return true; + } else if (value->IsObject()) { + v8::Local<v8::Object> object = value->ToObject(); + QQmlDelegateModelItem * const cacheItem = v8_resource_cast<QQmlDelegateModelItem>(object); + if (QQmlDelegateModelPrivate *model = cacheItem && cacheItem->metaType->model + ? QQmlDelegateModelPrivate::get(cacheItem->metaType->model) + : 0) { + *index = model->m_cache.indexOf(cacheItem); + *group = Compositor::Cache; + return true; + } + } + return false; +} + +/*! + \qmlmethod QtQml.Models2::DelegateModelGroup::insert(int index, jsdict data, array groups = undefined) + \qmlmethod QtQml.Models2::DelegateModelGroup::insert(jsdict data, var groups = undefined) + + Creates a new entry at \a index in a DelegateModel with the values from \a data that + correspond to roles in the model assigned to DelegateModel::model. + + If no index is supplied the data is appended to the model. + + The optional \a groups parameter identifies the groups the new entry should belong to, + if unspecified this is equal to the group insert was called on. + + Data inserted into a DelegateModel can later be merged with an existing entry in + DelegateModel::model using the \l resolve() function. This can be used to create placeholder + items that are later replaced by actual data. +*/ + +void QQmlDelegateModelGroup::insert(QQmlV8Function *args) +{ + Q_D(QQmlDelegateModelGroup); + QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(d->model); + + int index = model->m_compositor.count(d->group); + Compositor::Group group = d->group; + + if (args->Length() == 0) + return; + + int i = 0; + v8::Local<v8::Value> v = (*args)[i]; + if (d->parseIndex(v, &index, &group)) { + if (index < 0 || index > model->m_compositor.count(group)) { + qmlInfo(this) << tr("insert: index out of range"); + return; + } + if (++i == args->Length()) + return; + v = (*args)[i]; + } + + Compositor::insert_iterator before = index < model->m_compositor.count(group) + ? model->m_compositor.findInsertPosition(group, index) + : model->m_compositor.end(); + + int groups = 1 << d->group; + if (++i < args->Length()) + groups |= model->m_cacheMetaType->parseGroups((*args)[i]); + + if (v->IsArray()) { + return; + } else if (v->IsObject()) { + model->insert(before, v->ToObject(), groups); + model->emitChanges(); + } +} + +/*! + \qmlmethod QtQml.Models2::DelegateModelGroup::create(int index) + \qmlmethod QtQml.Models2::DelegateModelGroup::create(int index, jsdict data, array groups = undefined) + \qmlmethod QtQml.Models2::DelegateModelGroup::create(jsdict data, array groups = undefined) + + Returns a reference to the instantiated item at \a index in the group. + + If a \a data object is provided it will be \l {insert}{inserted} at \a index and an item + referencing this new entry will be returned. The optional \a groups parameter identifies + the groups the new entry should belong to, if unspecified this is equal to the group create() + was called on. + + All items returned by create are added to the + \l {QtQml.Models2::DelegateModel::persistedItems}{persistedItems} group. Items in this + group remain instantiated when not referenced by any view. +*/ + +void QQmlDelegateModelGroup::create(QQmlV8Function *args) +{ + Q_D(QQmlDelegateModelGroup); + if (!d->model) + return; + + if (args->Length() == 0) + return; + + QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(d->model); + + int index = model->m_compositor.count(d->group); + Compositor::Group group = d->group; + + int i = 0; + v8::Local<v8::Value> v = (*args)[i]; + if (d->parseIndex(v, &index, &group)) + ++i; + + if (i < args->Length() && index >= 0 && index <= model->m_compositor.count(group)) { + v = (*args)[i]; + if (v->IsObject()) { + int groups = 1 << d->group; + if (++i < args->Length()) + groups |= model->m_cacheMetaType->parseGroups((*args)[i]); + + Compositor::insert_iterator before = index < model->m_compositor.count(group) + ? model->m_compositor.findInsertPosition(group, index) + : model->m_compositor.end(); + + index = before.index[d->group]; + group = d->group; + + if (!model->insert(before, v->ToObject(), groups)) { + return; + } + } + } + if (index < 0 || index >= model->m_compositor.count(group)) { + qmlInfo(this) << tr("create: index out of range"); + return; + } + + QObject *object = model->object(group, index, false); + if (object) { + QVector<Compositor::Insert> inserts; + Compositor::iterator it = model->m_compositor.find(group, index); + model->m_compositor.setFlags(it, 1, d->group, Compositor::PersistedFlag, &inserts); + model->itemsInserted(inserts); + model->m_cache.at(it.cacheIndex)->releaseObject(); + } + + args->returnValue(args->engine()->newQObject(object)); + model->emitChanges(); +} + +/*! + \qmlmethod QtQml.Models2::DelegateModelGroup::resolve(int from, int to) + + Binds an unresolved item at \a from to an item in DelegateModel::model at index \a to. + + Unresolved items are entries whose data has been \l {insert()}{inserted} into a DelegateModelGroup + instead of being derived from a DelegateModel::model index. Resolving an item will replace + the item at the target index with the unresolved item. A resolved an item will reflect the data + of the source model at its bound index and will move when that index moves like any other item. + + If a new item is replaced in the DelegateModelGroup onChanged() handler its insertion and + replacement will be communicated to views as an atomic operation, creating the appearance + that the model contents have not changed, or if the unresolved and model item are not adjacent + that the previously unresolved item has simply moved. + +*/ +void QQmlDelegateModelGroup::resolve(QQmlV8Function *args) +{ + Q_D(QQmlDelegateModelGroup); + if (!d->model) + return; + + QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(d->model); + + if (args->Length() < 2) + return; + + int from = -1; + int to = -1; + Compositor::Group fromGroup = d->group; + Compositor::Group toGroup = d->group; + + v8::Local<v8::Value> v = (*args)[0]; + if (d->parseIndex(v, &from, &fromGroup)) { + if (from < 0 || from >= model->m_compositor.count(fromGroup)) { + qmlInfo(this) << tr("resolve: from index out of range"); + return; + } + } else { + qmlInfo(this) << tr("resolve: from index invalid"); + return; + } + + v = (*args)[1]; + if (d->parseIndex(v, &to, &toGroup)) { + if (to < 0 || to >= model->m_compositor.count(toGroup)) { + qmlInfo(this) << tr("resolve: to index out of range"); + return; + } + } else { + qmlInfo(this) << tr("resolve: to index invalid"); + return; + } + + Compositor::iterator fromIt = model->m_compositor.find(fromGroup, from); + Compositor::iterator toIt = model->m_compositor.find(toGroup, to); + + if (!fromIt->isUnresolved()) { + qmlInfo(this) << tr("resolve: from is not an unresolved item"); + return; + } + if (!toIt->list) { + qmlInfo(this) << tr("resolve: to is not a model item"); + return; + } + + const int unresolvedFlags = fromIt->flags; + const int resolvedFlags = toIt->flags; + const int resolvedIndex = toIt.modelIndex(); + void * const resolvedList = toIt->list; + + QQmlDelegateModelItem *cacheItem = model->m_cache.at(fromIt.cacheIndex); + cacheItem->groups &= ~Compositor::UnresolvedFlag; + + if (toIt.cacheIndex > fromIt.cacheIndex) + toIt.decrementIndexes(1, unresolvedFlags); + if (!toIt->inGroup(fromGroup) || toIt.index[fromGroup] > from) + from += 1; + + model->itemsMoved( + QVector<Compositor::Remove>() << Compositor::Remove(fromIt, 1, unresolvedFlags, 0), + QVector<Compositor::Insert>() << Compositor::Insert(toIt, 1, unresolvedFlags, 0)); + model->itemsInserted( + QVector<Compositor::Insert>() << Compositor::Insert(toIt, 1, (resolvedFlags & ~unresolvedFlags) | Compositor::CacheFlag)); + toIt.incrementIndexes(1, resolvedFlags | unresolvedFlags); + model->itemsRemoved(QVector<Compositor::Remove>() << Compositor::Remove(toIt, 1, resolvedFlags)); + + model->m_compositor.setFlags(toGroup, to, 1, unresolvedFlags & ~Compositor::UnresolvedFlag); + model->m_compositor.clearFlags(fromGroup, from, 1, unresolvedFlags); + + if (resolvedFlags & Compositor::CacheFlag) + model->m_compositor.insert(Compositor::Cache, toIt.cacheIndex, resolvedList, resolvedIndex, 1, Compositor::CacheFlag); + + Q_ASSERT(model->m_cache.count() == model->m_compositor.count(Compositor::Cache)); + + if (!cacheItem->isReferenced()) { + Q_ASSERT(toIt.cacheIndex == model->m_cache.indexOf(cacheItem)); + model->m_cache.removeAt(toIt.cacheIndex); + model->m_compositor.clearFlags(Compositor::Cache, toIt.cacheIndex, 1, Compositor::CacheFlag); + delete cacheItem; + Q_ASSERT(model->m_cache.count() == model->m_compositor.count(Compositor::Cache)); + } else { + cacheItem->resolveIndex(model->m_adaptorModel, resolvedIndex); + if (cacheItem->attached) + cacheItem->attached->emitUnresolvedChanged(); + } + + model->emitChanges(); +} + +/*! + \qmlmethod QtQml.Models2::DelegateModelGroup::remove(int index, int count) + + Removes \a count items starting at \a index from the group. +*/ + +void QQmlDelegateModelGroup::remove(QQmlV8Function *args) +{ + Q_D(QQmlDelegateModelGroup); + if (!d->model) + return; + Compositor::Group group = d->group; + int index = -1; + int count = 1; + + if (args->Length() == 0) + return; + + int i = 0; + v8::Local<v8::Value> v = (*args)[i]; + if (!d->parseIndex(v, &index, &group)) { + qmlInfo(this) << tr("remove: invalid index"); + return; + } + + if (++i < args->Length()) { + v = (*args)[i]; + if (v->IsInt32()) + count = v->Int32Value(); + } + + QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(d->model); + if (index < 0 || index >= model->m_compositor.count(group)) { + qmlInfo(this) << tr("remove: index out of range"); + } else if (count != 0) { + Compositor::iterator it = model->m_compositor.find(group, index); + if (count < 0 || count > model->m_compositor.count(d->group) - it.index[d->group]) { + qmlInfo(this) << tr("remove: invalid count"); + } else { + model->removeGroups(it, count, d->group, 1 << d->group); + } + } +} + +bool QQmlDelegateModelGroupPrivate::parseGroupArgs( + QQmlV8Function *args, Compositor::Group *group, int *index, int *count, int *groups) const +{ + if (!model || !QQmlDelegateModelPrivate::get(model)->m_cacheMetaType) + return false; + + if (args->Length() < 2) + return false; + + int i = 0; + v8::Local<v8::Value> v = (*args)[i]; + if (!parseIndex(v, index, group)) + return false; + + v = (*args)[++i]; + if (v->IsInt32()) { + *count = v->Int32Value(); + + if (++i == args->Length()) + return false; + v = (*args)[i]; + } + + *groups = QQmlDelegateModelPrivate::get(model)->m_cacheMetaType->parseGroups(v); + + return true; +} + +/*! + \qmlmethod QtQml.Models2::DelegateModelGroup::addGroups(int index, int count, stringlist groups) + + Adds \a count items starting at \a index to \a groups. +*/ + +void QQmlDelegateModelGroup::addGroups(QQmlV8Function *args) +{ + Q_D(QQmlDelegateModelGroup); + Compositor::Group group = d->group; + int index = -1; + int count = 1; + int groups = 0; + + if (!d->parseGroupArgs(args, &group, &index, &count, &groups)) + return; + + QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(d->model); + if (index < 0 || index >= model->m_compositor.count(group)) { + qmlInfo(this) << tr("addGroups: index out of range"); + } else if (count != 0) { + Compositor::iterator it = model->m_compositor.find(group, index); + if (count < 0 || count > model->m_compositor.count(d->group) - it.index[d->group]) { + qmlInfo(this) << tr("addGroups: invalid count"); + } else { + model->addGroups(it, count, d->group, groups); + } + } +} + +/*! + \qmlmethod QtQml.Models2::DelegateModelGroup::removeGroups(int index, int count, stringlist groups) + + Removes \a count items starting at \a index from \a groups. +*/ + +void QQmlDelegateModelGroup::removeGroups(QQmlV8Function *args) +{ + Q_D(QQmlDelegateModelGroup); + Compositor::Group group = d->group; + int index = -1; + int count = 1; + int groups = 0; + + if (!d->parseGroupArgs(args, &group, &index, &count, &groups)) + return; + + QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(d->model); + if (index < 0 || index >= model->m_compositor.count(group)) { + qmlInfo(this) << tr("removeGroups: index out of range"); + } else if (count != 0) { + Compositor::iterator it = model->m_compositor.find(group, index); + if (count < 0 || count > model->m_compositor.count(d->group) - it.index[d->group]) { + qmlInfo(this) << tr("removeGroups: invalid count"); + } else { + model->removeGroups(it, count, d->group, groups); + } + } +} + +/*! + \qmlmethod QtQml.Models2::DelegateModelGroup::setGroups(int index, int count, stringlist groups) + + Sets the \a groups \a count items starting at \a index belong to. +*/ + +void QQmlDelegateModelGroup::setGroups(QQmlV8Function *args) +{ + Q_D(QQmlDelegateModelGroup); + Compositor::Group group = d->group; + int index = -1; + int count = 1; + int groups = 0; + + if (!d->parseGroupArgs(args, &group, &index, &count, &groups)) + return; + + QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(d->model); + if (index < 0 || index >= model->m_compositor.count(group)) { + qmlInfo(this) << tr("setGroups: index out of range"); + } else if (count != 0) { + Compositor::iterator it = model->m_compositor.find(group, index); + if (count < 0 || count > model->m_compositor.count(d->group) - it.index[d->group]) { + qmlInfo(this) << tr("setGroups: invalid count"); + } else { + model->setGroups(it, count, d->group, groups); + } + } +} + +/*! + \qmlmethod QtQml.Models2::DelegateModelGroup::setGroups(int index, int count, stringlist groups) + + Sets the \a groups \a count items starting at \a index belong to. +*/ + +/*! + \qmlmethod QtQml.Models2::DelegateModelGroup::move(var from, var to, int count) + + Moves \a count at \a from in a group \a to a new position. +*/ + +void QQmlDelegateModelGroup::move(QQmlV8Function *args) +{ + Q_D(QQmlDelegateModelGroup); + + if (args->Length() < 2) + return; + + Compositor::Group fromGroup = d->group; + Compositor::Group toGroup = d->group; + int from = -1; + int to = -1; + int count = 1; + + if (!d->parseIndex((*args)[0], &from, &fromGroup)) { + qmlInfo(this) << tr("move: invalid from index"); + return; + } + + if (!d->parseIndex((*args)[1], &to, &toGroup)) { + qmlInfo(this) << tr("move: invalid to index"); + return; + } + + if (args->Length() > 2) { + v8::Local<v8::Value> v = (*args)[2]; + if (v->IsInt32()) + count = v->Int32Value(); + } + + QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(d->model); + + if (count < 0) { + qmlInfo(this) << tr("move: invalid count"); + } else if (from < 0 || from + count > model->m_compositor.count(fromGroup)) { + qmlInfo(this) << tr("move: from index out of range"); + } else if (!model->m_compositor.verifyMoveTo(fromGroup, from, toGroup, to, count, d->group)) { + qmlInfo(this) << tr("move: to index out of range"); + } else if (count > 0) { + QVector<Compositor::Remove> removes; + QVector<Compositor::Insert> inserts; + + model->m_compositor.move(fromGroup, from, toGroup, to, count, d->group, &removes, &inserts); + model->itemsMoved(removes, inserts); + model->emitChanges(); + } + +} + +/*! + \qmlsignal QtQml.Models2::DelegateModelGroup::onChanged(array removed, array inserted) + + This handler is called when items have been removed from or inserted into the group. + + Each object in the \a removed and \a inserted arrays has two values; the \e index of the first + item inserted or removed and a \e count of the number of consecutive items inserted or removed. + + Each index is adjusted for previous changes with all removed items preceding any inserted + items. +*/ + +//============================================================================ + +QQmlPartsModel::QQmlPartsModel(QQmlDelegateModel *model, const QString &part, QObject *parent) + : QQmlInstanceModel(*new QObjectPrivate, parent) + , m_model(model) + , m_part(part) + , m_compositorGroup(Compositor::Cache) + , m_inheritGroup(true) +{ + QQmlDelegateModelPrivate *d = QQmlDelegateModelPrivate::get(m_model); + if (d->m_cacheMetaType) { + QQmlDelegateModelGroupPrivate::get(d->m_groups[1])->emitters.insert(this); + m_compositorGroup = Compositor::Default; + } else { + d->m_pendingParts.insert(this); + } +} + +QQmlPartsModel::~QQmlPartsModel() +{ +} + +QString QQmlPartsModel::filterGroup() const +{ + if (m_inheritGroup) + return m_model->filterGroup(); + return m_filterGroup; +} + +void QQmlPartsModel::setFilterGroup(const QString &group) +{ + if (QQmlDelegateModelPrivate::get(m_model)->m_transaction) { + qmlInfo(this) << tr("The group of a DelegateModel cannot be changed within onChanged"); + return; + } + + if (m_filterGroup != group || m_inheritGroup) { + m_filterGroup = group; + m_inheritGroup = false; + updateFilterGroup(); + + emit filterGroupChanged(); + } +} + +void QQmlPartsModel::resetFilterGroup() +{ + if (!m_inheritGroup) { + m_inheritGroup = true; + updateFilterGroup(); + emit filterGroupChanged(); + } +} + +void QQmlPartsModel::updateFilterGroup() +{ + QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(m_model); + if (!model->m_cacheMetaType) + return; + + if (m_inheritGroup) { + if (m_filterGroup == model->m_filterGroup) + return; + m_filterGroup = model->m_filterGroup; + } + + QQmlListCompositor::Group previousGroup = m_compositorGroup; + m_compositorGroup = Compositor::Default; + QQmlDelegateModelGroupPrivate::get(model->m_groups[Compositor::Default])->emitters.insert(this); + for (int i = 1; i < model->m_groupCount; ++i) { + if (m_filterGroup == model->m_cacheMetaType->groupNames.at(i - 1)) { + m_compositorGroup = Compositor::Group(i); + break; + } + } + + QQmlDelegateModelGroupPrivate::get(model->m_groups[m_compositorGroup])->emitters.insert(this); + if (m_compositorGroup != previousGroup) { + QVector<QQmlChangeSet::Remove> removes; + QVector<QQmlChangeSet::Insert> inserts; + model->m_compositor.transition(previousGroup, m_compositorGroup, &removes, &inserts); + + QQmlChangeSet changeSet; + changeSet.move(removes, inserts); + if (!changeSet.isEmpty()) + emit modelUpdated(changeSet, false); + + if (changeSet.difference() != 0) + emit countChanged(); + } +} + +void QQmlPartsModel::updateFilterGroup( + Compositor::Group group, const QQmlChangeSet &changeSet) +{ + if (!m_inheritGroup) + return; + + m_compositorGroup = group; + QQmlDelegateModelGroupPrivate::get(QQmlDelegateModelPrivate::get(m_model)->m_groups[m_compositorGroup])->emitters.insert(this); + + if (!changeSet.isEmpty()) + emit modelUpdated(changeSet, false); + + if (changeSet.difference() != 0) + emit countChanged(); + + emit filterGroupChanged(); +} + +int QQmlPartsModel::count() const +{ + QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(m_model); + return model->m_delegate + ? model->m_compositor.count(m_compositorGroup) + : 0; +} + +bool QQmlPartsModel::isValid() const +{ + return m_model->isValid(); +} + +QObject *QQmlPartsModel::object(int index, bool asynchronous) +{ + 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; + } + + QObject *object = model->object(m_compositorGroup, index, asynchronous); + + if (QQuickPackage *package = qmlobject_cast<QQuickPackage *>(object)) { + QObject *part = package->part(m_part); + if (!part) + return 0; + m_packaged.insertMulti(part, package); + return part; + } + + model->release(object); + if (!model->m_delegateValidated) { + if (object) + qmlInfo(model->m_delegate) << tr("Delegate component must be Package type."); + model->m_delegateValidated = true; + } + + return 0; +} + +QQmlInstanceModel::ReleaseFlags QQmlPartsModel::release(QObject *item) +{ + QQmlInstanceModel::ReleaseFlags flags = 0; + + QHash<QObject *, QQuickPackage *>::iterator it = m_packaged.find(item); + if (it != m_packaged.end()) { + QQuickPackage *package = *it; + QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(m_model); + flags = model->release(package); + m_packaged.erase(it); + if (!m_packaged.contains(item)) + flags &= ~Referenced; + if (flags & Destroyed) + QQmlDelegateModelPrivate::get(m_model)->emitDestroyingPackage(package); + } + return flags; +} + +QString QQmlPartsModel::stringValue(int index, const QString &role) +{ + return QQmlDelegateModelPrivate::get(m_model)->stringValue(m_compositorGroup, index, role); +} + +void QQmlPartsModel::setWatchedRoles(QList<QByteArray> roles) +{ + QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(m_model); + model->m_adaptorModel.replaceWatchedRoles(m_watchedRoles, roles); + m_watchedRoles = roles; +} + +int QQmlPartsModel::indexOf(QObject *item, QObject *) const +{ + QHash<QObject *, QQuickPackage *>::const_iterator it = m_packaged.find(item); + if (it != m_packaged.end()) { + if (QQmlDelegateModelItem *cacheItem = QQmlDelegateModelItem::dataForObject(*it)) + return cacheItem->groupIndex(m_compositorGroup); + } + return -1; +} + +void QQmlPartsModel::createdPackage(int index, QQuickPackage *package) +{ + emit createdItem(index, package->part(m_part)); +} + +void QQmlPartsModel::initPackage(int index, QQuickPackage *package) +{ + emit initItem(index, package->part(m_part)); +} + +void QQmlPartsModel::destroyingPackage(QQuickPackage *package) +{ + QObject *item = package->part(m_part); + Q_ASSERT(!m_packaged.contains(item)); + emit destroyingItem(item); +} + +void QQmlPartsModel::emitModelUpdated(const QQmlChangeSet &changeSet, bool reset) +{ + emit modelUpdated(changeSet, reset); + if (changeSet.difference() != 0) + emit countChanged(); +} + +//============================================================================ + +v8::Handle<v8::Value> get_change_index(v8::Local<v8::String>, const v8::AccessorInfo &info) +{ + return info.This()->GetInternalField(0); +} + +v8::Handle<v8::Value> get_change_count(v8::Local<v8::String>, const v8::AccessorInfo &info) +{ + return info.This()->GetInternalField(1); +} + +v8::Handle<v8::Value> get_change_moveId(v8::Local<v8::String>, const v8::AccessorInfo &info) +{ + return info.This()->GetInternalField(2); +} + +class QQmlDelegateModelGroupChangeArray : public QV8ObjectResource +{ + V8_RESOURCE_TYPE(ChangeSetArrayType) +public: + QQmlDelegateModelGroupChangeArray(QV8Engine *engine) + : QV8ObjectResource(engine) + { + } + + virtual quint32 count() const = 0; + virtual const QQmlChangeSet::Change &at(int index) const = 0; + + static v8::Handle<v8::Value> get_change(quint32 index, const v8::AccessorInfo &info) + { + QQmlDelegateModelGroupChangeArray *array = v8_resource_cast<QQmlDelegateModelGroupChangeArray>(info.This()); + V8ASSERT_TYPE(array, "Not a valid change array"); + + if (index >= array->count()) + return v8::Undefined(); + + const QQmlChangeSet::Change &change = array->at(index); + + v8::Local<v8::Object> object = engineData(array->engine)->constructorChange->NewInstance(); + object->SetInternalField(0, v8::Int32::New(change.index)); + object->SetInternalField(1, v8::Int32::New(change.count)); + if (change.isMove()) + object->SetInternalField(2, v8::Int32::New(change.moveId)); + + return object; + } + + static v8::Handle<v8::Value> get_length(v8::Local<v8::String>, const v8::AccessorInfo &info) + { + QQmlDelegateModelGroupChangeArray *array = v8_resource_cast<QQmlDelegateModelGroupChangeArray>(info.This()); + V8ASSERT_TYPE(array, "Not a valid change array"); + + return v8::Integer::New(array->count()); + } + + static v8::Local<v8::Function> constructor() + { + v8::Local<v8::FunctionTemplate> changeArray = v8::FunctionTemplate::New(); + changeArray->InstanceTemplate()->SetHasExternalResource(true); + changeArray->InstanceTemplate()->SetIndexedPropertyHandler(get_change); + changeArray->InstanceTemplate()->SetAccessor(v8::String::New("length"), get_length); + return changeArray->GetFunction(); + } +}; + +class QQmlDelegateModelGroupRemoveArray : public QQmlDelegateModelGroupChangeArray +{ +public: + QQmlDelegateModelGroupRemoveArray(QV8Engine *engine, const QVector<QQmlChangeSet::Remove> &changes) + : QQmlDelegateModelGroupChangeArray(engine) + , changes(changes) + { + } + + quint32 count() const { return changes.count(); } + const QQmlChangeSet::Change &at(int index) const { return changes.at(index); } + +private: + QVector<QQmlChangeSet::Remove> changes; +}; + +class QQmlDelegateModelGroupInsertArray : public QQmlDelegateModelGroupChangeArray +{ +public: + QQmlDelegateModelGroupInsertArray(QV8Engine *engine, const QVector<QQmlChangeSet::Insert> &changes) + : QQmlDelegateModelGroupChangeArray(engine) + , changes(changes) + { + } + + quint32 count() const { return changes.count(); } + const QQmlChangeSet::Change &at(int index) const { return changes.at(index); } + +private: + QVector<QQmlChangeSet::Insert> changes; +}; + +QQmlDelegateModelEngineData::QQmlDelegateModelEngineData(QV8Engine *) +{ + strings = qPersistentNew(v8::Array::New(StringCount)); + strings->Set(Model, v8::String::New("model")); + strings->Set(Groups, v8::String::New("groups")); + strings->Set(IsUnresolved, v8::String::New("isUnresolved")); + strings->Set(ItemsIndex, v8::String::New("itemsIndex")); + strings->Set(PersistedItemsIndex, v8::String::New("persistedItemsIndex")); + strings->Set(InItems, v8::String::New("inItems")); + strings->Set(InPersistedItems, v8::String::New("inPersistedItems")); + + v8::Local<v8::FunctionTemplate> change = v8::FunctionTemplate::New(); + change->InstanceTemplate()->SetAccessor(v8::String::New("index"), get_change_index); + change->InstanceTemplate()->SetAccessor(v8::String::New("count"), get_change_count); + change->InstanceTemplate()->SetAccessor(v8::String::New("moveId"), get_change_moveId); + change->InstanceTemplate()->SetInternalFieldCount(3); + constructorChange = qPersistentNew(change->GetFunction()); + constructorChangeArray = qPersistentNew(QQmlDelegateModelGroupChangeArray::constructor()); +} + +QQmlDelegateModelEngineData::~QQmlDelegateModelEngineData() +{ + qPersistentDispose(strings); + qPersistentDispose(constructorChange); + qPersistentDispose(constructorChangeArray); +} + +v8::Local<v8::Object> QQmlDelegateModelEngineData::array( + QV8Engine *engine, const QVector<QQmlChangeSet::Remove> &changes) +{ + v8::Local<v8::Object> array = constructorChangeArray->NewInstance(); + array->SetExternalResource(new QQmlDelegateModelGroupRemoveArray(engine, changes)); + return array; +} + +v8::Local<v8::Object> QQmlDelegateModelEngineData::array( + QV8Engine *engine, const QVector<QQmlChangeSet::Insert> &changes) +{ + v8::Local<v8::Object> array = constructorChangeArray->NewInstance(); + array->SetExternalResource(new QQmlDelegateModelGroupInsertArray(engine, changes)); + return array; +} + +QT_END_NAMESPACE + diff --git a/src/qml/types/qqmldelegatemodel_p.h b/src/qml/types/qqmldelegatemodel_p.h new file mode 100644 index 0000000000..5702c59787 --- /dev/null +++ b/src/qml/types/qqmldelegatemodel_p.h @@ -0,0 +1,234 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** 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 Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/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 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQMLDATAMODEL_P_H +#define QQMLDATAMODEL_P_H + +#include <private/qtqmlglobal_p.h> +#include <private/qqmllistcompositor_p.h> +#include <private/qqmlobjectmodel_p.h> + +#include <QtCore/qabstractitemmodel.h> +#include <QtCore/qstringlist.h> + +#include <private/qv8engine_p.h> +#include <private/qqmlglobal_p.h> + +Q_DECLARE_METATYPE(QModelIndex) + +QT_BEGIN_NAMESPACE + +class QQmlChangeSet; +class QQmlComponent; +class QQuickPackage; +class QQmlV8Function; +class QQmlDelegateModelGroup; +class QQmlDelegateModelAttached; +class QQmlDelegateModelPrivate; + + +class Q_QML_PRIVATE_EXPORT QQmlDelegateModel : public QQmlInstanceModel, public QQmlParserStatus +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QQmlDelegateModel) + + Q_PROPERTY(QVariant model READ model WRITE setModel) + Q_PROPERTY(QQmlComponent *delegate READ delegate WRITE setDelegate) + Q_PROPERTY(QString filterOnGroup READ filterGroup WRITE setFilterGroup NOTIFY filterGroupChanged RESET resetFilterGroup) + Q_PROPERTY(QQmlDelegateModelGroup *items READ items CONSTANT) //TODO : worth renaming? + Q_PROPERTY(QQmlDelegateModelGroup *persistedItems READ persistedItems CONSTANT) + Q_PROPERTY(QQmlListProperty<QQmlDelegateModelGroup> groups READ groups CONSTANT) + Q_PROPERTY(QObject *parts READ parts CONSTANT) + Q_PROPERTY(QVariant rootIndex READ rootIndex WRITE setRootIndex NOTIFY rootIndexChanged) + Q_CLASSINFO("DefaultProperty", "delegate") + Q_INTERFACES(QQmlParserStatus) +public: + QQmlDelegateModel(); + QQmlDelegateModel(QQmlContext *, QObject *parent=0); + virtual ~QQmlDelegateModel(); + + void classBegin(); + void componentComplete(); + + QVariant model() const; + void setModel(const QVariant &); + + QQmlComponent *delegate() const; + void setDelegate(QQmlComponent *); + + QVariant rootIndex() const; + void setRootIndex(const QVariant &root); + + Q_INVOKABLE QVariant modelIndex(int idx) const; + Q_INVOKABLE QVariant parentModelIndex() const; + + int count() const; + bool isValid() const { return delegate() != 0; } + QObject *object(int index, bool asynchronous=false); + ReleaseFlags release(QObject *object); + void cancel(int index); + virtual QString stringValue(int index, const QString &role); + virtual void setWatchedRoles(QList<QByteArray> roles); + + int indexOf(QObject *object, QObject *objectContext) const; + + QString filterGroup() const; + void setFilterGroup(const QString &group); + void resetFilterGroup(); + + QQmlDelegateModelGroup *items(); + QQmlDelegateModelGroup *persistedItems(); + QQmlListProperty<QQmlDelegateModelGroup> groups(); + QObject *parts(); + + bool event(QEvent *); + + static QQmlDelegateModelAttached *qmlAttachedProperties(QObject *obj); + +Q_SIGNALS: + void filterGroupChanged(); + void defaultGroupsChanged(); + void rootIndexChanged(); + +private Q_SLOTS: + void _q_itemsChanged(int index, int count, const QVector<int> &roles); + void _q_itemsInserted(int index, int count); + void _q_itemsRemoved(int index, int count); + void _q_itemsMoved(int from, int to, int count); + void _q_modelReset(); + void _q_rowsInserted(const QModelIndex &,int,int); + void _q_rowsAboutToBeRemoved(const QModelIndex &parent, int begin, int end); + void _q_rowsRemoved(const QModelIndex &,int,int); + void _q_rowsMoved(const QModelIndex &, int, int, const QModelIndex &, int); + void _q_dataChanged(const QModelIndex&,const QModelIndex&,const QVector<int> &); + void _q_layoutChanged(); + +private: + Q_DISABLE_COPY(QQmlDelegateModel) +}; + +class QQmlDelegateModelGroupPrivate; +class Q_QML_PRIVATE_EXPORT QQmlDelegateModelGroup : public QObject +{ + Q_OBJECT + Q_PROPERTY(int count READ count NOTIFY countChanged) + 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(); + + QString name() const; + void setName(const QString &name); + + int count() const; + + bool defaultInclude() const; + void setDefaultInclude(bool include); + + Q_INVOKABLE QQmlV8Handle get(int index); + +public Q_SLOTS: + void insert(QQmlV8Function *); + void create(QQmlV8Function *); + void resolve(QQmlV8Function *); + void remove(QQmlV8Function *); + void addGroups(QQmlV8Function *); + void removeGroups(QQmlV8Function *); + void setGroups(QQmlV8Function *); + void move(QQmlV8Function *); + +Q_SIGNALS: + void countChanged(); + void nameChanged(); + void defaultIncludeChanged(); + void changed(const QQmlV8Handle &removed, const QQmlV8Handle &inserted); +private: + Q_DECLARE_PRIVATE(QQmlDelegateModelGroup) +}; + +class QQmlDelegateModelItem; +class QQmlDelegateModelAttachedMetaObject; +class QQmlDelegateModelAttached : public QObject +{ + Q_OBJECT + Q_PROPERTY(QQmlDelegateModel *model READ model CONSTANT) + Q_PROPERTY(QStringList groups READ groups WRITE setGroups NOTIFY groupsChanged) + Q_PROPERTY(bool isUnresolved READ isUnresolved NOTIFY unresolvedChanged) +public: + QQmlDelegateModelAttached(QObject *parent); + QQmlDelegateModelAttached(QQmlDelegateModelItem *cacheItem, QObject *parent); + ~QQmlDelegateModelAttached() {} + + void setCacheItem(QQmlDelegateModelItem *item); + + QQmlDelegateModel *model() const; + + QStringList groups() const; + void setGroups(const QStringList &groups); + + bool isUnresolved() const; + + void emitChanges(); + + void emitUnresolvedChanged() { emit unresolvedChanged(); } + +Q_SIGNALS: + void groupsChanged(); + void unresolvedChanged(); + +public: + QQmlDelegateModelItem *m_cacheItem; + int m_previousGroups; + int m_currentIndex[QQmlListCompositor::MaximumGroupCount]; + int m_previousIndex[QQmlListCompositor::MaximumGroupCount]; + + friend class QQmlDelegateModelAttachedMetaObject; +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQmlDelegateModel) +QML_DECLARE_TYPEINFO(QQmlDelegateModel, QML_HAS_ATTACHED_PROPERTIES) +QML_DECLARE_TYPE(QQmlDelegateModelGroup) + +#endif // QQMLDATAMODEL_P_H diff --git a/src/qml/types/qqmldelegatemodel_p_p.h b/src/qml/types/qqmldelegatemodel_p_p.h new file mode 100644 index 0000000000..68242f433d --- /dev/null +++ b/src/qml/types/qqmldelegatemodel_p_p.h @@ -0,0 +1,411 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** 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 Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/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 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQMLDATAMODEL_P_P_H +#define QQMLDATAMODEL_P_P_H + +#include "qqmldelegatemodel_p.h" + + +#include <QtQml/qqmlcontext.h> +#include <QtQml/qqmlincubator.h> + +#include <private/qqmladaptormodel_p.h> +#include <private/qqmlopenmetaobject_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. +// + +QT_BEGIN_NAMESPACE + +typedef QQmlListCompositor Compositor; + +class QQmlDelegateModelAttachedMetaObject; + +class QQmlDelegateModelItemMetaType : public QQmlRefCount +{ +public: + QQmlDelegateModelItemMetaType(QV8Engine *engine, QQmlDelegateModel *model, const QStringList &groupNames); + ~QQmlDelegateModelItemMetaType(); + + void initializeMetaObject(); + void initializeConstructor(); + + int parseGroups(const QStringList &groupNames) const; + int parseGroups(const v8::Local<v8::Value> &groupNames) const; + + static void release_index(v8::Persistent<v8::Value> object, void *parameter); + static void release_model(v8::Persistent<v8::Value> object, void *parameter); + + static v8::Handle<v8::Value> get_model(v8::Local<v8::String>, const v8::AccessorInfo &info); + static v8::Handle<v8::Value> get_groups(v8::Local<v8::String>, const v8::AccessorInfo &info); + static void set_groups( + v8::Local<v8::String>, v8::Local<v8::Value> value, const v8::AccessorInfo &info); + static v8::Handle<v8::Value> get_member(v8::Local<v8::String>, const v8::AccessorInfo &info); + static void set_member( + v8::Local<v8::String>, v8::Local<v8::Value> value, const v8::AccessorInfo &info); + static v8::Handle<v8::Value> get_index(v8::Local<v8::String>, const v8::AccessorInfo &info); + + QQmlGuard<QQmlDelegateModel> model; + const int groupCount; + QV8Engine * const v8Engine; + QQmlDelegateModelAttachedMetaObject *metaObject; + const QStringList groupNames; + v8::Persistent<v8::ObjectTemplate> constructor; +}; + +class QQmlAdaptorModel; +class QQDMIncubationTask; + +class QQmlDelegateModelItem : public QObject, public QV8ObjectResource +{ + Q_OBJECT + Q_PROPERTY(int index READ modelIndex NOTIFY modelIndexChanged) + Q_PROPERTY(QObject *model READ modelObject CONSTANT) + V8_RESOURCE_TYPE(VisualDataItemType) +public: + QQmlDelegateModelItem(QQmlDelegateModelItemMetaType *metaType, int modelIndex); + ~QQmlDelegateModelItem(); + + void referenceObject() { ++objectRef; } + bool releaseObject() { return --objectRef == 0 && !(groups & Compositor::PersistedFlag); } + bool isObjectReferenced() const { return objectRef != 0 || (groups & Compositor::PersistedFlag); } + + bool isReferenced() const { + return scriptRef + || incubationTask + || ((groups & Compositor::UnresolvedFlag) && (groups & Compositor::GroupMask)); + } + + void Dispose(); + + QObject *modelObject() { return this; } + + void incubateObject( + QQmlComponent *component, + QQmlEngine *engine, + QQmlContextData *context, + QQmlContextData *forContext); + void destroyObject(); + + static QQmlDelegateModelItem *dataForObject(QObject *object); + + int groupIndex(Compositor::Group group); + + int modelIndex() const { return index; } + void setModelIndex(int idx) { index = idx; emit modelIndexChanged(); } + + virtual v8::Handle<v8::Value> get() { return engine->newQObject(this); } + + virtual void setValue(const QString &role, const QVariant &value) { Q_UNUSED(role); Q_UNUSED(value); } + virtual bool resolveIndex(const QQmlAdaptorModel &, int) { return false; } + + QQmlDelegateModelItemMetaType * const metaType; + QQmlContextData *contextData; + QObject *object; + QQmlDelegateModelAttached *attached; + QQDMIncubationTask *incubationTask; + int objectRef; + int scriptRef; + int groups; + int index; + + +Q_SIGNALS: + void modelIndexChanged(); + +protected: + void objectDestroyed(QObject *); +}; + + +class QQmlDelegateModelPrivate; +class QQDMIncubationTask : public QQmlIncubator +{ +public: + QQDMIncubationTask(QQmlDelegateModelPrivate *l, IncubationMode mode) + : QQmlIncubator(mode) + , incubating(0) + , vdm(l) {} + + virtual void statusChanged(Status); + virtual void setInitialState(QObject *); + + QQmlDelegateModelItem *incubating; + QQmlDelegateModelPrivate *vdm; + int index[QQmlListCompositor::MaximumGroupCount]; +}; + + +class QQmlDelegateModelGroupEmitter +{ +public: + virtual ~QQmlDelegateModelGroupEmitter() {} + virtual void emitModelUpdated(const QQmlChangeSet &changeSet, bool reset) = 0; + virtual void createdPackage(int, QQuickPackage *) {} + virtual void initPackage(int, QQuickPackage *) {} + virtual void destroyingPackage(QQuickPackage *) {} + + QIntrusiveListNode emitterNode; +}; + +typedef QIntrusiveList<QQmlDelegateModelGroupEmitter, &QQmlDelegateModelGroupEmitter::emitterNode> QQmlDelegateModelGroupEmitterList; + +class QQmlDelegateModelGroupPrivate : public QObjectPrivate +{ +public: + Q_DECLARE_PUBLIC(QQmlDelegateModelGroup) + + QQmlDelegateModelGroupPrivate() : group(Compositor::Cache), defaultInclude(false) {} + + static QQmlDelegateModelGroupPrivate *get(QQmlDelegateModelGroup *group) { + return static_cast<QQmlDelegateModelGroupPrivate *>(QObjectPrivate::get(group)); } + + void setModel(QQmlDelegateModel *model, Compositor::Group group); + bool isChangedConnected(); + void emitChanges(QV8Engine *engine); + void emitModelUpdated(bool reset); + + void createdPackage(int index, QQuickPackage *package); + void initPackage(int index, QQuickPackage *package); + void destroyingPackage(QQuickPackage *package); + + bool parseIndex(const v8::Local<v8::Value> &value, int *index, Compositor::Group *group) const; + bool parseGroupArgs( + QQmlV8Function *args, Compositor::Group *group, int *index, int *count, int *groups) const; + + Compositor::Group group; + QQmlGuard<QQmlDelegateModel> model; + QQmlDelegateModelGroupEmitterList emitters; + QQmlChangeSet changeSet; + QString name; + bool defaultInclude; +}; + +class QQmlDelegateModelParts; + +class QQmlDelegateModelPrivate : public QObjectPrivate, public QQmlDelegateModelGroupEmitter +{ + Q_DECLARE_PUBLIC(QQmlDelegateModel) +public: + QQmlDelegateModelPrivate(QQmlContext *); + ~QQmlDelegateModelPrivate(); + + static QQmlDelegateModelPrivate *get(QQmlDelegateModel *m) { + return static_cast<QQmlDelegateModelPrivate *>(QObjectPrivate::get(m)); + } + + void init(); + void connectModel(QQmlAdaptorModel *model); + + QObject *object(Compositor::Group group, int index, bool asynchronous); + QQmlDelegateModel::ReleaseFlags release(QObject *object); + QString stringValue(Compositor::Group group, int index, const QString &name); + void emitCreatedPackage(QQDMIncubationTask *incubationTask, QQuickPackage *package); + void emitInitPackage(QQDMIncubationTask *incubationTask, QQuickPackage *package); + void emitCreatedItem(QQDMIncubationTask *incubationTask, QObject *item) { + emit q_func()->createdItem(incubationTask->index[m_compositorGroup], item); } + void emitInitItem(QQDMIncubationTask *incubationTask, QObject *item) { + emit q_func()->initItem(incubationTask->index[m_compositorGroup], item); } + void emitDestroyingPackage(QQuickPackage *package); + void emitDestroyingItem(QObject *item) { emit q_func()->destroyingItem(item); } + void removeCacheItem(QQmlDelegateModelItem *cacheItem); + + void updateFilterGroup(); + + void addGroups(Compositor::iterator from, int count, Compositor::Group group, int groupFlags); + void removeGroups(Compositor::iterator from, int count, Compositor::Group group, int groupFlags); + void setGroups(Compositor::iterator from, int count, Compositor::Group group, int groupFlags); + + void itemsInserted( + const QVector<Compositor::Insert> &inserts, + QVarLengthArray<QVector<QQmlChangeSet::Insert>, Compositor::MaximumGroupCount> *translatedInserts, + QHash<int, QList<QQmlDelegateModelItem *> > *movedItems = 0); + void itemsInserted(const QVector<Compositor::Insert> &inserts); + void itemsRemoved( + const QVector<Compositor::Remove> &removes, + QVarLengthArray<QVector<QQmlChangeSet::Remove>, Compositor::MaximumGroupCount> *translatedRemoves, + QHash<int, QList<QQmlDelegateModelItem *> > *movedItems = 0); + 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); + template <typename T> static v8::Local<v8::Array> buildChangeList(const QVector<T> &changes); + void emitChanges(); + void emitModelUpdated(const QQmlChangeSet &changeSet, bool reset); + + bool insert(Compositor::insert_iterator &before, const v8::Local<v8::Object> &object, int groups); + + 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); + + void releaseIncubator(QQDMIncubationTask *incubationTask); + void incubatorStatusChanged(QQDMIncubationTask *incubationTask, QQmlIncubator::Status status); + void setInitialState(QQDMIncubationTask *incubationTask, QObject *o); + + QQmlAdaptorModel m_adaptorModel; + QQmlListCompositor m_compositor; + QQmlComponent *m_delegate; + QQmlDelegateModelItemMetaType *m_cacheMetaType; + QQmlContext *m_context; + QQmlDelegateModelParts *m_parts; + QQmlDelegateModelGroupEmitterList m_pendingParts; + + QList<QQmlDelegateModelItem *> m_cache; + QList<QQDMIncubationTask *> m_finishedIncubating; + QList<QByteArray> m_watchedRoles; + + QString m_filterGroup; + + int m_count; + int m_groupCount; + + QQmlListCompositor::Group m_compositorGroup; + bool m_complete : 1; + bool m_delegateValidated : 1; + bool m_reset : 1; + bool m_transaction : 1; + bool m_incubatorCleanupScheduled : 1; + + union { + struct { + QQmlDelegateModelGroup *m_cacheItems; + QQmlDelegateModelGroup *m_items; + QQmlDelegateModelGroup *m_persistedItems; + }; + QQmlDelegateModelGroup *m_groups[Compositor::MaximumGroupCount]; + }; +}; + +class QQmlPartsModel : public QQmlInstanceModel, public QQmlDelegateModelGroupEmitter +{ + 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(); + + QString filterGroup() const; + void setFilterGroup(const QString &group); + void resetFilterGroup(); + void updateFilterGroup(); + void updateFilterGroup(Compositor::Group group, const QQmlChangeSet &changeSet); + + int count() const; + bool isValid() const; + QObject *object(int index, bool asynchronous=false); + ReleaseFlags release(QObject *item); + QString stringValue(int index, const QString &role); + QList<QByteArray> watchedRoles() const { return m_watchedRoles; } + void setWatchedRoles(QList<QByteArray> roles); + + int indexOf(QObject *item, QObject *objectContext) const; + + void emitModelUpdated(const QQmlChangeSet &changeSet, bool reset); + + void createdPackage(int index, QQuickPackage *package); + void initPackage(int index, QQuickPackage *package); + void destroyingPackage(QQuickPackage *package); + +Q_SIGNALS: + void filterGroupChanged(); + +private: + QQmlDelegateModel *m_model; + QHash<QObject *, QQuickPackage *> m_packaged; + QString m_part; + QString m_filterGroup; + QList<QByteArray> m_watchedRoles; + Compositor::Group m_compositorGroup; + bool m_inheritGroup; +}; + +class QMetaPropertyBuilder; + +class QQmlDelegateModelPartsMetaObject : public QQmlOpenMetaObject +{ +public: + QQmlDelegateModelPartsMetaObject(QObject *parent) + : QQmlOpenMetaObject(parent) {} + + virtual void propertyCreated(int, QMetaPropertyBuilder &); + virtual QVariant initialValue(int); +}; + +class QQmlDelegateModelParts : public QObject +{ +Q_OBJECT +public: + QQmlDelegateModelParts(QQmlDelegateModel *parent); + + QQmlDelegateModel *model; + QList<QQmlPartsModel *> models; +}; + +class QQmlDelegateModelAttachedMetaObject : public QAbstractDynamicMetaObject, public QQmlRefCount +{ +public: + QQmlDelegateModelAttachedMetaObject( + QQmlDelegateModelItemMetaType *metaType, QMetaObject *metaObject); + ~QQmlDelegateModelAttachedMetaObject(); + + void objectDestroyed(QObject *); + int metaCall(QObject *, QMetaObject::Call, int _id, void **); + +private: + QQmlDelegateModelItemMetaType * const metaType; + QMetaObject * const metaObject; + const int memberPropertyOffset; + const int indexPropertyOffset; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/qml/types/qqmllistmodel.cpp b/src/qml/types/qqmllistmodel.cpp new file mode 100644 index 0000000000..9609e91b1c --- /dev/null +++ b/src/qml/types/qqmllistmodel.cpp @@ -0,0 +1,2588 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** 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 Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/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 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qqmllistmodel_p_p.h" +#include "qqmllistmodelworkeragent_p.h" +#include <private/qqmlopenmetaobject_p.h> +#include <private/qqmljsast_p.h> +#include <private/qqmljsengine_p.h> + + +#include <private/qqmlcustomparser_p.h> +#include <private/qqmlscript_p.h> +#include <private/qqmlengine_p.h> +#include <qqmlcontext.h> +#include <qqmlinfo.h> + +#include <QtCore/qdebug.h> +#include <QtCore/qstack.h> +#include <QXmlStreamReader> + +QT_BEGIN_NAMESPACE + +// Set to 1024 as a debugging aid - easier to distinguish uids from indices of elements/models. +enum { MIN_LISTMODEL_UID = 1024 }; + +static QAtomicInt uidCounter(MIN_LISTMODEL_UID); + +template <typename T> +static bool isMemoryUsed(const char *mem) +{ + for (size_t i=0 ; i < sizeof(T) ; ++i) { + if (mem[i] != 0) + return true; + } + + return false; +} + +static QString roleTypeName(ListLayout::Role::DataType t) +{ + QString result; + const char *roleTypeNames[] = { "String", "Number", "Bool", "List", "QObject", "VariantMap", "DateTime" }; + + if (t > ListLayout::Role::Invalid && t < ListLayout::Role::MaxDataType) + result = QString::fromLatin1(roleTypeNames[t]); + + return result; +} + +const ListLayout::Role &ListLayout::getRoleOrCreate(const QString &key, Role::DataType type) +{ + QStringHash<Role *>::Node *node = roleHash.findNode(key); + if (node) { + const Role &r = *node->value; + if (type != r.type) + qmlInfo(0) << QString::fromLatin1("Can't assign to existing role '%1' of different type [%2 -> %3]").arg(r.name).arg(roleTypeName(type)).arg(roleTypeName(r.type)); + return r; + } + + return createRole(key, type); +} + +const ListLayout::Role &ListLayout::getRoleOrCreate(v8::Handle<v8::String> key, Role::DataType type) +{ + QHashedV8String hashedKey(key); + QStringHash<Role *>::Node *node = roleHash.findNode(hashedKey); + if (node) { + const Role &r = *node->value; + if (type != r.type) + qmlInfo(0) << QString::fromLatin1("Can't assign to existing role '%1' of different type [%2 -> %3]").arg(r.name).arg(roleTypeName(type)).arg(roleTypeName(r.type)); + return r; + } + + QString qkey; + qkey.resize(key->Length()); + key->Write(reinterpret_cast<uint16_t*>(qkey.data())); + + return createRole(qkey, type); +} + +const ListLayout::Role &ListLayout::createRole(const QString &key, ListLayout::Role::DataType type) +{ + const int dataSizes[] = { sizeof(QString), sizeof(double), sizeof(bool), sizeof(ListModel *), sizeof(QQmlGuard<QObject>), sizeof(QVariantMap), sizeof(QDateTime) }; + const int dataAlignments[] = { sizeof(QString), sizeof(double), sizeof(bool), sizeof(ListModel *), sizeof(QObject *), sizeof(QVariantMap), sizeof(QDateTime) }; + + Role *r = new Role; + r->name = key; + r->type = type; + + if (type == Role::List) { + r->subLayout = new ListLayout; + } else { + r->subLayout = 0; + } + + int dataSize = dataSizes[type]; + int dataAlignment = dataAlignments[type]; + + int dataOffset = (currentBlockOffset + dataAlignment-1) & ~(dataAlignment-1); + if (dataOffset + dataSize > ListElement::BLOCK_SIZE) { + r->blockIndex = ++currentBlock; + r->blockOffset = 0; + currentBlockOffset = dataSize; + } else { + r->blockIndex = currentBlock; + r->blockOffset = dataOffset; + currentBlockOffset = dataOffset + dataSize; + } + + int roleIndex = roles.count(); + r->index = roleIndex; + + roles.append(r); + roleHash.insert(key, r); + + return *r; +} + +ListLayout::ListLayout(const ListLayout *other) : currentBlock(0), currentBlockOffset(0) +{ + for (int i=0 ; i < other->roles.count() ; ++i) { + Role *role = new Role(other->roles[i]); + roles.append(role); + roleHash.insert(role->name, role); + } + currentBlockOffset = other->currentBlockOffset; + currentBlock = other->currentBlock; +} + +ListLayout::~ListLayout() +{ + for (int i=0 ; i < roles.count() ; ++i) { + delete roles[i]; + } +} + +void ListLayout::sync(ListLayout *src, ListLayout *target) +{ + int roleOffset = target->roles.count(); + int newRoleCount = src->roles.count() - roleOffset; + + for (int i=0 ; i < newRoleCount ; ++i) { + Role *role = new Role(src->roles[roleOffset + i]); + target->roles.append(role); + target->roleHash.insert(role->name, role); + } + + target->currentBlockOffset = src->currentBlockOffset; + target->currentBlock = src->currentBlock; +} + +ListLayout::Role::Role(const Role *other) +{ + name = other->name; + type = other->type; + blockIndex = other->blockIndex; + blockOffset = other->blockOffset; + index = other->index; + if (other->subLayout) + subLayout = new ListLayout(other->subLayout); + else + subLayout = 0; +} + +ListLayout::Role::~Role() +{ + delete subLayout; +} + +const ListLayout::Role *ListLayout::getRoleOrCreate(const QString &key, const QVariant &data) +{ + Role::DataType type; + + 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; + default: type = Role::Invalid; break; + } + + if (type == Role::Invalid) { + qmlInfo(0) << "Can't create role for unsupported data type"; + return 0; + } + + return &getRoleOrCreate(key, type); +} + +const ListLayout::Role *ListLayout::getExistingRole(const QString &key) +{ + Role *r = 0; + QStringHash<Role *>::Node *node = roleHash.findNode(key); + if (node) + r = node->value; + return r; +} + +const ListLayout::Role *ListLayout::getExistingRole(v8::Handle<v8::String> key) +{ + Role *r = 0; + QHashedV8String hashedKey(key); + QStringHash<Role *>::Node *node = roleHash.findNode(hashedKey); + if (node) + r = node->value; + return r; +} + +ModelObject *ListModel::getOrCreateModelObject(QQmlListModel *model, int elementIndex) +{ + ListElement *e = elements[elementIndex]; + if (e->m_objectCache == 0) { + e->m_objectCache = new ModelObject(model, elementIndex); + } + return e->m_objectCache; +} + +void ListModel::sync(ListModel *src, ListModel *target, QHash<int, ListModel *> *targetModelHash) +{ + // Sanity check + target->m_uid = src->m_uid; + if (targetModelHash) + targetModelHash->insert(target->m_uid, target); + + // Build hash of elements <-> uid for each of the lists + QHash<int, ElementSync> elementHash; + for (int i=0 ; i < target->elements.count() ; ++i) { + ListElement *e = target->elements.at(i); + int uid = e->getUid(); + ElementSync sync; + sync.target = e; + elementHash.insert(uid, sync); + } + for (int i=0 ; i < src->elements.count() ; ++i) { + ListElement *e = src->elements.at(i); + int uid = e->getUid(); + + QHash<int, ElementSync>::iterator it = elementHash.find(uid); + if (it == elementHash.end()) { + ElementSync sync; + sync.src = e; + elementHash.insert(uid, sync); + } else { + ElementSync &sync = it.value(); + sync.src = e; + } + } + + // 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) { + s.target->destroy(target->m_layout); + target->elements.removeOne(s.target); + delete s.target; + } + ++it; + } + + // Sync the layouts + ListLayout::sync(src->m_layout, target->m_layout); + + // Clear the target list, and append in correct order from the source + target->elements.clear(); + 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(); + ListElement *targetElement = s.target; + if (targetElement == 0) { + targetElement = new ListElement(srcElement->getUid()); + } + ListElement::sync(srcElement, src->m_layout, targetElement, target->m_layout, targetModelHash); + target->elements.append(targetElement); + } + + target->updateCacheIndices(); + + // Update values stored in target meta objects + for (int i=0 ; i < target->elements.count() ; ++i) { + ListElement *e = target->elements[i]; + if (e->m_objectCache) + e->m_objectCache->updateValues(); + } +} + +ListModel::ListModel(ListLayout *layout, QQmlListModel *modelCache, int uid) : 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; + if (m_modelCache && m_modelCache->m_primary == false) + delete m_modelCache; + m_modelCache = 0; +} + +int ListModel::appendElement() +{ + int elementIndex = elements.count(); + newElement(elementIndex); + return elementIndex; +} + +void ListModel::insertElement(int index) +{ + newElement(index); + updateCacheIndices(); +} + +void ListModel::move(int from, int to, int n) +{ + if (from > to) { + // Only move forwards - flip if backwards moving + int tfrom = from; + int tto = to; + from = tto; + to = tto+n; + n = tfrom-tto; + } + + QPODVector<ListElement *, 4> store; + for (int i=0 ; i < (to-from) ; ++i) + store.append(elements[from+n+i]); + for (int i=0 ; i < n ; ++i) + store.append(elements[from+i]); + for (int i=0 ; i < store.count() ; ++i) + elements[from+i] = store[i]; + + updateCacheIndices(); +} + +void ListModel::newElement(int index) +{ + ListElement *e = new ListElement; + elements.insert(index, e); +} + +void ListModel::updateCacheIndices() +{ + for (int i=0 ; i < elements.count() ; ++i) { + ListElement *e = elements.at(i); + if (e->m_objectCache) { + e->m_objectCache->m_elementIndex = i; + } + } +} + +QVariant ListModel::getProperty(int elementIndex, int roleIndex, const QQmlListModel *owner, QV8Engine *eng) +{ + ListElement *e = elements[elementIndex]; + const ListLayout::Role &r = m_layout->getExistingRole(roleIndex); + return e->getProperty(r, owner, eng); +} + +ListModel *ListModel::getListProperty(int elementIndex, const ListLayout::Role &role) +{ + ListElement *e = elements[elementIndex]; + return e->getListProperty(role); +} + +void ListModel::set(int elementIndex, v8::Handle<v8::Object> object, QVector<int> *roles, QV8Engine *eng) +{ + ListElement *e = elements[elementIndex]; + + v8::Local<v8::Array> propertyNames = object->GetPropertyNames(); + int propertyCount = propertyNames->Length(); + + for (int i=0 ; i < propertyCount ; ++i) { + v8::Local<v8::String> propertyName = propertyNames->Get(i)->ToString(); + v8::Local<v8::Value> propertyValue = object->Get(propertyName); + + // Check if this key exists yet + int roleIndex = -1; + + // Add the value now + if (propertyValue->IsString()) { + const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::String); + v8::Handle<v8::String> jsString = propertyValue->ToString(); + QString qstr; + qstr.resize(jsString->Length()); + jsString->Write(reinterpret_cast<uint16_t*>(qstr.data())); + roleIndex = e->setStringProperty(r, qstr); + } else if (propertyValue->IsNumber()) { + const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::Number); + roleIndex = e->setDoubleProperty(r, propertyValue->NumberValue()); + } else if (propertyValue->IsArray()) { + const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::List); + ListModel *subModel = new ListModel(r.subLayout, 0, -1); + + v8::Handle<v8::Array> subArray = v8::Handle<v8::Array>::Cast(propertyValue); + int arrayLength = subArray->Length(); + for (int j=0 ; j < arrayLength ; ++j) { + v8::Handle<v8::Object> subObject = subArray->Get(j)->ToObject(); + subModel->append(subObject, eng); + } + + roleIndex = e->setListProperty(r, subModel); + } else if (propertyValue->IsBoolean()) { + const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::Bool); + roleIndex = e->setBoolProperty(r, propertyValue->BooleanValue()); + } else if (propertyValue->IsDate()) { + const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::DateTime); + QDateTime dt = QV8Engine::qtDateTimeFromJsDate(v8::Handle<v8::Date>::Cast(propertyValue)->NumberValue()); + roleIndex = e->setDateTimeProperty(r, dt); + } else if (propertyValue->IsObject()) { + QV8ObjectResource *r = (QV8ObjectResource *) propertyValue->ToObject()->GetExternalResource(); + if (r && r->resourceType() == QV8ObjectResource::QObjectType) { + QObject *o = QV8QObjectWrapper::toQObject(r); + const ListLayout::Role &role = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::QObject); + if (role.type == ListLayout::Role::QObject) + roleIndex = e->setQObjectProperty(role, o); + } else { + const ListLayout::Role &role = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::VariantMap); + if (role.type == ListLayout::Role::VariantMap) + roleIndex = e->setVariantMapProperty(role, propertyValue->ToObject(), eng); + } + } else if (propertyValue.IsEmpty() || propertyValue->IsUndefined() || propertyValue->IsNull()) { + const ListLayout::Role *r = m_layout->getExistingRole(propertyName); + if (r) + e->clearProperty(*r); + } + + if (roleIndex != -1) + roles->append(roleIndex); + } + + if (e->m_objectCache) { + e->m_objectCache->updateValues(*roles); + } +} + +void ListModel::set(int elementIndex, v8::Handle<v8::Object> object, QV8Engine *eng) +{ + ListElement *e = elements[elementIndex]; + + v8::Local<v8::Array> propertyNames = object->GetPropertyNames(); + int propertyCount = propertyNames->Length(); + + for (int i=0 ; i < propertyCount ; ++i) { + v8::Local<v8::String> propertyName = propertyNames->Get(i)->ToString(); + v8::Local<v8::Value> propertyValue = object->Get(propertyName); + + // Add the value now + if (propertyValue->IsString()) { + const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::String); + if (r.type == ListLayout::Role::String) { + v8::Handle<v8::String> jsString = propertyValue->ToString(); + QString qstr; + qstr.resize(jsString->Length()); + jsString->Write(reinterpret_cast<uint16_t*>(qstr.data())); + e->setStringPropertyFast(r, qstr); + } + } else if (propertyValue->IsNumber()) { + const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::Number); + if (r.type == ListLayout::Role::Number) { + e->setDoublePropertyFast(r, propertyValue->NumberValue()); + } + } else if (propertyValue->IsArray()) { + 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); + + v8::Handle<v8::Array> subArray = v8::Handle<v8::Array>::Cast(propertyValue); + int arrayLength = subArray->Length(); + for (int j=0 ; j < arrayLength ; ++j) { + v8::Handle<v8::Object> subObject = subArray->Get(j)->ToObject(); + subModel->append(subObject, eng); + } + + e->setListPropertyFast(r, subModel); + } + } else if (propertyValue->IsBoolean()) { + const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::Bool); + if (r.type == ListLayout::Role::Bool) { + e->setBoolPropertyFast(r, propertyValue->BooleanValue()); + } + } else if (propertyValue->IsDate()) { + const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::DateTime); + if (r.type == ListLayout::Role::DateTime) { + QDateTime dt = QV8Engine::qtDateTimeFromJsDate(v8::Handle<v8::Date>::Cast(propertyValue)->NumberValue()); + e->setDateTimePropertyFast(r, dt); + } + } else if (propertyValue->IsObject()) { + QV8ObjectResource *r = (QV8ObjectResource *) propertyValue->ToObject()->GetExternalResource(); + if (r && r->resourceType() == QV8ObjectResource::QObjectType) { + QObject *o = QV8QObjectWrapper::toQObject(r); + const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::QObject); + if (r.type == ListLayout::Role::QObject) + e->setQObjectPropertyFast(r, o); + } else { + const ListLayout::Role &role = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::VariantMap); + if (role.type == ListLayout::Role::VariantMap) + e->setVariantMapFast(role, propertyValue->ToObject(), eng); + } + } else if (propertyValue.IsEmpty() || propertyValue->IsUndefined() || propertyValue->IsNull()) { + const ListLayout::Role *r = m_layout->getExistingRole(propertyName); + if (r) + e->clearProperty(*r); + } + } +} + +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) +{ + for (int i=0 ; i < count ; ++i) { + elements[index+i]->destroy(m_layout); + delete elements[index+i]; + } + elements.remove(index, count); + updateCacheIndices(); +} + +void ListModel::insert(int elementIndex, v8::Handle<v8::Object> object, QV8Engine *eng) +{ + insertElement(elementIndex); + set(elementIndex, object, eng); +} + +int ListModel::append(v8::Handle<v8::Object> object, QV8Engine *eng) +{ + int elementIndex = appendElement(); + set(elementIndex, object, eng); + return elementIndex; +} + +int ListModel::setOrCreateProperty(int elementIndex, const QString &key, const QVariant &data) +{ + int roleIndex = -1; + + if (elementIndex >= 0 && elementIndex < elements.count()) { + ListElement *e = elements[elementIndex]; + + const ListLayout::Role *r = m_layout->getRoleOrCreate(key, data); + if (r) { + roleIndex = e->setVariantProperty(*r, data); + + if (roleIndex != -1 && e->m_objectCache) { + QVector<int> roles; + roles << roleIndex; + e->m_objectCache->updateValues(roles); + } + } + } + + return roleIndex; +} + +int ListModel::setExistingProperty(int elementIndex, const QString &key, v8::Handle<v8::Value> data, QV8Engine *eng) +{ + int roleIndex = -1; + + if (elementIndex >= 0 && elementIndex < elements.count()) { + ListElement *e = elements[elementIndex]; + const ListLayout::Role *r = m_layout->getExistingRole(key); + if (r) + roleIndex = e->setJsProperty(*r, data, eng); + } + + return roleIndex; +} + +inline char *ListElement::getPropertyMemory(const ListLayout::Role &role) +{ + ListElement *e = this; + int blockIndex = 0; + while (blockIndex < role.blockIndex) { + if (e->next == 0) { + e->next = new ListElement; + e->next->uid = uid; + } + e = e->next; + ++blockIndex; + } + + char *mem = &e->data[role.blockOffset]; + return mem; +} + +QString *ListElement::getStringProperty(const ListLayout::Role &role) +{ + char *mem = getPropertyMemory(role); + QString *s = reinterpret_cast<QString *>(mem); + return s->data_ptr() ? s : 0; +} + +QObject *ListElement::getQObjectProperty(const ListLayout::Role &role) +{ + char *mem = getPropertyMemory(role); + QQmlGuard<QObject> *o = reinterpret_cast<QQmlGuard<QObject> *>(mem); + return o->data(); +} + +QVariantMap *ListElement::getVariantMapProperty(const ListLayout::Role &role) +{ + QVariantMap *map = 0; + + char *mem = getPropertyMemory(role); + if (isMemoryUsed<QVariantMap>(mem)) + map = reinterpret_cast<QVariantMap *>(mem); + + return map; +} + +QDateTime *ListElement::getDateTimeProperty(const ListLayout::Role &role) +{ + QDateTime *dt = 0; + + char *mem = getPropertyMemory(role); + if (isMemoryUsed<QDateTime>(mem)) + dt = reinterpret_cast<QDateTime *>(mem); + + return dt; +} + +QQmlGuard<QObject> *ListElement::getGuardProperty(const ListLayout::Role &role) +{ + char *mem = getPropertyMemory(role); + + bool existingGuard = false; + for (size_t i=0 ; i < sizeof(QQmlGuard<QObject>) ; ++i) { + if (mem[i] != 0) { + existingGuard = true; + break; + } + } + + QQmlGuard<QObject> *o = 0; + + if (existingGuard) + o = reinterpret_cast<QQmlGuard<QObject> *>(mem); + + return o; +} + +ListModel *ListElement::getListProperty(const ListLayout::Role &role) +{ + char *mem = getPropertyMemory(role); + ListModel **value = reinterpret_cast<ListModel **>(mem); + return *value; +} + +QVariant ListElement::getProperty(const ListLayout::Role &role, const QQmlListModel *owner, QV8Engine *eng) +{ + char *mem = getPropertyMemory(role); + + QVariant data; + + switch (role.type) { + case ListLayout::Role::Number: + { + double *value = reinterpret_cast<double *>(mem); + data = *value; + } + break; + case ListLayout::Role::String: + { + QString *value = reinterpret_cast<QString *>(mem); + if (value->data_ptr() != 0) + data = *value; + } + break; + case ListLayout::Role::Bool: + { + bool *value = reinterpret_cast<bool *>(mem); + data = *value; + } + break; + case ListLayout::Role::List: + { + ListModel **value = reinterpret_cast<ListModel **>(mem); + ListModel *model = *value; + + if (model) { + if (model->m_modelCache == 0) { + model->m_modelCache = new QQmlListModel(owner, model, eng); + QQmlEngine::setContextForObject(model->m_modelCache, QQmlEngine::contextForObject(owner)); + } + + QObject *object = model->m_modelCache; + data = QVariant::fromValue(object); + } + } + break; + case ListLayout::Role::QObject: + { + QQmlGuard<QObject> *guard = reinterpret_cast<QQmlGuard<QObject> *>(mem); + QObject *object = guard->data(); + if (object) + data = QVariant::fromValue(object); + } + break; + case ListLayout::Role::VariantMap: + { + if (isMemoryUsed<QVariantMap>(mem)) { + QVariantMap *map = reinterpret_cast<QVariantMap *>(mem); + data = *map; + } + } + break; + case ListLayout::Role::DateTime: + { + if (isMemoryUsed<QDateTime>(mem)) { + QDateTime *dt = reinterpret_cast<QDateTime *>(mem); + data = *dt; + } + } + break; + default: + break; + } + + return data; +} + +int ListElement::setStringProperty(const ListLayout::Role &role, const QString &s) +{ + int roleIndex = -1; + + if (role.type == ListLayout::Role::String) { + char *mem = getPropertyMemory(role); + QString *c = reinterpret_cast<QString *>(mem); + bool changed; + if (c->data_ptr() == 0) { + new (mem) QString(s); + changed = true; + } else { + changed = c->compare(s) != 0; + *c = s; + } + if (changed) + roleIndex = role.index; + } + + return roleIndex; +} + +int ListElement::setDoubleProperty(const ListLayout::Role &role, double d) +{ + int roleIndex = -1; + + if (role.type == ListLayout::Role::Number) { + char *mem = getPropertyMemory(role); + double *value = new (mem) double; + bool changed = *value != d; + *value = d; + if (changed) + roleIndex = role.index; + } + + return roleIndex; +} + +int ListElement::setBoolProperty(const ListLayout::Role &role, bool b) +{ + int roleIndex = -1; + + if (role.type == ListLayout::Role::Bool) { + char *mem = getPropertyMemory(role); + bool *value = new (mem) bool; + bool changed = *value != b; + *value = b; + if (changed) + roleIndex = role.index; + } + + return roleIndex; +} + +int ListElement::setListProperty(const ListLayout::Role &role, ListModel *m) +{ + int roleIndex = -1; + + if (role.type == ListLayout::Role::List) { + char *mem = getPropertyMemory(role); + ListModel **value = new (mem) ListModel *; + if (*value) { + (*value)->destroy(); + delete *value; + } + *value = m; + roleIndex = role.index; + } + + return roleIndex; +} + +int ListElement::setQObjectProperty(const ListLayout::Role &role, QObject *o) +{ + int roleIndex = -1; + + if (role.type == ListLayout::Role::QObject) { + char *mem = getPropertyMemory(role); + QQmlGuard<QObject> *g = reinterpret_cast<QQmlGuard<QObject> *>(mem); + bool existingGuard = false; + for (size_t i=0 ; i < sizeof(QQmlGuard<QObject>) ; ++i) { + if (mem[i] != 0) { + existingGuard = true; + break; + } + } + bool changed; + if (existingGuard) { + changed = g->data() != o; + g->~QQmlGuard(); + } else { + changed = true; + } + new (mem) QQmlGuard<QObject>(o); + if (changed) + roleIndex = role.index; + } + + return roleIndex; +} + +int ListElement::setVariantMapProperty(const ListLayout::Role &role, v8::Handle<v8::Object> o, QV8Engine *eng) +{ + int roleIndex = -1; + + if (role.type == ListLayout::Role::VariantMap) { + char *mem = getPropertyMemory(role); + if (isMemoryUsed<QVariantMap>(mem)) { + QVariantMap *map = reinterpret_cast<QVariantMap *>(mem); + map->~QMap(); + } + new (mem) QVariantMap(eng->variantMapFromJS(o)); + roleIndex = role.index; + } + + return roleIndex; +} + +int ListElement::setVariantMapProperty(const ListLayout::Role &role, QVariantMap *m) +{ + int roleIndex = -1; + + if (role.type == ListLayout::Role::VariantMap) { + char *mem = getPropertyMemory(role); + if (isMemoryUsed<QVariantMap>(mem)) { + QVariantMap *map = reinterpret_cast<QVariantMap *>(mem); + map->~QMap(); + } + if (m) + new (mem) QVariantMap(*m); + else + new (mem) QVariantMap; + roleIndex = role.index; + } + + return roleIndex; +} + +int ListElement::setDateTimeProperty(const ListLayout::Role &role, const QDateTime &dt) +{ + int roleIndex = -1; + + if (role.type == ListLayout::Role::DateTime) { + char *mem = getPropertyMemory(role); + if (isMemoryUsed<QDateTime>(mem)) { + QDateTime *dt = reinterpret_cast<QDateTime *>(mem); + dt->~QDateTime(); + } + new (mem) QDateTime(dt); + roleIndex = role.index; + } + + return roleIndex; +} + +void ListElement::setStringPropertyFast(const ListLayout::Role &role, const QString &s) +{ + char *mem = getPropertyMemory(role); + new (mem) QString(s); +} + +void ListElement::setDoublePropertyFast(const ListLayout::Role &role, double d) +{ + char *mem = getPropertyMemory(role); + double *value = new (mem) double; + *value = d; +} + +void ListElement::setBoolPropertyFast(const ListLayout::Role &role, bool b) +{ + char *mem = getPropertyMemory(role); + bool *value = new (mem) bool; + *value = b; +} + +void ListElement::setQObjectPropertyFast(const ListLayout::Role &role, QObject *o) +{ + char *mem = getPropertyMemory(role); + new (mem) QQmlGuard<QObject>(o); +} + +void ListElement::setListPropertyFast(const ListLayout::Role &role, ListModel *m) +{ + char *mem = getPropertyMemory(role); + ListModel **value = new (mem) ListModel *; + *value = m; +} + +void ListElement::setVariantMapFast(const ListLayout::Role &role, v8::Handle<v8::Object> o, QV8Engine *eng) +{ + char *mem = getPropertyMemory(role); + QVariantMap *map = new (mem) QVariantMap; + *map = eng->variantMapFromJS(o); +} + +void ListElement::setDateTimePropertyFast(const ListLayout::Role &role, const QDateTime &dt) +{ + char *mem = getPropertyMemory(role); + new (mem) QDateTime(dt); +} + +void ListElement::clearProperty(const ListLayout::Role &role) +{ + switch (role.type) { + case ListLayout::Role::String: + setStringProperty(role, QString()); + break; + case ListLayout::Role::Number: + setDoubleProperty(role, 0.0); + break; + case ListLayout::Role::Bool: + setBoolProperty(role, false); + break; + case ListLayout::Role::List: + setListProperty(role, 0); + break; + case ListLayout::Role::QObject: + setQObjectProperty(role, 0); + break; + case ListLayout::Role::DateTime: + setDateTimeProperty(role, QDateTime()); + break; + case ListLayout::Role::VariantMap: + setVariantMapProperty(role, 0); + break; + default: + break; + } +} + +ListElement::ListElement() +{ + m_objectCache = 0; + uid = uidCounter.fetchAndAddOrdered(1); + next = 0; + memset(data, 0, sizeof(data)); +} + +ListElement::ListElement(int existingUid) +{ + m_objectCache = 0; + uid = existingUid; + next = 0; + memset(data, 0, sizeof(data)); +} + +ListElement::~ListElement() +{ + delete next; +} + +void ListElement::sync(ListElement *src, ListLayout *srcLayout, ListElement *target, ListLayout *targetLayout, QHash<int, ListModel *> *targetModelHash) +{ + for (int i=0 ; i < srcLayout->roleCount() ; ++i) { + const ListLayout::Role &srcRole = srcLayout->getExistingRole(i); + const ListLayout::Role &targetRole = targetLayout->getExistingRole(i); + + switch (srcRole.type) { + case ListLayout::Role::List: + { + ListModel *srcSubModel = src->getListProperty(srcRole); + ListModel *targetSubModel = target->getListProperty(targetRole); + + if (srcSubModel) { + if (targetSubModel == 0) { + targetSubModel = new ListModel(targetRole.subLayout, 0, srcSubModel->getUid()); + target->setListPropertyFast(targetRole, targetSubModel); + } + ListModel::sync(srcSubModel, targetSubModel, targetModelHash); + } + } + break; + case ListLayout::Role::QObject: + { + QObject *object = src->getQObjectProperty(srcRole); + target->setQObjectProperty(targetRole, object); + } + break; + case ListLayout::Role::String: + case ListLayout::Role::Number: + case ListLayout::Role::Bool: + case ListLayout::Role::DateTime: + { + QVariant v = src->getProperty(srcRole, 0, 0); + target->setVariantProperty(targetRole, v); + } + case ListLayout::Role::VariantMap: + { + QVariantMap *map = src->getVariantMapProperty(srcRole); + target->setVariantMapProperty(targetRole, map); + } + break; + default: + break; + } + } + +} + +void ListElement::destroy(ListLayout *layout) +{ + if (layout) { + for (int i=0 ; i < layout->roleCount() ; ++i) { + const ListLayout::Role &r = layout->getExistingRole(i); + + switch (r.type) { + case ListLayout::Role::String: + { + QString *string = getStringProperty(r); + if (string) + string->~QString(); + } + break; + case ListLayout::Role::List: + { + ListModel *model = getListProperty(r); + if (model) { + model->destroy(); + delete model; + } + } + break; + case ListLayout::Role::QObject: + { + QQmlGuard<QObject> *guard = getGuardProperty(r); + if (guard) + guard->~QQmlGuard(); + } + break; + case ListLayout::Role::VariantMap: + { + QVariantMap *map = getVariantMapProperty(r); + if (map) + map->~QMap(); + } + break; + case ListLayout::Role::DateTime: + { + QDateTime *dt = getDateTimeProperty(r); + if (dt) + dt->~QDateTime(); + } + break; + default: + // other types don't need explicit cleanup. + break; + } + } + + delete m_objectCache; + } + + if (next) + next->destroy(0); + uid = -1; +} + +int ListElement::setVariantProperty(const ListLayout::Role &role, const QVariant &d) +{ + int roleIndex = -1; + + switch (role.type) { + case ListLayout::Role::Number: + roleIndex = setDoubleProperty(role, d.toDouble()); + break; + case ListLayout::Role::String: + roleIndex = setStringProperty(role, d.toString()); + break; + case ListLayout::Role::Bool: + roleIndex = setBoolProperty(role, d.toBool()); + break; + case ListLayout::Role::List: + roleIndex = setListProperty(role, d.value<ListModel *>()); + break; + case ListLayout::Role::VariantMap: { + QVariantMap map = d.toMap(); + roleIndex = setVariantMapProperty(role, &map); + } + break; + case ListLayout::Role::DateTime: + roleIndex = setDateTimeProperty(role, d.toDateTime()); + break; + default: + break; + } + + return roleIndex; +} + +int ListElement::setJsProperty(const ListLayout::Role &role, v8::Handle<v8::Value> d, QV8Engine *eng) +{ + // Check if this key exists yet + int roleIndex = -1; + + // Add the value now + if (d->IsString()) { + v8::Handle<v8::String> jsString = d->ToString(); + QString qstr; + qstr.resize(jsString->Length()); + jsString->Write(reinterpret_cast<uint16_t*>(qstr.data())); + roleIndex = setStringProperty(role, qstr); + } else if (d->IsNumber()) { + roleIndex = setDoubleProperty(role, d->NumberValue()); + } else if (d->IsArray()) { + if (role.type == ListLayout::Role::List) { + ListModel *subModel = new ListModel(role.subLayout, 0, -1); + v8::Handle<v8::Array> subArray = v8::Handle<v8::Array>::Cast(d); + int arrayLength = subArray->Length(); + for (int j=0 ; j < arrayLength ; ++j) { + v8::Handle<v8::Object> subObject = subArray->Get(j)->ToObject(); + subModel->append(subObject, eng); + } + roleIndex = setListProperty(role, subModel); + } else { + qmlInfo(0) << QString::fromLatin1("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()); + } else if (d->IsDate()) { + QDateTime dt = QV8Engine::qtDateTimeFromJsDate(v8::Handle<v8::Date>::Cast(d)->NumberValue()); + roleIndex = setDateTimeProperty(role, dt); + } else if (d->IsObject()) { + QV8ObjectResource *r = (QV8ObjectResource *) d->ToObject()->GetExternalResource(); + if (role.type == ListLayout::Role::QObject && r && r->resourceType() == QV8ObjectResource::QObjectType) { + QObject *o = QV8QObjectWrapper::toQObject(r); + roleIndex = setQObjectProperty(role, o); + } else if (role.type == ListLayout::Role::VariantMap) { + roleIndex = setVariantMapProperty(role, d->ToObject(), eng); + } + } else if (d.IsEmpty() || d->IsUndefined() || d->IsNull()) { + clearProperty(role); + } + + return roleIndex; +} + +ModelObject::ModelObject(QQmlListModel *model, int elementIndex) +: m_model(model), m_elementIndex(elementIndex), m_meta(new ModelNodeMetaObject(this)) +{ + updateValues(); + setNodeUpdatesEnabled(true); +} + +void ModelObject::updateValues() +{ + int roleCount = m_model->m_listModel->roleCount(); + for (int i=0 ; i < roleCount ; ++i) { + const ListLayout::Role &role = m_model->m_listModel->getExistingRole(i); + QByteArray name = role.name.toUtf8(); + const QVariant &data = m_model->data(m_elementIndex, i); + setValue(name, data, role.type == ListLayout::Role::List); + } +} + +void ModelObject::updateValues(const QVector<int> &roles) +{ + int roleCount = roles.count(); + for (int i=0 ; i < roleCount ; ++i) { + int roleIndex = roles.at(i); + const ListLayout::Role &role = m_model->m_listModel->getExistingRole(roleIndex); + QByteArray name = role.name.toUtf8(); + const QVariant &data = m_model->data(m_elementIndex, roleIndex); + setValue(name, data, role.type == ListLayout::Role::List); + } +} + +ModelNodeMetaObject::ModelNodeMetaObject(ModelObject *object) +: QQmlOpenMetaObject(object), m_enabled(false), m_obj(object) +{ +} + +ModelNodeMetaObject::~ModelNodeMetaObject() +{ +} + +void ModelNodeMetaObject::propertyWritten(int index) +{ + if (!m_enabled) + return; + + QV8Engine *eng = m_obj->m_model->engine(); + + QString propName = QString::fromUtf8(name(index)); + QVariant value = operator[](index); + + v8::HandleScope handle_scope; + v8::Context::Scope scope(eng->context()); + + v8::Handle<v8::Value> v = eng->fromVariant(value); + + int roleIndex = m_obj->m_model->m_listModel->setExistingProperty(m_obj->m_elementIndex, propName, v, eng); + if (roleIndex != -1) { + QVector<int> roles; + roles << roleIndex; + m_obj->m_model->emitItemsChanged(m_obj->m_elementIndex, 1, roles); + } +} + +DynamicRoleModelNode::DynamicRoleModelNode(QQmlListModel *owner, int uid) : m_owner(owner), m_uid(uid), m_meta(new DynamicRoleModelNodeMetaObject(this)) +{ + setNodeUpdatesEnabled(true); +} + +DynamicRoleModelNode *DynamicRoleModelNode::create(const QVariantMap &obj, QQmlListModel *owner) +{ + DynamicRoleModelNode *object = new DynamicRoleModelNode(owner, uidCounter.fetchAndAddOrdered(1)); + QVector<int> roles; + object->updateValues(obj, roles); + return object; +} + +void DynamicRoleModelNode::sync(DynamicRoleModelNode *src, DynamicRoleModelNode *target, QHash<int, QQmlListModel *> *targetModelHash) +{ + 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 *>()); + + if (srcModel) { + if (targetModel == 0) + targetModel = QQmlListModel::createWithOwner(target->m_owner); + + QQmlListModel::sync(srcModel, targetModel, targetModelHash); + + QObject *targetModelObject = targetModel; + value = QVariant::fromValue(targetModelObject); + } else if (targetModel) { + delete targetModel; + } + + target->setValue(name, value); + } +} + +void DynamicRoleModelNode::updateValues(const QVariantMap &object, QVector<int> &roles) +{ + const QList<QString> &keys = object.keys(); + + QList<QString>::const_iterator it = keys.begin(); + QList<QString>::const_iterator end = keys.end(); + + while (it != end) { + const QString &key = *it; + + int roleIndex = m_owner->m_roles.indexOf(key); + if (roleIndex == -1) { + roleIndex = m_owner->m_roles.count(); + m_owner->m_roles.append(key); + } + + QVariant value = object[key]; + + if (value.type() == QVariant::List) { + QQmlListModel *subModel = QQmlListModel::createWithOwner(m_owner); + + QVariantList subArray = value.toList(); + QVariantList::const_iterator subIt = subArray.begin(); + QVariantList::const_iterator subEnd = subArray.end(); + while (subIt != subEnd) { + const QVariantMap &subObject = subIt->toMap(); + subModel->m_modelObjects.append(DynamicRoleModelNode::create(subObject, subModel)); + ++subIt; + } + + QObject *subModelObject = subModel; + value = QVariant::fromValue(subModelObject); + } + + const QByteArray &keyUtf8 = key.toUtf8(); + + QQmlListModel *existingModel = qobject_cast<QQmlListModel *>(m_meta->value(keyUtf8).value<QObject *>()); + if (existingModel) + delete existingModel; + + if (m_meta->setValue(keyUtf8, value)) + roles << roleIndex; + + ++it; + } +} + +DynamicRoleModelNodeMetaObject::DynamicRoleModelNodeMetaObject(DynamicRoleModelNode *object) + : QQmlOpenMetaObject(object), m_enabled(false), m_owner(object) +{ +} + +DynamicRoleModelNodeMetaObject::~DynamicRoleModelNodeMetaObject() +{ + for (int i=0 ; i < count() ; ++i) { + QQmlListModel *subModel = qobject_cast<QQmlListModel *>(value(i).value<QObject *>()); + if (subModel) + delete subModel; + } +} + +void DynamicRoleModelNodeMetaObject::propertyWrite(int index) +{ + if (!m_enabled) + return; + + QVariant v = value(index); + QQmlListModel *model = qobject_cast<QQmlListModel *>(v.value<QObject *>()); + if (model) + delete model; +} + +void DynamicRoleModelNodeMetaObject::propertyWritten(int index) +{ + if (!m_enabled) + return; + + QQmlListModel *parentModel = m_owner->m_owner; + + QVariant v = value(index); + if (v.type() == QVariant::List) { + QQmlListModel *subModel = QQmlListModel::createWithOwner(parentModel); + + QVariantList subArray = v.toList(); + QVariantList::const_iterator subIt = subArray.begin(); + QVariantList::const_iterator subEnd = subArray.end(); + while (subIt != subEnd) { + const QVariantMap &subObject = subIt->toMap(); + subModel->m_modelObjects.append(DynamicRoleModelNode::create(subObject, subModel)); + ++subIt; + } + + QObject *subModelObject = subModel; + v = QVariant::fromValue(subModelObject); + + setValue(index, v); + } + + int elementIndex = parentModel->m_modelObjects.indexOf(m_owner); + int roleIndex = parentModel->m_roles.indexOf(QString::fromLatin1(name(index).constData())); + + if (elementIndex != -1 && roleIndex != -1) { + + QVector<int> roles; + roles << roleIndex; + + parentModel->emitItemsChanged(elementIndex, 1, roles); + } +} + +QQmlListModelParser::ListInstruction *QQmlListModelParser::ListModelData::instructions() const +{ + return (QQmlListModelParser::ListInstruction *)((char *)this + sizeof(ListModelData)); +} + +/*! + \qmltype ListModel + \instantiates QQmlListModel + \inqmlmodule QtQml.Models 2 + \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 explicitly in QML. + + This type is also available in the QtQuick 2 import. For full documentation, see \l QtQuick2::ListModel +*/ +/*! + \qmltype ListModel + \instantiates QQmlListModel + \inqmlmodule QtQuick 2 + \brief Defines a free-form list data source + \ingroup qtquick-models + + The ListModel is a simple container of ListElement definitions, each containing data roles. + The contents can be defined dynamically, or explicitly in QML. + + The number of elements in the model can be obtained from its \l count property. + A number of familiar methods are also provided to manipulate the contents of the + model, including append(), insert(), move(), remove() and set(). These methods + accept dictionaries as their arguments; these are translated to ListElement objects + by the model. + + Elements can be manipulated via the model using the setProperty() method, which + allows the roles of the specified element to be set and changed. + + \section1 Example Usage + + The following example shows a ListModel containing three elements, with the roles + "name" and "cost". + + \div {class="float-right"} + \inlineimage listmodel.png + \enddiv + + \snippet qml/listmodel/listmodel.qml 0 + + Roles (properties) in each element must begin with a lower-case letter and + should be common to all elements in a model. The ListElement documentation + provides more guidelines for how elements should be defined. + + Since the example model contains an \c id property, it can be referenced + by views, such as the ListView in this example: + + \snippet qml/listmodel/listmodel-simple.qml 0 + \dots 8 + \snippet qml/listmodel/listmodel-simple.qml 1 + + It is possible for roles to contain list data. In the following example we + create a list of fruit attributes: + + \snippet qml/listmodel/listmodel-nested.qml model + + The delegate displays all the fruit attributes: + + \div {class="float-right"} + \inlineimage listmodel-nested.png + \enddiv + + \snippet qml/listmodel/listmodel-nested.qml delegate + + \section1 Modifying List Models + + The content of a ListModel may be created and modified using the clear(), + append(), set(), insert() and setProperty() methods. For example: + + \snippet qml/listmodel/listmodel-modify.qml delegate + + Note that when creating content dynamically the set of available properties + cannot be changed once set. Whatever properties are first added to the model + are the only permitted properties in the model. + + \section1 Using Threaded List Models with WorkerScript + + ListModel can be used together with WorkerScript access a list model + from multiple threads. This is useful if list modifications are + synchronous and take some time: the list operations can be moved to a + different thread to avoid blocking of the main GUI thread. + + Here is an example that uses WorkerScript to periodically append the + current time to a list model: + + \snippet quick/threading/threadedlistmodel/timedisplay.qml 0 + + The included file, \tt dataloader.js, looks like this: + + \snippet quick/threading/threadedlistmodel/dataloader.js 0 + + The timer in the main example sends messages to the worker script by calling + \l WorkerScript::sendMessage(). When this message is received, + \l{WorkerScript::onMessage}{WorkerScript.onMessage()} is invoked in \c dataloader.js, + which appends the current time to the list model. + + Note the call to sync() from the \l{WorkerScript::onMessage}{WorkerScript.onMessage()} + handler. You must call sync() or else the changes made to the list from the external + thread will not be reflected in the list model in the main thread. + + \sa {qml-data-models}{Data Models}, {declarative/threading/threadedlistmodel}{Threaded ListModel example}, QtQml +*/ + +QQmlListModel::QQmlListModel(QObject *parent) +: QAbstractListModel(parent) +{ + m_mainThread = true; + m_primary = true; + m_agent = 0; + m_uid = uidCounter.fetchAndAddOrdered(1); + m_dynamicRoles = false; + + m_layout = new ListLayout; + m_listModel = new ListModel(m_layout, this, -1); + + m_engine = 0; +} + +QQmlListModel::QQmlListModel(const QQmlListModel *owner, ListModel *data, QV8Engine *eng, QObject *parent) +: QAbstractListModel(parent) +{ + m_mainThread = owner->m_mainThread; + m_primary = false; + m_agent = owner->m_agent; + + Q_ASSERT(owner->m_dynamicRoles == false); + m_dynamicRoles = false; + m_layout = 0; + m_listModel = data; + + m_engine = eng; +} + +QQmlListModel::QQmlListModel(QQmlListModel *orig, QQmlListModelWorkerAgent *agent) +: QAbstractListModel(agent) +{ + m_mainThread = false; + m_primary = true; + m_agent = agent; + m_dynamicRoles = orig->m_dynamicRoles; + + m_layout = new ListLayout(orig->m_layout); + m_listModel = new ListModel(m_layout, this, orig->m_listModel->getUid()); + + if (m_dynamicRoles) + sync(orig, this, 0); + else + ListModel::sync(orig->m_listModel, m_listModel, 0); + + m_engine = 0; +} + +QQmlListModel::~QQmlListModel() +{ + for (int i=0 ; i < m_modelObjects.count() ; ++i) + delete m_modelObjects[i]; + + if (m_primary) { + m_listModel->destroy(); + delete m_listModel; + + if (m_mainThread && m_agent) { + m_agent->modelDestroyed(); + m_agent->release(); + } + } + + m_listModel = 0; + + delete m_layout; + m_layout = 0; +} + +QQmlListModel *QQmlListModel::createWithOwner(QQmlListModel *newOwner) +{ + QQmlListModel *model = new QQmlListModel; + + model->m_mainThread = newOwner->m_mainThread; + model->m_engine = newOwner->m_engine; + model->m_agent = newOwner->m_agent; + model->m_dynamicRoles = newOwner->m_dynamicRoles; + + if (model->m_mainThread && model->m_agent) + model->m_agent->addref(); + + QQmlEngine::setContextForObject(model, QQmlEngine::contextForObject(newOwner)); + + return model; +} + +QV8Engine *QQmlListModel::engine() const +{ + if (m_engine == 0) { + m_engine = QQmlEnginePrivate::getV8Engine(qmlEngine(this)); + } + + return m_engine; +} + +void QQmlListModel::sync(QQmlListModel *src, QQmlListModel *target, QHash<int, QQmlListModel *> *targetModelHash) +{ + Q_ASSERT(src->m_dynamicRoles && target->m_dynamicRoles); + + target->m_uid = src->m_uid; + if (targetModelHash) + targetModelHash->insert(target->m_uid, target); + 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) { + DynamicRoleModelNode *e = target->m_modelObjects.at(i); + int uid = e->getUid(); + ElementSync sync; + sync.target = e; + elementHash.insert(uid, sync); + } + for (int i=0 ; i < src->m_modelObjects.count() ; ++i) { + DynamicRoleModelNode *e = src->m_modelObjects.at(i); + int uid = e->getUid(); + + QHash<int, ElementSync>::iterator it = elementHash.find(uid); + if (it == elementHash.end()) { + ElementSync sync; + sync.src = e; + elementHash.insert(uid, sync); + } else { + ElementSync &sync = it.value(); + sync.src = e; + } + } + + // 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); + delete s.target; + } + ++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(); + DynamicRoleModelNode *targetElement = s.target; + if (targetElement == 0) { + targetElement = new DynamicRoleModelNode(target, srcElement->getUid()); + } + DynamicRoleModelNode::sync(srcElement, targetElement, targetModelHash); + 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); + } +} + +void QQmlListModel::emitItemsRemoved(int index, int count) +{ + if (count <= 0) + return; + + if (m_mainThread) { + beginRemoveRows(QModelIndex(), index, index + count - 1); + 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); + } +} + +void QQmlListModel::emitItemsInserted(int index, int count) +{ + if (count <= 0) + return; + + if (m_mainThread) { + beginInsertRows(QModelIndex(), index, index + count - 1); + endInsertRows(); + emit countChanged(); + } else { + int uid = m_dynamicRoles ? getUid() : m_listModel->getUid(); + m_agent->data.insertChange(uid, index, count); + } +} + +void QQmlListModel::emitItemsMoved(int from, int to, int n) +{ + if (n <= 0) + return; + + if (m_mainThread) { + beginMoveRows(QModelIndex(), from, from + n - 1, QModelIndex(), to > from ? to + n : to); + endMoveRows(); + } else { + int uid = m_dynamicRoles ? getUid() : m_listModel->getUid(); + m_agent->data.moveChange(uid, from, n, to); + } +} + +QQmlListModelWorkerAgent *QQmlListModel::agent() +{ + if (m_agent) + return m_agent; + + m_agent = new QQmlListModelWorkerAgent(this); + return m_agent; +} + +QModelIndex QQmlListModel::index(int row, int column, const QModelIndex &parent) const +{ + return row >= 0 && row < count() && column == 0 && !parent.isValid() + ? createIndex(row, column) + : QModelIndex(); +} + +int QQmlListModel::rowCount(const QModelIndex &parent) const +{ + return !parent.isValid() ? count() : 0; +} + +QVariant QQmlListModel::data(const QModelIndex &index, int role) const +{ + return data(index.row(), role); +} + +QVariant QQmlListModel::data(int index, int role) const +{ + QVariant v; + + if (index >= count() || index < 0) + return v; + + if (m_dynamicRoles) + v = m_modelObjects[index]->getValue(m_roles[role]); + else + v = m_listModel->getProperty(index, role, this, engine()); + + return v; +} + +QHash<int, QByteArray> QQmlListModel::roleNames() const +{ + QHash<int, QByteArray> roleNames; + + if (m_dynamicRoles) { + for (int i = 0 ; i < m_roles.count() ; ++i) + roleNames.insert(i, m_roles.at(i).toUtf8()); + } else { + for (int i = 0 ; i < m_listModel->roleCount() ; ++i) { + const ListLayout::Role &r = m_listModel->getExistingRole(i); + roleNames.insert(i, r.name.toUtf8()); + } + } + + return roleNames; +} + +/*! + \qmlproperty bool QtQml2::ListModel::dynamicRoles + + By default, the type of a role is fixed the first time + the role is used. For example, if you create a role called + "data" and assign a number to it, you can no longer assign + a string to the "data" role. However, when the dynamicRoles + property is enabled, the type of a given role is not fixed + and can be different between elements. + + The dynamicRoles property must be set before any data is + added to the ListModel, and must be set from the main + thread. + + A ListModel that has data statically defined (via the + ListElement QML syntax) cannot have the dynamicRoles + property enabled. + + There is a significant performance cost to using a + ListModel with dynamic roles enabled. The cost varies + from platform to platform but is typically somewhere + between 4-6x slower than using static role types. + + Due to the performance cost of using dynamic roles, + they are disabled by default. +*/ +void QQmlListModel::setDynamicRoles(bool enableDynamicRoles) +{ + if (m_mainThread && m_agent == 0) { + if (enableDynamicRoles) { + if (m_layout->roleCount()) + qmlInfo(this) << tr("unable to enable dynamic roles as this model is not empty!"); + else + m_dynamicRoles = true; + } else { + if (m_roles.count()) { + qmlInfo(this) << tr("unable to enable static roles as this model is not empty!"); + } else { + m_dynamicRoles = false; + } + } + } else { + qmlInfo(this) << tr("dynamic role setting must be made from the main thread, before any worker scripts are created"); + } +} + +/*! + \qmlproperty int QtQml2::ListModel::count + The number of data entries in the model. +*/ +int QQmlListModel::count() const +{ + int count; + + if (m_dynamicRoles) + count = m_modelObjects.count(); + else { + count = m_listModel->elementCount(); + } + + return count; +} + +/*! + \qmlmethod QtQml2::ListModel::clear() + + Deletes all content from the model. + + \sa append(), remove() +*/ +void QQmlListModel::clear() +{ + int cleared = count(); + + if (m_dynamicRoles) { + for (int i=0 ; i < m_modelObjects.count() ; ++i) + delete m_modelObjects[i]; + m_modelObjects.clear(); + } else { + m_listModel->clear(); + } + + emitItemsRemoved(0, cleared); +} + +/*! + \qmlmethod QtQml2::ListModel::remove(int index, int count = 1) + + Deletes the content at \a index from the model. + + \sa clear() +*/ +void QQmlListModel::remove(QQmlV8Function *args) +{ + int argLength = args->Length(); + + if (argLength == 1 || argLength == 2) { + int index = (*args)[0]->Int32Value(); + int removeCount = (argLength == 2 ? ((*args)[1]->Int32Value()) : 1); + + if (index < 0 || index+removeCount > count() || removeCount <= 0) { + qmlInfo(this) << tr("remove: indices [%1 - %2] out of range [0 - %3]").arg(index).arg(index+removeCount).arg(count()); + return; + } + + 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); + } + + emitItemsRemoved(index, removeCount); + } else { + qmlInfo(this) << tr("remove: incorrect number of arguments"); + } +} + +/*! + \qmlmethod QtQml2::ListModel::insert(int index, jsobject dict) + + Adds a new item to the list model at position \a index, with the + values in \a dict. + + \code + fruitModel.insert(2, {"cost": 5.95, "name":"Pizza"}) + \endcode + + The \a index must be to an existing item in the list, or one past + the end of the list (equivalent to append). + + \sa set(), append() +*/ + +void QQmlListModel::insert(QQmlV8Function *args) +{ + if (args->Length() == 2) { + + v8::Handle<v8::Value> arg0 = (*args)[0]; + int index = arg0->Int32Value(); + + if (index < 0 || index > count()) { + qmlInfo(this) << tr("insert: index %1 out of range").arg(index); + return; + } + + v8::Handle<v8::Value> arg1 = (*args)[1]; + + if (arg1->IsArray()) { + v8::Handle<v8::Array> objectArray = v8::Handle<v8::Array>::Cast(arg1); + int objectArrayLength = objectArray->Length(); + for (int i=0 ; i < objectArrayLength ; ++i) { + v8::Handle<v8::Object> argObject = objectArray->Get(i)->ToObject(); + + if (m_dynamicRoles) { + m_modelObjects.insert(index+i, DynamicRoleModelNode::create(args->engine()->variantMapFromJS(argObject), this)); + } else { + m_listModel->insert(index+i, argObject, args->engine()); + } + } + emitItemsInserted(index, objectArrayLength); + } else if (arg1->IsObject()) { + v8::Handle<v8::Object> argObject = arg1->ToObject(); + + if (m_dynamicRoles) { + m_modelObjects.insert(index, DynamicRoleModelNode::create(args->engine()->variantMapFromJS(argObject), this)); + } else { + m_listModel->insert(index, argObject, args->engine()); + } + + emitItemsInserted(index, 1); + } else { + qmlInfo(this) << tr("insert: value is not an object"); + } + } else { + qmlInfo(this) << tr("insert: value is not an object"); + } +} + +/*! + \qmlmethod QtQml2::ListModel::move(int from, int to, int n) + + Moves \a n items \a from one position \a to another. + + The from and to ranges must exist; for example, to move the first 3 items + to the end of the list: + + \code + fruitModel.move(0, fruitModel.count - 3, 3) + \endcode + + \sa append() +*/ +void QQmlListModel::move(int from, int to, int n) +{ + if (n==0 || from==to) + return; + if (!canMove(from, to, n)) { + qmlInfo(this) << tr("move: out of range"); + return; + } + + if (m_dynamicRoles) { + + int realFrom = from; + int realTo = to; + int realN = n; + + if (from > to) { + // Only move forwards - flip if backwards moving + int tfrom = from; + int tto = to; + realFrom = tto; + realTo = tto+n; + realN = tfrom-tto; + } + + QPODVector<DynamicRoleModelNode *, 4> store; + for (int i=0 ; i < (realTo-realFrom) ; ++i) + store.append(m_modelObjects[realFrom+realN+i]); + for (int i=0 ; i < realN ; ++i) + store.append(m_modelObjects[realFrom+i]); + for (int i=0 ; i < store.count() ; ++i) + m_modelObjects[realFrom+i] = store[i]; + + } else { + m_listModel->move(from, to, n); + } + + emitItemsMoved(from, to, n); +} + +/*! + \qmlmethod QtQml2::ListModel::append(jsobject dict) + + Adds a new item to the end of the list model, with the + values in \a dict. + + \code + fruitModel.append({"cost": 5.95, "name":"Pizza"}) + \endcode + + \sa set(), remove() +*/ +void QQmlListModel::append(QQmlV8Function *args) +{ + if (args->Length() == 1) { + v8::Handle<v8::Value> arg = (*args)[0]; + + if (arg->IsArray()) { + v8::Handle<v8::Array> objectArray = v8::Handle<v8::Array>::Cast(arg); + int objectArrayLength = objectArray->Length(); + + int index = count(); + for (int i=0 ; i < objectArrayLength ; ++i) { + v8::Handle<v8::Object> argObject = objectArray->Get(i)->ToObject(); + + if (m_dynamicRoles) { + m_modelObjects.append(DynamicRoleModelNode::create(args->engine()->variantMapFromJS(argObject), this)); + } else { + m_listModel->append(argObject, args->engine()); + } + } + + emitItemsInserted(index, objectArrayLength); + } else if (arg->IsObject()) { + v8::Handle<v8::Object> argObject = arg->ToObject(); + + int index; + + if (m_dynamicRoles) { + index = m_modelObjects.count(); + m_modelObjects.append(DynamicRoleModelNode::create(args->engine()->variantMapFromJS(argObject), this)); + } else { + index = m_listModel->append(argObject, args->engine()); + } + + emitItemsInserted(index, 1); + } else { + qmlInfo(this) << tr("append: value is not an object"); + } + } else { + qmlInfo(this) << tr("append: value is not an object"); + } +} + +/*! + \qmlmethod object QtQml2::ListModel::get(int index) + + Returns the item at \a index in the list model. This allows the item + data to be accessed or modified from JavaScript: + + \code + Component.onCompleted: { + fruitModel.append({"cost": 5.95, "name":"Jackfruit"}); + console.log(fruitModel.get(0).cost); + fruitModel.get(0).cost = 10.95; + } + \endcode + + The \a index must be an element in the list. + + Note that properties of the returned object that are themselves objects + will also be models, and this get() method is used to access elements: + + \code + fruitModel.append(..., "attributes": + [{"name":"spikes","value":"7mm"}, + {"name":"color","value":"green"}]); + fruitModel.get(0).attributes.get(1).value; // == "green" + \endcode + + \warning The returned object is not guaranteed to remain valid. It + should not be used in \l{Property Binding}{property bindings}. + + \sa append() +*/ +QQmlV8Handle QQmlListModel::get(int index) const +{ + v8::Handle<v8::Value> result = v8::Undefined(); + + if (index >= 0 && index < count()) { + QV8Engine *v8engine = engine(); + + if (m_dynamicRoles) { + DynamicRoleModelNode *object = m_modelObjects[index]; + result = v8engine->newQObject(object); + } else { + ModelObject *object = m_listModel->getOrCreateModelObject(const_cast<QQmlListModel *>(this), index); + result = v8engine->newQObject(object); + } + } + + return QQmlV8Handle::fromHandle(result); +} + +/*! + \qmlmethod QtQml2::ListModel::set(int index, jsobject dict) + + Changes the item at \a index in the list model with the + values in \a dict. Properties not appearing in \a dict + are left unchanged. + + \code + fruitModel.set(3, {"cost": 5.95, "name":"Pizza"}) + \endcode + + If \a index is equal to count() then a new item is appended to the + list. Otherwise, \a index must be an element in the list. + + \sa append() +*/ +void QQmlListModel::set(int index, const QQmlV8Handle &handle) +{ + v8::Handle<v8::Value> valuemap = handle.toHandle(); + + if (!valuemap->IsObject() || valuemap->IsArray()) { + qmlInfo(this) << tr("set: value is not an object"); + return; + } + if (index > count() || index < 0) { + qmlInfo(this) << tr("set: index %1 out of range").arg(index); + return; + } + + v8::Handle<v8::Object> object = valuemap->ToObject(); + + if (index == count()) { + + if (m_dynamicRoles) { + m_modelObjects.append(DynamicRoleModelNode::create(engine()->variantMapFromJS(object), this)); + } else { + m_listModel->insert(index, object, engine()); + } + + emitItemsInserted(index, 1); + } else { + + QVector<int> roles; + + if (m_dynamicRoles) { + m_modelObjects[index]->updateValues(engine()->variantMapFromJS(object), roles); + } else { + m_listModel->set(index, object, &roles, engine()); + } + + if (roles.count()) + emitItemsChanged(index, 1, roles); + } +} + +/*! + \qmlmethod QtQml2::ListModel::setProperty(int index, string property, variant value) + + Changes the \a property of the item at \a index in the list model to \a value. + + \code + fruitModel.setProperty(3, "cost", 5.95) + \endcode + + The \a index must be an element in the list. + + \sa append() +*/ +void QQmlListModel::setProperty(int index, const QString& property, const QVariant& value) +{ + if (count() == 0 || index >= count() || index < 0) { + qmlInfo(this) << tr("set: index %1 out of range").arg(index); + return; + } + + if (m_dynamicRoles) { + int roleIndex = m_roles.indexOf(property); + if (roleIndex == -1) { + roleIndex = m_roles.count(); + m_roles.append(property); + } + if (m_modelObjects[index]->setValue(property.toUtf8(), value)) { + QVector<int> roles; + roles << roleIndex; + emitItemsChanged(index, 1, roles); + } + } else { + int roleIndex = m_listModel->setOrCreateProperty(index, property, value); + if (roleIndex != -1) { + + QVector<int> roles; + roles << roleIndex; + + emitItemsChanged(index, 1, roles); + } + } +} + +/*! + \qmlmethod QtQml2::ListModel::sync() + + Writes any unsaved changes to the list model after it has been modified + from a worker script. +*/ +void QQmlListModel::sync() +{ + // This is just a dummy method to make it look like sync() exists in + // ListModel (and not just QQmlListModelWorkerAgent) and to let + // us document sync(). + qmlInfo(this) << "List sync() can only be called from a WorkerScript"; +} + +bool QQmlListModelParser::compileProperty(const QQmlCustomParserProperty &prop, QList<ListInstruction> &instr, QByteArray &data) +{ + QList<QVariant> values = prop.assignedValues(); + for(int ii = 0; ii < values.count(); ++ii) { + const QVariant &value = values.at(ii); + + if(value.userType() == qMetaTypeId<QQmlCustomParserNode>()) { + QQmlCustomParserNode node = + qvariant_cast<QQmlCustomParserNode>(value); + + if (node.name() != listElementTypeName) { + const QMetaObject *mo = resolveType(node.name()); + if (mo != &QQmlListElement::staticMetaObject) { + error(node, QQmlListModel::tr("ListElement: cannot contain nested elements")); + return false; + } + listElementTypeName = node.name(); // cache right name for next time + } + + { + ListInstruction li; + li.type = ListInstruction::Push; + li.dataIdx = -1; + instr << li; + } + + QList<QQmlCustomParserProperty> props = node.properties(); + for(int jj = 0; jj < props.count(); ++jj) { + const QQmlCustomParserProperty &nodeProp = props.at(jj); + if (nodeProp.name().isEmpty()) { + error(nodeProp, QQmlListModel::tr("ListElement: cannot contain nested elements")); + return false; + } + if (nodeProp.name() == QStringLiteral("id")) { + error(nodeProp, QQmlListModel::tr("ListElement: cannot use reserved \"id\" property")); + return false; + } + + ListInstruction li; + int ref = data.count(); + data.append(nodeProp.name().toUtf8()); + data.append('\0'); + li.type = ListInstruction::Set; + li.dataIdx = ref; + instr << li; + + if(!compileProperty(nodeProp, instr, data)) + return false; + + li.type = ListInstruction::Pop; + li.dataIdx = -1; + instr << li; + } + + { + ListInstruction li; + li.type = ListInstruction::Pop; + li.dataIdx = -1; + instr << li; + } + + } else { + + QQmlScript::Variant variant = + qvariant_cast<QQmlScript::Variant>(value); + + int ref = data.count(); + + QByteArray d; + d += char(variant.type()); // type tag + if (variant.isString()) { + d += variant.asString().toUtf8(); + } else if (variant.isNumber()) { + d += QByteArray::number(variant.asNumber(),'g',20); + } else if (variant.isBoolean()) { + d += char(variant.asBoolean()); + } else if (variant.isScript()) { + if (definesEmptyList(variant.asScript())) { + d[0] = char(QQmlScript::Variant::Invalid); // marks empty list + } else { + QByteArray script = variant.asScript().toUtf8(); + bool ok; + int v = evaluateEnum(script, &ok); + if (!ok) { + using namespace QQmlJS; + AST::Node *node = variant.asAST(); + AST::StringLiteral *literal = 0; + if (AST::CallExpression *callExpr = AST::cast<AST::CallExpression *>(node)) { + if (AST::IdentifierExpression *idExpr = AST::cast<AST::IdentifierExpression *>(callExpr->base)) { + if (idExpr->name == QLatin1String("QT_TR_NOOP") || idExpr->name == QLatin1String("QT_TRID_NOOP")) { + if (callExpr->arguments && !callExpr->arguments->next) + literal = AST::cast<AST::StringLiteral *>(callExpr->arguments->expression); + if (!literal) { + error(prop, QQmlListModel::tr("ListElement: improperly specified %1").arg(idExpr->name.toString())); + return false; + } + } else if (idExpr->name == QLatin1String("QT_TRANSLATE_NOOP")) { + if (callExpr->arguments && callExpr->arguments->next && !callExpr->arguments->next->next) + literal = AST::cast<AST::StringLiteral *>(callExpr->arguments->next->expression); + if (!literal) { + error(prop, QQmlListModel::tr("ListElement: improperly specified QT_TRANSLATE_NOOP")); + return false; + } + } + } + } + + if (literal) { + d[0] = char(QQmlScript::Variant::String); + d += literal->value.toUtf8(); + } else { + error(prop, QQmlListModel::tr("ListElement: cannot use script for property value")); + return false; + } + } else { + d[0] = char(QQmlScript::Variant::Number); + d += QByteArray::number(v); + } + } + } + d.append('\0'); + data.append(d); + + ListInstruction li; + li.type = ListInstruction::Value; + li.dataIdx = ref; + instr << li; + } + } + + return true; +} + +QByteArray QQmlListModelParser::compile(const QList<QQmlCustomParserProperty> &customProps) +{ + QList<ListInstruction> instr; + QByteArray data; + listElementTypeName = QString(); // unknown + + for(int ii = 0; ii < customProps.count(); ++ii) { + const QQmlCustomParserProperty &prop = customProps.at(ii); + if(!prop.name().isEmpty()) { // isn't default property + error(prop, QQmlListModel::tr("ListModel: undefined property '%1'").arg(prop.name())); + return QByteArray(); + } + + if(!compileProperty(prop, instr, data)) { + return QByteArray(); + } + } + + int size = sizeof(ListModelData) + + instr.count() * sizeof(ListInstruction) + + data.count(); + + QByteArray rv; + rv.resize(size); + + ListModelData *lmd = (ListModelData *)rv.data(); + lmd->dataOffset = sizeof(ListModelData) + + instr.count() * sizeof(ListInstruction); + lmd->instrCount = instr.count(); + for (int ii = 0; ii < instr.count(); ++ii) + lmd->instructions()[ii] = instr.at(ii); + ::memcpy(rv.data() + lmd->dataOffset, data.constData(), data.count()); + + return rv; +} + +void QQmlListModelParser::setCustomData(QObject *obj, const QByteArray &d) +{ + QQmlListModel *rv = static_cast<QQmlListModel *>(obj); + + QV8Engine *engine = QQmlEnginePrivate::getV8Engine(qmlEngine(rv)); + rv->m_engine = engine; + + const ListModelData *lmd = (const ListModelData *)d.constData(); + const char *data = ((const char *)lmd) + lmd->dataOffset; + + bool setRoles = false; + + QStack<DataStackElement> stack; + + for (int ii = 0; ii < lmd->instrCount; ++ii) { + const ListInstruction &instr = lmd->instructions()[ii]; + + switch(instr.type) { + case ListInstruction::Push: + { + Q_ASSERT(!rv->m_dynamicRoles); + + ListModel *subModel = 0; + + if (stack.count() == 0) { + subModel = rv->m_listModel; + } else { + const DataStackElement &e0 = stack.at(stack.size() - 1); + DataStackElement &e1 = stack[stack.size() - 2]; + + const ListLayout::Role &role = e1.model->getOrCreateListRole(e0.name); + if (role.type == ListLayout::Role::List) { + subModel = e1.model->getListProperty(e1.elementIndex, role); + + if (subModel == 0) { + subModel = new ListModel(role.subLayout, 0, -1); + QVariant vModel = QVariant::fromValue(subModel); + e1.model->setOrCreateProperty(e1.elementIndex, e0.name, vModel); + } + } + } + + DataStackElement e; + e.model = subModel; + e.elementIndex = subModel ? subModel->appendElement() : -1; + stack.push(e); + } + break; + + case ListInstruction::Pop: + stack.pop(); + break; + + case ListInstruction::Value: + { + const DataStackElement &e0 = stack.at(stack.size() - 1); + DataStackElement &e1 = stack[stack.size() - 2]; + + QString name = e0.name; + QVariant value; + + switch (QQmlScript::Variant::Type(data[instr.dataIdx])) { + case QQmlScript::Variant::Invalid: + { + const ListLayout::Role &role = e1.model->getOrCreateListRole(e0.name); + ListModel *emptyModel = new ListModel(role.subLayout, 0, -1); + value = QVariant::fromValue(emptyModel); + } + break; + case QQmlScript::Variant::Boolean: + value = bool(data[1 + instr.dataIdx]); + break; + case QQmlScript::Variant::Number: + value = QByteArray(data + 1 + instr.dataIdx).toDouble(); + break; + case QQmlScript::Variant::String: + value = QString::fromUtf8(data + 1 + instr.dataIdx); + break; + default: + Q_ASSERT("Format error in ListInstruction"); + } + + e1.model->setOrCreateProperty(e1.elementIndex, name, value); + setRoles = true; + } + break; + + case ListInstruction::Set: + { + DataStackElement e; + e.name = QString::fromUtf8(data + instr.dataIdx); + stack.push(e); + } + break; + } + } + + if (setRoles == false) + qmlInfo(obj) << "All ListElement declarations are empty, no roles can be created unless dynamicRoles is set."; +} + +bool QQmlListModelParser::definesEmptyList(const QString &s) +{ + if (s.startsWith(QLatin1Char('[')) && s.endsWith(QLatin1Char(']'))) { + for (int i=1; i<s.length()-1; i++) { + if (!s[i].isSpace()) + return false; + } + return true; + } + return false; +} + + +/*! + \qmltype ListElement + \instantiates QQmlListElement + \inqmlmodule QtQml.Models 2 + \brief Defines a data item in a ListModel + + List elements are defined inside ListModel definitions, and represent items in a list. + + This type is also available in the QtQuick 2 import. For full documentation, see \l QtQuick2::ListElement +*/ +/*! + \qmltype ListElement + \instantiates QQmlListElement + \inqmlmodule QtQuick 2 + \brief Defines a data item in a ListModel + \ingroup qtquick-models + + List elements are defined inside ListModel definitions, and represent items in a + list that will be displayed using ListView or \l Repeater items. + + List elements are defined like other QML elements except that they contain + a collection of \e role definitions instead of properties. Using the same + syntax as property definitions, roles both define how the data is accessed + and include the data itself. + + The names used for roles must begin with a lower-case letter and should be + common to all elements in a given model. Values must be simple constants; either + strings (quoted and optionally within a call to QT_TR_NOOP), boolean values + (true, false), numbers, or enumeration values (such as AlignText.AlignHCenter). + + \section1 Referencing Roles + + The role names are used by delegates to obtain data from list elements. + Each role name is accessible in the delegate's scope, and refers to the + corresponding role in the current element. Where a role name would be + ambiguous to use, it can be accessed via the \l{ListView::}{model} + property (e.g., \c{model.cost} instead of \c{cost}). + + \section1 Example Usage + + The following model defines a series of list elements, each of which + contain "name" and "cost" roles and their associated values. + + \snippet qml/listmodel/listelements.qml model + + The delegate obtains the name and cost for each element by simply referring + to \c name and \c cost: + + \snippet qml/listmodel/listelements.qml view + + \sa ListModel +*/ + +QT_END_NAMESPACE diff --git a/src/qml/types/qqmllistmodel_p.h b/src/qml/types/qqmllistmodel_p.h new file mode 100644 index 0000000000..70477115bd --- /dev/null +++ b/src/qml/types/qqmllistmodel_p.h @@ -0,0 +1,198 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** 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 Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/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 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQMLLISTMODEL_H +#define QQMLLISTMODEL_H + +#include <qqml.h> +#include <private/qqmlcustomparser_p.h> + +#include <QtCore/QObject> +#include <QtCore/QStringList> +#include <QtCore/QHash> +#include <QtCore/QList> +#include <QtCore/QVariant> +#include <QtCore/qabstractitemmodel.h> + +#include <private/qv8engine_p.h> +#include <private/qpodvector_p.h> + +QT_BEGIN_NAMESPACE + + +class QQmlListModelWorkerAgent; +class ListModel; +class ListLayout; + +class Q_QML_PRIVATE_EXPORT QQmlListModel : public QAbstractListModel +{ + Q_OBJECT + Q_PROPERTY(int count READ count NOTIFY countChanged) + Q_PROPERTY(bool dynamicRoles READ dynamicRoles WRITE setDynamicRoles) + +public: + QQmlListModel(QObject *parent=0); + ~QQmlListModel(); + + QModelIndex index(int row, int column, const QModelIndex &parent) const; + int rowCount(const QModelIndex &parent) const; + QVariant data(const QModelIndex &index, int role) const; + QHash<int,QByteArray> roleNames() const; + + QVariant data(int index, int role) const; + int count() const; + + Q_INVOKABLE void clear(); + Q_INVOKABLE void remove(QQmlV8Function *args); + Q_INVOKABLE void append(QQmlV8Function *args); + Q_INVOKABLE void insert(QQmlV8Function *args); + Q_INVOKABLE QQmlV8Handle get(int index) const; + Q_INVOKABLE void set(int index, const QQmlV8Handle &); + Q_INVOKABLE void setProperty(int index, const QString& property, const QVariant& value); + Q_INVOKABLE void move(int from, int to, int count); + Q_INVOKABLE void sync(); + + QQmlListModelWorkerAgent *agent(); + + bool dynamicRoles() const { return m_dynamicRoles; } + void setDynamicRoles(bool enableDynamicRoles); + +Q_SIGNALS: + void countChanged(); + +private: + friend class QQmlListModelParser; + friend class QQmlListModelWorkerAgent; + friend class ModelObject; + friend class ModelNodeMetaObject; + friend class ListModel; + friend class ListElement; + friend class DynamicRoleModelNode; + friend class DynamicRoleModelNodeMetaObject; + + // Constructs a flat list model for a worker agent + QQmlListModel(QQmlListModel *orig, QQmlListModelWorkerAgent *agent); + QQmlListModel(const QQmlListModel *owner, ListModel *data, QV8Engine *eng, QObject *parent=0); + + QV8Engine *engine() const; + + inline bool canMove(int from, int to, int n) const { return !(from+n > count() || to+n > count() || from < 0 || to < 0 || n < 0); } + + QQmlListModelWorkerAgent *m_agent; + mutable QV8Engine *m_engine; + bool m_mainThread; + bool m_primary; + + bool m_dynamicRoles; + + ListLayout *m_layout; + ListModel *m_listModel; + + QVector<class DynamicRoleModelNode *> m_modelObjects; + QVector<QString> m_roles; + int m_uid; + + struct ElementSync + { + ElementSync() : src(0), target(0) {} + + DynamicRoleModelNode *src; + DynamicRoleModelNode *target; + }; + + int getUid() const { return m_uid; } + + static void sync(QQmlListModel *src, QQmlListModel *target, QHash<int, QQmlListModel *> *targetModelHash); + static QQmlListModel *createWithOwner(QQmlListModel *newOwner); + + void emitItemsChanged(int index, int count, const QVector<int> &roles); + void emitItemsRemoved(int index, int count); + void emitItemsInserted(int index, int count); + void emitItemsMoved(int from, int to, int n); +}; + +// ### FIXME +class QQmlListElement : public QObject +{ +Q_OBJECT +}; + +class QQmlListModelParser : public QQmlCustomParser +{ +public: + QQmlListModelParser() : QQmlCustomParser(QQmlCustomParser::AcceptsSignalHandlers) {} + QByteArray compile(const QList<QQmlCustomParserProperty> &); + void setCustomData(QObject *, const QByteArray &); + +private: + struct ListInstruction + { + enum { Push, Pop, Value, Set } type; + int dataIdx; + }; + struct ListModelData + { + int dataOffset; + int instrCount; + ListInstruction *instructions() const; + }; + bool compileProperty(const QQmlCustomParserProperty &prop, QList<ListInstruction> &instr, QByteArray &data); + + bool definesEmptyList(const QString &); + + QString listElementTypeName; + + struct DataStackElement + { + DataStackElement() : model(0), elementIndex(0) {} + + QString name; + ListModel *model; + int elementIndex; + }; +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQmlListModel) +QML_DECLARE_TYPE(QQmlListElement) + +#endif // QQMLLISTMODEL_H diff --git a/src/qml/types/qqmllistmodel_p_p.h b/src/qml/types/qqmllistmodel_p_p.h new file mode 100644 index 0000000000..0190081320 --- /dev/null +++ b/src/qml/types/qqmllistmodel_p_p.h @@ -0,0 +1,378 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** 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 Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/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 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQMLLISTMODEL_P_P_H +#define QQMLLISTMODEL_P_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 "qqmllistmodel_p.h" +#include <private/qqmlengine_p.h> +#include <private/qqmlopenmetaobject_p.h> +#include <qqml.h> + +QT_BEGIN_NAMESPACE + + +class DynamicRoleModelNode; + +class DynamicRoleModelNodeMetaObject : public QQmlOpenMetaObject +{ +public: + DynamicRoleModelNodeMetaObject(DynamicRoleModelNode *object); + ~DynamicRoleModelNodeMetaObject(); + + bool m_enabled; + +protected: + void propertyWrite(int index); + void propertyWritten(int index); + +private: + DynamicRoleModelNode *m_owner; +}; + +class DynamicRoleModelNode : public QObject +{ + Q_OBJECT +public: + DynamicRoleModelNode(QQmlListModel *owner, int uid); + + static DynamicRoleModelNode *create(const QVariantMap &obj, QQmlListModel *owner); + + void updateValues(const QVariantMap &object, QVector<int> &roles); + + QVariant getValue(const QString &name) + { + return m_meta->value(name.toUtf8()); + } + + bool setValue(const QByteArray &name, const QVariant &val) + { + return m_meta->setValue(name, val); + } + + void setNodeUpdatesEnabled(bool enable) + { + m_meta->m_enabled = enable; + } + + int getUid() const + { + return m_uid; + } + + static void sync(DynamicRoleModelNode *src, DynamicRoleModelNode *target, QHash<int, QQmlListModel *> *targetModelHash); + +private: + QQmlListModel *m_owner; + int m_uid; + DynamicRoleModelNodeMetaObject *m_meta; + + friend class DynamicRoleModelNodeMetaObject; +}; + +class ModelObject; + +class ModelNodeMetaObject : public QQmlOpenMetaObject +{ +public: + ModelNodeMetaObject(ModelObject *object); + ~ModelNodeMetaObject(); + + bool m_enabled; + +protected: + void propertyWritten(int index); + +private: + + ModelObject *m_obj; +}; + +class ModelObject : public QObject +{ + Q_OBJECT +public: + ModelObject(QQmlListModel *model, int elementIndex); + + void setValue(const QByteArray &name, const QVariant &val, bool force) + { + if (force) { + QVariant existingValue = m_meta->value(name); + if (existingValue.isValid()) { + (*m_meta)[name] = QVariant(); + } + } + m_meta->setValue(name, val); + } + + void setNodeUpdatesEnabled(bool enable) + { + m_meta->m_enabled = enable; + } + + void updateValues(); + void updateValues(const QVector<int> &roles); + + QQmlListModel *m_model; + int m_elementIndex; + +private: + ModelNodeMetaObject *m_meta; +}; + +class ListLayout +{ +public: + ListLayout() : currentBlock(0), currentBlockOffset(0) {} + ListLayout(const ListLayout *other); + ~ListLayout(); + + class Role + { + public: + + Role() : type(Invalid), blockIndex(-1), blockOffset(-1), index(-1), subLayout(0) {} + explicit Role(const Role *other); + ~Role(); + + // This enum must be kept in sync with the roleTypeNames variable in qdeclarativelistmodel.cpp + enum DataType + { + Invalid = -1, + + String, + Number, + Bool, + List, + QObject, + VariantMap, + DateTime, + + MaxDataType + }; + + QString name; + DataType type; + int blockIndex; + int blockOffset; + int index; + ListLayout *subLayout; + }; + + const Role *getRoleOrCreate(const QString &key, const QVariant &data); + const Role &getRoleOrCreate(v8::Handle<v8::String> key, Role::DataType type); + const Role &getRoleOrCreate(const QString &key, Role::DataType type); + + const Role &getExistingRole(int index) { return *roles.at(index); } + const Role *getExistingRole(const QString &key); + const Role *getExistingRole(v8::Handle<v8::String> key); + + int roleCount() const { return roles.count(); } + + static void sync(ListLayout *src, ListLayout *target); + +private: + const Role &createRole(const QString &key, Role::DataType type); + + int currentBlock; + int currentBlockOffset; + QVector<Role *> roles; + QStringHash<Role *> roleHash; +}; + +class ListElement +{ +public: + + ListElement(); + ListElement(int existingUid); + ~ListElement(); + + static void sync(ListElement *src, ListLayout *srcLayout, ListElement *target, ListLayout *targetLayout, QHash<int, ListModel *> *targetModelHash); + + enum + { + BLOCK_SIZE = 64 - sizeof(int) - sizeof(ListElement *) - sizeof(ModelObject *) + }; + +private: + + void destroy(ListLayout *layout); + + int setVariantProperty(const ListLayout::Role &role, const QVariant &d); + + int setJsProperty(const ListLayout::Role &role, v8::Handle<v8::Value> d, QV8Engine *eng); + + int setStringProperty(const ListLayout::Role &role, const QString &s); + int setDoubleProperty(const ListLayout::Role &role, double n); + int setBoolProperty(const ListLayout::Role &role, bool b); + int setListProperty(const ListLayout::Role &role, ListModel *m); + int setQObjectProperty(const ListLayout::Role &role, QObject *o); + int setVariantMapProperty(const ListLayout::Role &role, v8::Handle<v8::Object> o, QV8Engine *eng); + int setVariantMapProperty(const ListLayout::Role &role, QVariantMap *m); + int setDateTimeProperty(const ListLayout::Role &role, const QDateTime &dt); + + void setStringPropertyFast(const ListLayout::Role &role, const QString &s); + void setDoublePropertyFast(const ListLayout::Role &role, double n); + void setBoolPropertyFast(const ListLayout::Role &role, bool b); + void setQObjectPropertyFast(const ListLayout::Role &role, QObject *o); + void setListPropertyFast(const ListLayout::Role &role, ListModel *m); + void setVariantMapFast(const ListLayout::Role &role, v8::Handle<v8::Object> o, QV8Engine *eng); + void setDateTimePropertyFast(const ListLayout::Role &role, const QDateTime &dt); + + void clearProperty(const ListLayout::Role &role); + + QVariant getProperty(const ListLayout::Role &role, const QQmlListModel *owner, QV8Engine *eng); + ListModel *getListProperty(const ListLayout::Role &role); + QString *getStringProperty(const ListLayout::Role &role); + QObject *getQObjectProperty(const ListLayout::Role &role); + QQmlGuard<QObject> *getGuardProperty(const ListLayout::Role &role); + QVariantMap *getVariantMapProperty(const ListLayout::Role &role); + QDateTime *getDateTimeProperty(const ListLayout::Role &role); + + inline char *getPropertyMemory(const ListLayout::Role &role); + + int getUid() const { return uid; } + + char data[BLOCK_SIZE]; + ListElement *next; + + int uid; + ModelObject *m_objectCache; + + friend class ListModel; +}; + +class ListModel +{ +public: + + ListModel(ListLayout *layout, QQmlListModel *modelCache, int uid); + ~ListModel() {} + + void destroy(); + + int setOrCreateProperty(int elementIndex, const QString &key, const QVariant &data); + int setExistingProperty(int uid, const QString &key, v8::Handle<v8::Value> data, QV8Engine *eng); + + QVariant getProperty(int elementIndex, int roleIndex, const QQmlListModel *owner, QV8Engine *eng); + ListModel *getListProperty(int elementIndex, const ListLayout::Role &role); + + int roleCount() const + { + return m_layout->roleCount(); + } + + const ListLayout::Role &getExistingRole(int index) + { + return m_layout->getExistingRole(index); + } + + const ListLayout::Role &getOrCreateListRole(const QString &name) + { + return m_layout->getRoleOrCreate(name, ListLayout::Role::List); + } + + int elementCount() const + { + return elements.count(); + } + + void set(int elementIndex, v8::Handle<v8::Object> object, QVector<int> *roles, QV8Engine *eng); + void set(int elementIndex, v8::Handle<v8::Object> object, QV8Engine *eng); + + int append(v8::Handle<v8::Object> object, QV8Engine *eng); + void insert(int elementIndex, v8::Handle<v8::Object> object, QV8Engine *eng); + + void clear(); + 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); + + ModelObject *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; + }; + + void newElement(int index); + + void updateCacheIndices(); + + friend class ListElement; + friend class QQmlListModelWorkerAgent; +}; + +QT_END_NAMESPACE + +Q_DECLARE_METATYPE(ListModel *); + +#endif // QQUICKLISTMODEL_P_P_H + diff --git a/src/qml/types/qqmllistmodelworkeragent.cpp b/src/qml/types/qqmllistmodelworkeragent.cpp new file mode 100644 index 0000000000..9554e6d1e5 --- /dev/null +++ b/src/qml/types/qqmllistmodelworkeragent.cpp @@ -0,0 +1,259 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** 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 Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/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 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qqmllistmodelworkeragent_p.h" +#include "qqmllistmodel_p_p.h" +#include <private/qqmldata_p.h> +#include <private/qqmlengine_p.h> +#include <qqmlinfo.h> + +#include <QtCore/qcoreevent.h> +#include <QtCore/qcoreapplication.h> +#include <QtCore/qdebug.h> + + +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) +{ + 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) +: m_ref(1), m_orig(model), m_copy(new QQmlListModel(model, this)) +{ +} + +QQmlListModelWorkerAgent::~QQmlListModelWorkerAgent() +{ + mutex.lock(); + syncDone.wakeAll(); + mutex.unlock(); +} + +void QQmlListModelWorkerAgent::setV8Engine(QV8Engine *eng) +{ + m_copy->m_engine = eng; +} + +void QQmlListModelWorkerAgent::addref() +{ + m_ref.ref(); +} + +void QQmlListModelWorkerAgent::release() +{ + bool del = !m_ref.deref(); + + if (del) + deleteLater(); +} + +void QQmlListModelWorkerAgent::modelDestroyed() +{ + m_orig = 0; +} + +int QQmlListModelWorkerAgent::count() const +{ + return m_copy->count(); +} + +void QQmlListModelWorkerAgent::clear() +{ + m_copy->clear(); +} + +void QQmlListModelWorkerAgent::remove(QQmlV8Function *args) +{ + m_copy->remove(args); +} + +void QQmlListModelWorkerAgent::append(QQmlV8Function *args) +{ + m_copy->append(args); +} + +void QQmlListModelWorkerAgent::insert(QQmlV8Function *args) +{ + m_copy->insert(args); +} + +QQmlV8Handle QQmlListModelWorkerAgent::get(int index) const +{ + return m_copy->get(index); +} + +void QQmlListModelWorkerAgent::set(int index, const QQmlV8Handle &value) +{ + m_copy->set(index, value); +} + +void QQmlListModelWorkerAgent::setProperty(int index, const QString& property, const QVariant& value) +{ + m_copy->setProperty(index, property, value); +} + +void QQmlListModelWorkerAgent::move(int from, int to, int count) +{ + m_copy->move(from, to, count); +} + +void QQmlListModelWorkerAgent::sync() +{ + Sync *s = new Sync; + s->data = data; + s->list = m_copy; + data.changes.clear(); + + mutex.lock(); + QCoreApplication::postEvent(this, s); + syncDone.wait(&mutex); + mutex.unlock(); +} + +bool QQmlListModelWorkerAgent::event(QEvent *e) +{ + if (e->type() == QEvent::User) { + bool cc = false; + 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; + + Q_ASSERT(m_orig->m_dynamicRoles == s->list->m_dynamicRoles); + if (m_orig->m_dynamicRoles) + QQmlListModel::sync(s->list, m_orig, &targetModelDynamicHash); + 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; + } + } + } + } + + syncDone.wakeAll(); + locker.unlock(); + + if (cc) + emit m_orig->countChanged(); + return true; + } + + return QObject::event(e); +} + +QT_END_NAMESPACE + diff --git a/src/qml/types/qqmllistmodelworkeragent_p.h b/src/qml/types/qqmllistmodelworkeragent_p.h new file mode 100644 index 0000000000..614017069c --- /dev/null +++ b/src/qml/types/qqmllistmodelworkeragent_p.h @@ -0,0 +1,156 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** 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 Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/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 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKLISTMODELWORKERAGENT_P_H +#define QQUICKLISTMODELWORKERAGENT_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 <qqml.h> + +#include <QMutex> +#include <QWaitCondition> + +#include <private/qv8engine_p.h> + +QT_BEGIN_NAMESPACE + + +class QQmlListModel; + +class QQmlListModelWorkerAgent : public QObject +{ + Q_OBJECT + Q_PROPERTY(int count READ count) + +public: + QQmlListModelWorkerAgent(QQmlListModel *); + ~QQmlListModelWorkerAgent(); + void setV8Engine(QV8Engine *eng); + + void addref(); + void release(); + + int count() const; + + Q_INVOKABLE void clear(); + Q_INVOKABLE void remove(QQmlV8Function *args); + Q_INVOKABLE void append(QQmlV8Function *args); + Q_INVOKABLE void insert(QQmlV8Function *args); + Q_INVOKABLE QQmlV8Handle get(int index) const; + Q_INVOKABLE void set(int index, const QQmlV8Handle &); + Q_INVOKABLE void setProperty(int index, const QString& property, const QVariant& value); + Q_INVOKABLE void move(int from, int to, int count); + Q_INVOKABLE void sync(); + + struct VariantRef + { + VariantRef() : a(0) {} + 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(); } + + VariantRef &operator=(const VariantRef &o) { + if (o.a) o.a->addref(); + if (a) a->release(); a = o.a; + return *this; + } + + QQmlListModelWorkerAgent *a; + }; + void modelDestroyed(); +protected: + virtual bool event(QEvent *); + +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() : QEvent(QEvent::User) {} + Data data; + QQmlListModel *list; + }; + + QAtomicInt m_ref; + QQmlListModel *m_orig; + QQmlListModel *m_copy; + QMutex mutex; + QWaitCondition syncDone; +}; + +QT_END_NAMESPACE + +Q_DECLARE_METATYPE(QQmlListModelWorkerAgent::VariantRef) + +#endif // QQUICKLISTMODELWORKERAGENT_P_H + diff --git a/src/qml/types/qqmlmodelsmodule.cpp b/src/qml/types/qqmlmodelsmodule.cpp new file mode 100644 index 0000000000..4f6b0a5580 --- /dev/null +++ b/src/qml/types/qqmlmodelsmodule.cpp @@ -0,0 +1,60 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Research In Motion. +** Contact: http://www.qt-project.org/legal +** +** 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 Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/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 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qqmlmodelsmodule_p.h" +#include <private/qqmllistmodel_p.h> +#include <private/qqmldelegatemodel_p.h> +#include <private/qqmlobjectmodel_p.h> + +QT_BEGIN_NAMESPACE + +void QQmlModelsModule::defineModule() +{ + const char uri[] = "QtQml.Models"; + + qmlRegisterType<QQmlListElement>(uri, 2, 1, "ListElement"); + qmlRegisterCustomType<QQmlListModel>(uri, 2, 1, "ListModel", new QQmlListModelParser); + qmlRegisterType<QQmlDelegateModel>(uri, 2, 1, "DelegateModel"); + qmlRegisterType<QQmlDelegateModelGroup>(uri, 2, 1, "DelegateModelGroup"); + qmlRegisterType<QQmlObjectModel>(uri, 2, 1, "ObjectModel"); +} + +QT_END_NAMESPACE diff --git a/src/qml/types/qqmlmodelsmodule_p.h b/src/qml/types/qqmlmodelsmodule_p.h new file mode 100644 index 0000000000..6e72dadf8b --- /dev/null +++ b/src/qml/types/qqmlmodelsmodule_p.h @@ -0,0 +1,57 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Research In Motion. +** Contact: http://www.qt-project.org/legal +** +** 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 Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/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 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQMLMODELSMODULE_H +#define QQMLMODELSMODULE_H + +#include <private/qtqmlglobal_p.h> + +QT_BEGIN_NAMESPACE + +class Q_QML_PRIVATE_EXPORT QQmlModelsModule +{ +public: + static void defineModule(); +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/qml/types/qqmlobjectmodel.cpp b/src/qml/types/qqmlobjectmodel.cpp new file mode 100644 index 0000000000..7f7bf92fa3 --- /dev/null +++ b/src/qml/types/qqmlobjectmodel.cpp @@ -0,0 +1,269 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** 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 Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/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 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qqmlobjectmodel_p.h" + +#include <QtCore/qcoreapplication.h> +#include <QtQml/qqmlcontext.h> +#include <QtQml/qqmlengine.h> + +#include <private/qqmlchangeset_p.h> +#include <private/qqmlglobal_p.h> +#include <private/qobject_p.h> + +#include <QtCore/qhash.h> +#include <QtCore/qlist.h> + +QT_BEGIN_NAMESPACE + +QHash<QObject*, QQmlObjectModelAttached*> QQmlObjectModelAttached::attachedProperties; + + +class QQmlObjectModelPrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QQmlObjectModel) +public: + class Item { + public: + Item(QObject *i) : item(i), ref(0) {} + + void addRef() { ++ref; } + bool deref() { return --ref == 0; } + + QObject *item; + int ref; + }; + + QQmlObjectModelPrivate() : QObjectPrivate() {} + + static void children_append(QQmlListProperty<QObject> *prop, QObject *item) { + static_cast<QQmlObjectModelPrivate *>(prop->data)->children.append(Item(item)); + static_cast<QQmlObjectModelPrivate *>(prop->data)->itemAppended(); + static_cast<QQmlObjectModelPrivate *>(prop->data)->emitChildrenChanged(); + } + + static int children_count(QQmlListProperty<QObject> *prop) { + return static_cast<QQmlObjectModelPrivate *>(prop->data)->children.count(); + } + + static QObject *children_at(QQmlListProperty<QObject> *prop, int index) { + return static_cast<QQmlObjectModelPrivate *>(prop->data)->children.at(index).item; + } + + static void children_clear(QQmlListProperty<QObject> *prop) { + static_cast<QQmlObjectModelPrivate *>(prop->data)->itemCleared(static_cast<QQmlObjectModelPrivate *>(prop->data)->children); + static_cast<QQmlObjectModelPrivate *>(prop->data)->children.clear(); + static_cast<QQmlObjectModelPrivate *>(prop->data)->emitChildrenChanged(); + } + + void itemAppended() { + Q_Q(QQmlObjectModel); + QQmlObjectModelAttached *attached = QQmlObjectModelAttached::properties(children.last().item); + attached->setIndex(children.count()-1); + QQmlChangeSet changeSet; + changeSet.insert(children.count() - 1, 1); + emit q->modelUpdated(changeSet, false); + emit q->countChanged(); + } + + void itemCleared(const QList<Item> &children) { + Q_Q(QQmlObjectModel); + foreach (const Item &child, children) + emit q->destroyingItem(child.item); + emit q->countChanged(); + } + + void emitChildrenChanged() { + Q_Q(QQmlObjectModel); + emit q->childrenChanged(); + } + + int indexOf(QObject *item) const { + for (int i = 0; i < children.count(); ++i) + if (children.at(i).item == item) + return i; + return -1; + } + + + QList<Item> children; +}; + + +/*! + \qmltype ObjectModel + \instantiates QQmlObjectModel + \inqmlmodule QtQml.Models 2 + \ingroup qtquick-models + \brief Defines a set of items to be used as a model + + A ObjectModel contains the visual items to be used in a view. + When a ObjectModel is used in a view, the view does not require + a delegate since the ObjectModel already contains the visual + delegate (items). + + An item can determine its index within the + model via the \l{ObjectModel::index}{index} attached property. + + The example below places three colored rectangles in a ListView. + \code + import QtQuick 2.0 + + Rectangle { + ObjectModel { + id: itemModel + Rectangle { height: 30; width: 80; color: "red" } + Rectangle { height: 30; width: 80; color: "green" } + Rectangle { height: 30; width: 80; color: "blue" } + } + + ListView { + anchors.fill: parent + model: itemModel + } + } + \endcode + + \image visualitemmodel.png + + \sa {quick/views/objectmodel}{ObjectModel example} +*/ +/*! + \qmltype VisualItemModel + \instantiates QQmlObjectModel + \inqmlmodule QtQuick 2 + \brief Defines a set of objects to be used as a model + + The VisualItemModel type encapsulates 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 QtQuick for + compatibility reasons. + + For full details about the type, see the \l ObjectModel documentation. + + \sa {QtQml.Models2::ObjectModel} +*/ + +QQmlObjectModel::QQmlObjectModel(QObject *parent) + : QQmlInstanceModel(*(new QQmlObjectModelPrivate), parent) +{ +} + +/*! + \qmlattachedproperty int QtQml.Models2::ObjectModel::index + This attached property holds the index of this delegate's item within the model. + + It is attached to each instance of the delegate. +*/ + +QQmlListProperty<QObject> QQmlObjectModel::children() +{ + Q_D(QQmlObjectModel); + return QQmlListProperty<QObject>(this, + d, + d->children_append, + d->children_count, + d->children_at, + d->children_clear); +} + +/*! + \qmlproperty int QtQml.Models2::ObjectModel::count + + The number of items in the model. This property is readonly. +*/ +int QQmlObjectModel::count() const +{ + Q_D(const QQmlObjectModel); + return d->children.count(); +} + +bool QQmlObjectModel::isValid() const +{ + return true; +} + +QObject *QQmlObjectModel::object(int index, bool) +{ + Q_D(QQmlObjectModel); + QQmlObjectModelPrivate::Item &item = d->children[index]; + item.addRef(); + if (item.ref == 1) { + emit initItem(index, item.item); + emit createdItem(index, item.item); + } + return item.item; +} + +QQmlInstanceModel::ReleaseFlags QQmlObjectModel::release(QObject *item) +{ + Q_D(QQmlObjectModel); + int idx = d->indexOf(item); + if (idx >= 0) { + if (!d->children[idx].deref()) + return QQmlInstanceModel::Referenced; + } + return 0; +} + +QString QQmlObjectModel::stringValue(int index, const QString &name) +{ + Q_D(QQmlObjectModel); + if (index < 0 || index >= d->children.count()) + return QString(); + return QQmlEngine::contextForObject(d->children.at(index).item)->contextProperty(name).toString(); +} + +int QQmlObjectModel::indexOf(QObject *item, QObject *) const +{ + Q_D(const QQmlObjectModel); + return d->indexOf(item); +} + +QQmlObjectModelAttached *QQmlObjectModel::qmlAttachedProperties(QObject *obj) +{ + return QQmlObjectModelAttached::properties(obj); +} + +QT_END_NAMESPACE + diff --git a/src/qml/types/qqmlobjectmodel_p.h b/src/qml/types/qqmlobjectmodel_p.h new file mode 100644 index 0000000000..59a4a551a7 --- /dev/null +++ b/src/qml/types/qqmlobjectmodel_p.h @@ -0,0 +1,170 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** 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 Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/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 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQMLINSTANCEMODEL_P_H +#define QQMLINSTANCEMODEL_P_H + +#include <private/qtqmlglobal_p.h> +#include <QtQml/qqml.h> +#include <QtCore/qobject.h> + +QT_BEGIN_NAMESPACE + +class QObject; +class QQmlChangeSet; + +class Q_QML_PRIVATE_EXPORT QQmlInstanceModel : public QObject +{ + Q_OBJECT + + Q_PROPERTY(int count READ count NOTIFY countChanged) + +public: + virtual ~QQmlInstanceModel() {} + + enum ReleaseFlag { Referenced = 0x01, Destroyed = 0x02 }; + Q_DECLARE_FLAGS(ReleaseFlags, ReleaseFlag) + + virtual int count() const = 0; + virtual bool isValid() const = 0; + virtual QObject *object(int index, bool asynchronous=false) = 0; + virtual ReleaseFlags release(QObject *object) = 0; + virtual void cancel(int) {} + virtual QString stringValue(int, const QString &) = 0; + virtual void setWatchedRoles(QList<QByteArray> roles) = 0; + + virtual int indexOf(QObject *object, QObject *objectContext) const = 0; + +Q_SIGNALS: + void countChanged(); + void modelUpdated(const QQmlChangeSet &changeSet, bool reset); + void createdItem(int index, QObject *object); + void initItem(int index, QObject *object); + void destroyingItem(QObject *object); + +protected: + QQmlInstanceModel(QObjectPrivate &dd, QObject *parent = 0) + : QObject(dd, parent) {} + +private: + Q_DISABLE_COPY(QQmlInstanceModel) +}; + +class QQmlObjectModelAttached; +class QQmlObjectModelPrivate; +class Q_QML_PRIVATE_EXPORT QQmlObjectModel : public QQmlInstanceModel +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QQmlObjectModel) + + Q_PROPERTY(QQmlListProperty<QObject> children READ children NOTIFY childrenChanged DESIGNABLE false) + Q_CLASSINFO("DefaultProperty", "children") + +public: + QQmlObjectModel(QObject *parent=0); + virtual ~QQmlObjectModel() {} + + virtual int count() const; + virtual bool isValid() const; + virtual QObject *object(int index, bool asynchronous=false); + virtual ReleaseFlags release(QObject *object); + virtual QString stringValue(int index, const QString &role); + virtual void setWatchedRoles(QList<QByteArray>) {} + + virtual int indexOf(QObject *object, QObject *objectContext) const; + + QQmlListProperty<QObject> children(); + + static QQmlObjectModelAttached *qmlAttachedProperties(QObject *obj); + +Q_SIGNALS: + void childrenChanged(); + +private: + Q_DISABLE_COPY(QQmlObjectModel) +}; + +class QQmlObjectModelAttached : public QObject +{ + Q_OBJECT + +public: + QQmlObjectModelAttached(QObject *parent) + : QObject(parent), m_index(0) {} + ~QQmlObjectModelAttached() { + attachedProperties.remove(parent()); + } + + Q_PROPERTY(int index READ index NOTIFY indexChanged) + int index() const { return m_index; } + void setIndex(int idx) { + if (m_index != idx) { + m_index = idx; + emit indexChanged(); + } + } + + static QQmlObjectModelAttached *properties(QObject *obj) { + QQmlObjectModelAttached *rv = attachedProperties.value(obj); + if (!rv) { + rv = new QQmlObjectModelAttached(obj); + attachedProperties.insert(obj, rv); + } + return rv; + } + +Q_SIGNALS: + void indexChanged(); + +public: + int m_index; + + static QHash<QObject*, QQmlObjectModelAttached*> attachedProperties; +}; + + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQmlInstanceModel) +QML_DECLARE_TYPE(QQmlObjectModel) +QML_DECLARE_TYPEINFO(QQmlObjectModel, QML_HAS_ATTACHED_PROPERTIES) + +#endif // QQMLINSTANCEMODEL_P_H diff --git a/src/qml/types/qqmltimer.cpp b/src/qml/types/qqmltimer.cpp new file mode 100644 index 0000000000..a1cb8532f7 --- /dev/null +++ b/src/qml/types/qqmltimer.cpp @@ -0,0 +1,328 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** 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 Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/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 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qqmltimer_p.h" + +#include <QtCore/qcoreapplication.h> +#include "private/qpauseanimationjob_p.h" +#include <qdebug.h> + +#include <private/qobject_p.h> + +QT_BEGIN_NAMESPACE + + + +class QQmlTimerPrivate : public QObjectPrivate, public QAnimationJobChangeListener +{ + Q_DECLARE_PUBLIC(QQmlTimer) +public: + QQmlTimerPrivate() + : interval(1000), running(false), repeating(false), triggeredOnStart(false) + , classBegun(false), componentComplete(false), firstTick(true) {} + + virtual void animationFinished(QAbstractAnimationJob *); + virtual void animationCurrentLoopChanged(QAbstractAnimationJob *) { Q_Q(QQmlTimer); q->ticked(); } + + int interval; + QPauseAnimationJob pause; + bool running : 1; + bool repeating : 1; + bool triggeredOnStart : 1; + bool classBegun : 1; + bool componentComplete : 1; + bool firstTick : 1; +}; + +/*! + \qmltype Timer + \instantiates QQmlTimer + \inqmlmodule QtQml 2 + \ingroup qtquick-interceptors + \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. + + Here is a Timer that shows the current date and time, and updates + the text every 500 milliseconds. It uses the JavaScript \c Date + object to access the current time. + + \qml + import QtQuick 2.0 + + Item { + Timer { + interval: 500; running: true; repeat: true + onTriggered: time.text = Date().toString() + } + + Text { id: time } + } + \endqml + + The Timer type is synchronized with the animation timer. Since the animation + timer is usually set to 60fps, the resolution of Timer will be + at best 16ms. + + If the Timer is running and one of its properties is changed, the + elapsed time will be reset. For example, if a Timer with interval of + 1000ms has its \e repeat property changed 500ms after starting, the + elapsed time will be reset to 0, and the Timer will be triggered + 1000ms later. + + \sa {declarative/toys/clocks}{Clocks example} +*/ + +QQmlTimer::QQmlTimer(QObject *parent) + : QObject(*(new QQmlTimerPrivate), parent) +{ + Q_D(QQmlTimer); + d->pause.addAnimationChangeListener(d, QAbstractAnimationJob::Completion | QAbstractAnimationJob::CurrentLoop); + d->pause.setLoopCount(1); + d->pause.setDuration(d->interval); +} + +/*! + \qmlproperty int QtQml2::Timer::interval + + Sets the \a interval between triggers, in milliseconds. + + The default interval is 1000 milliseconds. +*/ +void QQmlTimer::setInterval(int interval) +{ + Q_D(QQmlTimer); + if (interval != d->interval) { + d->interval = interval; + update(); + emit intervalChanged(); + } +} + +int QQmlTimer::interval() const +{ + Q_D(const QQmlTimer); + return d->interval; +} + +/*! + \qmlproperty bool QtQml2::Timer::running + + If set to true, starts the timer; otherwise stops the timer. + For a non-repeating timer, \a running is set to false after the + timer has been triggered. + + \a running defaults to false. + + \sa repeat +*/ +bool QQmlTimer::isRunning() const +{ + Q_D(const QQmlTimer); + return d->running; +} + +void QQmlTimer::setRunning(bool running) +{ + Q_D(QQmlTimer); + if (d->running != running) { + d->running = running; + d->firstTick = true; + emit runningChanged(); + update(); + } +} + +/*! + \qmlproperty bool QtQml2::Timer::repeat + + If \a repeat is true the timer is triggered repeatedly at the + specified interval; otherwise, the timer will trigger once at the + specified interval and then stop (i.e. running will be set to false). + + \a repeat defaults to false. + + \sa running +*/ +bool QQmlTimer::isRepeating() const +{ + Q_D(const QQmlTimer); + return d->repeating; +} + +void QQmlTimer::setRepeating(bool repeating) +{ + Q_D(QQmlTimer); + if (repeating != d->repeating) { + d->repeating = repeating; + update(); + emit repeatChanged(); + } +} + +/*! + \qmlproperty bool QtQml2::Timer::triggeredOnStart + + When a timer is started, the first trigger is usually after the specified + interval has elapsed. It is sometimes desirable to trigger immediately + when the timer is started; for example, to establish an initial + state. + + If \a triggeredOnStart is true, the timer is triggered immediately + when started, and subsequently at the specified interval. Note that if + \e repeat is set to false, the timer is triggered twice; once on start, + and again at the interval. + + \a triggeredOnStart defaults to false. + + \sa running +*/ +bool QQmlTimer::triggeredOnStart() const +{ + Q_D(const QQmlTimer); + return d->triggeredOnStart; +} + +void QQmlTimer::setTriggeredOnStart(bool triggeredOnStart) +{ + Q_D(QQmlTimer); + if (d->triggeredOnStart != triggeredOnStart) { + d->triggeredOnStart = triggeredOnStart; + update(); + emit triggeredOnStartChanged(); + } +} + +/*! + \qmlmethod QtQml2::Timer::start() + \brief Starts the timer + + If the timer is already running, calling this method has no effect. The + \c running property will be true following a call to \c start(). +*/ +void QQmlTimer::start() +{ + setRunning(true); +} + +/*! + \qmlmethod QtQml2::Timer::stop() + \brief Stops the timer + + If the timer is not running, calling this method has no effect. The + \c running property will be false following a call to \c stop(). +*/ +void QQmlTimer::stop() +{ + setRunning(false); +} + +/*! + \qmlmethod QtQml2::Timer::restart() + \brief Restarts the timer + + If the Timer is not running it will be started, otherwise it will be + stopped, reset to initial state and started. The \c running property + will be true following a call to \c restart(). +*/ +void QQmlTimer::restart() +{ + setRunning(false); + setRunning(true); +} + +void QQmlTimer::update() +{ + Q_D(QQmlTimer); + if (d->classBegun && !d->componentComplete) + return; + d->pause.stop(); + if (d->running) { + d->pause.setCurrentTime(0); + d->pause.setLoopCount(d->repeating ? -1 : 1); + d->pause.setDuration(d->interval); + d->pause.start(); + if (d->triggeredOnStart && d->firstTick) { + QCoreApplication::removePostedEvents(this, QEvent::MetaCall); + QMetaObject::invokeMethod(this, "ticked", Qt::QueuedConnection); + } + } +} + +void QQmlTimer::classBegin() +{ + Q_D(QQmlTimer); + d->classBegun = true; +} + +void QQmlTimer::componentComplete() +{ + Q_D(QQmlTimer); + d->componentComplete = true; + update(); +} + +/*! + \qmlsignal QtQml2::Timer::onTriggered() + + This handler is called when the Timer is triggered. +*/ +void QQmlTimer::ticked() +{ + Q_D(QQmlTimer); + if (d->running && (d->pause.currentTime() > 0 || (d->triggeredOnStart && d->firstTick))) + emit triggered(); + d->firstTick = false; +} + +void QQmlTimerPrivate::animationFinished(QAbstractAnimationJob *) +{ + Q_Q(QQmlTimer); + if (repeating || !running) + return; + running = false; + firstTick = false; + emit q->triggered(); + emit q->runningChanged(); +} + +QT_END_NAMESPACE diff --git a/src/qml/types/qqmltimer_p.h b/src/qml/types/qqmltimer_p.h new file mode 100644 index 0000000000..c625522851 --- /dev/null +++ b/src/qml/types/qqmltimer_p.h @@ -0,0 +1,107 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** 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 Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/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 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQMLTIMER_H +#define QQMLTIMER_H + +#include <qqml.h> + +#include <QtCore/qobject.h> + +#include <private/qtqmlglobal_p.h> + +QT_BEGIN_NAMESPACE + +class QQmlTimerPrivate; +class Q_QML_PRIVATE_EXPORT QQmlTimer : public QObject, public QQmlParserStatus +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QQmlTimer) + Q_INTERFACES(QQmlParserStatus) + Q_PROPERTY(int interval READ interval WRITE setInterval NOTIFY intervalChanged) + Q_PROPERTY(bool running READ isRunning WRITE setRunning NOTIFY runningChanged) + Q_PROPERTY(bool repeat READ isRepeating WRITE setRepeating NOTIFY repeatChanged) + Q_PROPERTY(bool triggeredOnStart READ triggeredOnStart WRITE setTriggeredOnStart NOTIFY triggeredOnStartChanged) + Q_PROPERTY(QObject *parent READ parent CONSTANT) + +public: + QQmlTimer(QObject *parent=0); + + void setInterval(int interval); + int interval() const; + + bool isRunning() const; + void setRunning(bool running); + + bool isRepeating() const; + void setRepeating(bool repeating); + + bool triggeredOnStart() const; + void setTriggeredOnStart(bool triggeredOnStart); + +protected: + void classBegin(); + void componentComplete(); + +public Q_SLOTS: + void start(); + void stop(); + void restart(); + +Q_SIGNALS: + void triggered(); + void runningChanged(); + void intervalChanged(); + void repeatChanged(); + void triggeredOnStartChanged(); + +private: + void update(); + +private Q_SLOTS: + void ticked(); +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQmlTimer) + +#endif diff --git a/src/qml/types/qquickpackage.cpp b/src/qml/types/qquickpackage.cpp new file mode 100644 index 0000000000..e885524b27 --- /dev/null +++ b/src/qml/types/qquickpackage.cpp @@ -0,0 +1,198 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** 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 Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/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 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquickpackage_p.h" + +#include <private/qobject_p.h> +#include <private/qqmlguard_p.h> + +QT_BEGIN_NAMESPACE + +/*! + \qmltype Package + \instantiates QQuickPackage + \inqmlmodule QtQuick 2 + \ingroup qtquick-views + \brief Specifies a collection of named items + + The Package class is used in conjunction with + VisualDataModel to enable delegates with a shared context + to be provided to multiple views. + + Any item within a Package may be assigned a name via the + \l{Package::name}{Package.name} attached property. + + The example below creates a Package containing two named items; + \e list and \e grid. The third item in the package (the \l Rectangle) is parented to whichever + delegate it should appear in. This allows an item to move + between views. + + \snippet quick/views/package/Delegate.qml 0 + + These named items are used as the delegates by the two views who + reference the special \l{VisualDataModel::parts} property to select + a model which provides the chosen delegate. + + \snippet quick/views/package/view.qml 0 + + \sa {quick/views/package}{Package example}, {quick/demos/photoviewer}{Photo Viewer example}, QtQml +*/ + +/*! + \qmlattachedproperty string QtQuick2::Package::name + This attached property holds the name of an item within a Package. +*/ + + +class QQuickPackagePrivate : public QObjectPrivate +{ +public: + QQuickPackagePrivate() {} + + struct DataGuard : public QQmlGuard<QObject> + { + DataGuard(QObject *obj, QList<DataGuard> *l) : list(l) { (QQmlGuard<QObject>&)*this = obj; } + QList<DataGuard> *list; + void objectDestroyed(QObject *) { + // we assume priv will always be destroyed after objectDestroyed calls + list->removeOne(*this); + } + }; + + QList<DataGuard> dataList; + static void data_append(QQmlListProperty<QObject> *prop, QObject *o) { + QList<DataGuard> *list = static_cast<QList<DataGuard> *>(prop->data); + list->append(DataGuard(o, list)); + } + static void data_clear(QQmlListProperty<QObject> *prop) { + QList<DataGuard> *list = static_cast<QList<DataGuard> *>(prop->data); + list->clear(); + } + static QObject *data_at(QQmlListProperty<QObject> *prop, int index) { + QList<DataGuard> *list = static_cast<QList<DataGuard> *>(prop->data); + return list->at(index); + } + static int data_count(QQmlListProperty<QObject> *prop) { + QList<DataGuard> *list = static_cast<QList<DataGuard> *>(prop->data); + return list->count(); + } +}; + +QHash<QObject *, QQuickPackageAttached *> QQuickPackageAttached::attached; + +QQuickPackageAttached::QQuickPackageAttached(QObject *parent) +: QObject(parent) +{ + attached.insert(parent, this); +} + +QQuickPackageAttached::~QQuickPackageAttached() +{ + attached.remove(parent()); +} + +QString QQuickPackageAttached::name() const +{ + return _name; +} + +void QQuickPackageAttached::setName(const QString &n) +{ + _name = n; +} + +QQuickPackage::QQuickPackage(QObject *parent) + : QObject(*(new QQuickPackagePrivate), parent) +{ +} + +QQuickPackage::~QQuickPackage() +{ +} + +QQmlListProperty<QObject> QQuickPackage::data() +{ + Q_D(QQuickPackage); + return QQmlListProperty<QObject>(this, &d->dataList, QQuickPackagePrivate::data_append, + QQuickPackagePrivate::data_count, + QQuickPackagePrivate::data_at, + QQuickPackagePrivate::data_clear); +} + +bool QQuickPackage::hasPart(const QString &name) +{ + Q_D(QQuickPackage); + for (int ii = 0; ii < d->dataList.count(); ++ii) { + QObject *obj = d->dataList.at(ii); + QQuickPackageAttached *a = QQuickPackageAttached::attached.value(obj); + if (a && a->name() == name) + return true; + } + return false; +} + +QObject *QQuickPackage::part(const QString &name) +{ + Q_D(QQuickPackage); + if (name.isEmpty() && !d->dataList.isEmpty()) + return d->dataList.at(0); + + for (int ii = 0; ii < d->dataList.count(); ++ii) { + QObject *obj = d->dataList.at(ii); + QQuickPackageAttached *a = QQuickPackageAttached::attached.value(obj); + if (a && a->name() == name) + return obj; + } + + if (name == QLatin1String("default") && !d->dataList.isEmpty()) + return d->dataList.at(0); + + return 0; +} + +QQuickPackageAttached *QQuickPackage::qmlAttachedProperties(QObject *o) +{ + return new QQuickPackageAttached(o); +} + + + +QT_END_NAMESPACE diff --git a/src/qml/types/qquickpackage_p.h b/src/qml/types/qquickpackage_p.h new file mode 100644 index 0000000000..9427c886a8 --- /dev/null +++ b/src/qml/types/qquickpackage_p.h @@ -0,0 +1,92 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** 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 Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/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 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKPACKAGE_H +#define QQUICKPACKAGE_H + +#include <qqml.h> + +QT_BEGIN_NAMESPACE + +class QQuickPackagePrivate; +class QQuickPackageAttached; +class Q_AUTOTEST_EXPORT QQuickPackage : public QObject +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QQuickPackage) + + Q_CLASSINFO("DefaultProperty", "data") + Q_PROPERTY(QQmlListProperty<QObject> data READ data) + +public: + QQuickPackage(QObject *parent=0); + virtual ~QQuickPackage(); + + QQmlListProperty<QObject> data(); + + QObject *part(const QString & = QString()); + bool hasPart(const QString &); + + static QQuickPackageAttached *qmlAttachedProperties(QObject *); +}; + +class QQuickPackageAttached : public QObject +{ +Q_OBJECT +Q_PROPERTY(QString name READ name WRITE setName) +public: + QQuickPackageAttached(QObject *parent); + virtual ~QQuickPackageAttached(); + + QString name() const; + void setName(const QString &n); + + static QHash<QObject *, QQuickPackageAttached *> attached; +private: + QString _name; +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickPackage) +QML_DECLARE_TYPEINFO(QQuickPackage, QML_HAS_ATTACHED_PROPERTIES) + +#endif // QQUICKPACKAGE_H diff --git a/src/qml/types/qquickworkerscript.cpp b/src/qml/types/qquickworkerscript.cpp new file mode 100644 index 0000000000..ad09e1ec0c --- /dev/null +++ b/src/qml/types/qquickworkerscript.cpp @@ -0,0 +1,740 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** 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 Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/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 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquickworkerscript_p.h" +#include "qqmllistmodel_p.h" +#include "qqmllistmodelworkeragent_p.h" +#include <private/qqmlengine_p.h> +#include <private/qqmlexpression_p.h> + +#include <QtCore/qcoreevent.h> +#include <QtCore/qcoreapplication.h> +#include <QtCore/qdebug.h> +#include <QtQml/qjsengine.h> +#include <QtCore/qmutex.h> +#include <QtCore/qwaitcondition.h> +#include <QtCore/qfile.h> +#include <QtCore/qdatetime.h> +#include <QtNetwork/qnetworkaccessmanager.h> +#include <QtQml/qqmlinfo.h> +#include <QtQml/qqmlfile.h> +#include "qqmlnetworkaccessmanagerfactory.h" + +#include <private/qv8engine_p.h> +#include <private/qv8worker_p.h> + +QT_BEGIN_NAMESPACE + +class WorkerDataEvent : public QEvent +{ +public: + enum Type { WorkerData = QEvent::User }; + + WorkerDataEvent(int workerId, const QByteArray &data); + virtual ~WorkerDataEvent(); + + int workerId() const; + QByteArray data() const; + +private: + int m_id; + QByteArray m_data; +}; + +class WorkerLoadEvent : public QEvent +{ +public: + enum Type { WorkerLoad = WorkerDataEvent::WorkerData + 1 }; + + WorkerLoadEvent(int workerId, const QUrl &url); + + int workerId() const; + QUrl url() const; + +private: + int m_id; + QUrl m_url; +}; + +class WorkerRemoveEvent : public QEvent +{ +public: + enum Type { WorkerRemove = WorkerLoadEvent::WorkerLoad + 1 }; + + WorkerRemoveEvent(int workerId); + + int workerId() const; + +private: + int m_id; +}; + +class WorkerErrorEvent : public QEvent +{ +public: + enum Type { WorkerError = WorkerRemoveEvent::WorkerRemove + 1 }; + + WorkerErrorEvent(const QQmlError &error); + + QQmlError error() const; + +private: + QQmlError m_error; +}; + +class QQuickWorkerScriptEnginePrivate : public QObject +{ + Q_OBJECT +public: + enum WorkerEventTypes { + WorkerDestroyEvent = QEvent::User + 100 + }; + + QQuickWorkerScriptEnginePrivate(QQmlEngine *eng); + + class WorkerEngine : public QV8Engine + { + public: + WorkerEngine(QQuickWorkerScriptEnginePrivate *parent); + ~WorkerEngine(); + + void init(); + virtual QNetworkAccessManager *networkAccessManager(); + + QQuickWorkerScriptEnginePrivate *p; + + v8::Local<v8::Function> sendFunction(int id); + void callOnMessage(v8::Handle<v8::Object> object, v8::Handle<v8::Value> arg); + private: + v8::Persistent<v8::Function> onmessage; + v8::Persistent<v8::Function> createsend; + QNetworkAccessManager *accessManager; + }; + + 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; + v8::Persistent<v8::Object> object; + }; + + QHash<int, WorkerScript *> workers; + v8::Handle<v8::Object> getWorker(WorkerScript *); + + int m_nextId; + + static v8::Handle<v8::Value> sendMessage(const v8::Arguments &args); + +signals: + void stopThread(); + +protected: + virtual bool event(QEvent *); + +private: + void processMessage(int, const QByteArray &); + void processLoad(int, const QUrl &); + void reportScriptException(WorkerScript *, const QQmlError &error); +}; + +QQuickWorkerScriptEnginePrivate::WorkerEngine::WorkerEngine(QQuickWorkerScriptEnginePrivate *parent) +: QV8Engine(0), p(parent), accessManager(0) +{ +} + +QQuickWorkerScriptEnginePrivate::WorkerEngine::~WorkerEngine() +{ + qPersistentDispose(createsend); + qPersistentDispose(onmessage); + delete accessManager; +} + +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); "\ + "}); "\ + "}); "\ + "})" + + v8::HandleScope handle_scope; + v8::Context::Scope scope(context()); + + { + v8::Local<v8::Script> onmessagescript = v8::Script::New(v8::String::New(CALL_ONMESSAGE_SCRIPT)); + onmessage = qPersistentNew<v8::Function>(v8::Handle<v8::Function>::Cast(onmessagescript->Run())); + } + { + v8::Local<v8::Script> createsendscript = v8::Script::New(v8::String::New(SEND_MESSAGE_CREATE_SCRIPT)); + v8::Local<v8::Function> createsendconstructor = v8::Local<v8::Function>::Cast(createsendscript->Run()); + + v8::Handle<v8::Value> args[] = { + V8FUNCTION(QQuickWorkerScriptEnginePrivate::sendMessage, this) + }; + v8::Local<v8::Value> createsendvalue = createsendconstructor->Call(global(), 1, args); + + createsend = qPersistentNew<v8::Function>(v8::Handle<v8::Function>::Cast(createsendvalue)); + } +} + +// Requires handle and context scope +v8::Local<v8::Function> QQuickWorkerScriptEnginePrivate::WorkerEngine::sendFunction(int id) +{ + v8::Handle<v8::Value> args[] = { v8::Integer::New(id) }; + return v8::Local<v8::Function>::Cast(createsend->Call(global(), 1, args)); +} + +// Requires handle and context scope +void QQuickWorkerScriptEnginePrivate::WorkerEngine::callOnMessage(v8::Handle<v8::Object> object, + v8::Handle<v8::Value> arg) +{ + v8::Handle<v8::Value> args[] = { object, arg }; + onmessage->Call(global(), 2, args); +} + +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; +} + +QQuickWorkerScriptEnginePrivate::QQuickWorkerScriptEnginePrivate(QQmlEngine *engine) +: workerEngine(0), qmlengine(engine), m_nextId(0) +{ +} + +v8::Handle<v8::Value> QQuickWorkerScriptEnginePrivate::sendMessage(const v8::Arguments &args) +{ + WorkerEngine *engine = (WorkerEngine*)V8ENGINE(); + + int id = args[1]->Int32Value(); + + QByteArray data = QV8Worker::serialize(args[2], engine); + + QMutexLocker locker(&engine->p->m_lock); + WorkerScript *script = engine->p->workers.value(id); + if (!script) + return v8::Undefined(); + + if (script->owner) + QCoreApplication::postEvent(script->owner, new WorkerDataEvent(0, data)); + + return v8::Undefined(); +} + +// Requires handle scope and context scope +v8::Handle<v8::Object> QQuickWorkerScriptEnginePrivate::getWorker(WorkerScript *script) +{ + if (!script->initialized) { + script->initialized = true; + + script->object = qPersistentNew<v8::Object>(workerEngine->contextWrapper()->urlScope(script->source)); + + workerEngine->contextWrapper()->setReadOnly(script->object, false); + + v8::Local<v8::Object> api = v8::Object::New(); + api->Set(v8::String::New("sendMessage"), workerEngine->sendFunction(script->id)); + + script->object->Set(v8::String::New("WorkerScript"), api); + + workerEngine->contextWrapper()->setReadOnly(script->object, true); + } + + return script->object; +} + +bool QQuickWorkerScriptEnginePrivate::event(QEvent *event) +{ + if (event->type() == (QEvent::Type)WorkerDataEvent::WorkerData) { + WorkerDataEvent *workerEvent = static_cast<WorkerDataEvent *>(event); + processMessage(workerEvent->workerId(), workerEvent->data()); + return true; + } else if (event->type() == (QEvent::Type)WorkerLoadEvent::WorkerLoad) { + WorkerLoadEvent *workerEvent = static_cast<WorkerLoadEvent *>(event); + processLoad(workerEvent->workerId(), workerEvent->url()); + return true; + } else if (event->type() == (QEvent::Type)WorkerDestroyEvent) { + emit stopThread(); + return true; + } else if (event->type() == (QEvent::Type)WorkerRemoveEvent::WorkerRemove) { + WorkerRemoveEvent *workerEvent = static_cast<WorkerRemoveEvent *>(event); + workers.remove(workerEvent->workerId()); + return true; + } else { + return QObject::event(event); + } +} + +void QQuickWorkerScriptEnginePrivate::processMessage(int id, const QByteArray &data) +{ + WorkerScript *script = workers.value(id); + if (!script) + return; + + v8::HandleScope handle_scope; + v8::Context::Scope scope(workerEngine->context()); + + v8::Handle<v8::Value> value = QV8Worker::deserialize(data, workerEngine); + + v8::TryCatch tc; + workerEngine->callOnMessage(script->object, value); + + if (tc.HasCaught()) { + QQmlError error; + QQmlExpressionPrivate::exceptionToError(tc.Message(), error); + reportScriptException(script, error); + } +} + +void QQuickWorkerScriptEnginePrivate::processLoad(int id, const QUrl &url) +{ + if (url.isRelative()) + return; + + QString fileName = QQmlFile::urlToLocalFileOrQrc(url); + + QFile f(fileName); + if (f.open(QIODevice::ReadOnly)) { + QByteArray data = f.readAll(); + QString sourceCode = QString::fromUtf8(data); + QQmlScript::Parser::extractPragmas(sourceCode); + + v8::HandleScope handle_scope; + v8::Context::Scope scope(workerEngine->context()); + + WorkerScript *script = workers.value(id); + if (!script) + return; + script->source = url; + v8::Handle<v8::Object> activation = getWorker(script); + if (activation.IsEmpty()) + return; + + // XXX ??? + // workerEngine->baseUrl = url; + + v8::TryCatch tc; + v8::Local<v8::Script> program = workerEngine->qmlModeCompile(sourceCode, url.toString()); + + if (!tc.HasCaught()) + program->Run(activation); + + if (tc.HasCaught()) { + QQmlError error; + QQmlExpressionPrivate::exceptionToError(tc.Message(), error); + reportScriptException(script, error); + } + } else { + qWarning().nospace() << "WorkerScript: Cannot find source file " << url.toString(); + } +} + +void QQuickWorkerScriptEnginePrivate::reportScriptException(WorkerScript *script, + const QQmlError &error) +{ + QQuickWorkerScriptEnginePrivate *p = QQuickWorkerScriptEnginePrivate::get(workerEngine); + + QMutexLocker locker(&p->m_lock); + if (script->owner) + QCoreApplication::postEvent(script->owner, new WorkerErrorEvent(error)); +} + +WorkerDataEvent::WorkerDataEvent(int workerId, const QByteArray &data) +: QEvent((QEvent::Type)WorkerData), m_id(workerId), m_data(data) +{ +} + +WorkerDataEvent::~WorkerDataEvent() +{ +} + +int WorkerDataEvent::workerId() const +{ + return m_id; +} + +QByteArray WorkerDataEvent::data() const +{ + return m_data; +} + +WorkerLoadEvent::WorkerLoadEvent(int workerId, const QUrl &url) +: QEvent((QEvent::Type)WorkerLoad), m_id(workerId), m_url(url) +{ +} + +int WorkerLoadEvent::workerId() const +{ + return m_id; +} + +QUrl WorkerLoadEvent::url() const +{ + return m_url; +} + +WorkerRemoveEvent::WorkerRemoveEvent(int workerId) +: QEvent((QEvent::Type)WorkerRemove), m_id(workerId) +{ +} + +int WorkerRemoveEvent::workerId() const +{ + return m_id; +} + +WorkerErrorEvent::WorkerErrorEvent(const QQmlError &error) +: QEvent((QEvent::Type)WorkerError), m_error(error) +{ +} + +QQmlError WorkerErrorEvent::error() const +{ + return m_error; +} + +QQuickWorkerScriptEngine::QQuickWorkerScriptEngine(QQmlEngine *parent) +: QThread(parent), d(new QQuickWorkerScriptEnginePrivate(parent)) +{ + d->m_lock.lock(); + connect(d, SIGNAL(stopThread()), this, SLOT(quit()), Qt::DirectConnection); + start(QThread::LowestPriority); + d->m_wait.wait(&d->m_lock); + d->moveToThread(this); + d->m_lock.unlock(); +} + +QQuickWorkerScriptEngine::~QQuickWorkerScriptEngine() +{ + d->m_lock.lock(); + QCoreApplication::postEvent(d, new QEvent((QEvent::Type)QQuickWorkerScriptEnginePrivate::WorkerDestroyEvent)); + d->m_lock.unlock(); + + //We have to force to cleanup the main thread's event queue here + //to make sure the main GUI release all pending locks/wait conditions which + //some worker script/agent are waiting for (QQmlListModelWorkerAgent::sync() for example). + while (!isFinished()) { + // We can't simply wait here, because the worker thread will not terminate + // until the main thread processes the last data event it generates + QCoreApplication::processEvents(); + yieldCurrentThread(); + } + + d->deleteLater(); +} + +QQuickWorkerScriptEnginePrivate::WorkerScript::WorkerScript() +: id(-1), initialized(false), owner(0) +{ +} + +QQuickWorkerScriptEnginePrivate::WorkerScript::~WorkerScript() +{ + qPersistentDispose(object); +} + +int QQuickWorkerScriptEngine::registerWorkerScript(QQuickWorkerScript *owner) +{ + typedef QQuickWorkerScriptEnginePrivate::WorkerScript WorkerScript; + WorkerScript *script = new WorkerScript; + + script->id = d->m_nextId++; + script->owner = owner; + + d->m_lock.lock(); + d->workers.insert(script->id, script); + d->m_lock.unlock(); + + return script->id; +} + +void QQuickWorkerScriptEngine::removeWorkerScript(int id) +{ + QQuickWorkerScriptEnginePrivate::WorkerScript* script = d->workers.value(id); + if (script) { + script->owner = 0; + QCoreApplication::postEvent(d, new WorkerRemoveEvent(id)); + } +} + +void QQuickWorkerScriptEngine::executeUrl(int id, const QUrl &url) +{ + QCoreApplication::postEvent(d, new WorkerLoadEvent(id, url)); +} + +void QQuickWorkerScriptEngine::sendMessage(int id, const QByteArray &data) +{ + QCoreApplication::postEvent(d, new WorkerDataEvent(id, data)); +} + +void QQuickWorkerScriptEngine::run() +{ + d->m_lock.lock(); + + d->workerEngine = new QQuickWorkerScriptEnginePrivate::WorkerEngine(d); + d->workerEngine->init(); + + d->m_wait.wakeAll(); + + d->m_lock.unlock(); + + exec(); + + qDeleteAll(d->workers); + d->workers.clear(); + + delete d->workerEngine; d->workerEngine = 0; +} + + +/*! + \qmltype WorkerScript + \instantiates QQuickWorkerScript + \ingroup qtquick-threading + \inqmlmodule QtQuick 2 + \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 + that the main GUI thread is not blocked. + + Messages can be passed between the new thread and the parent thread + using \l sendMessage() and the \l {WorkerScript::onMessage}{onMessage()} handler. + + An example: + + \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: + + \quotefile qml/workerscript/script.js + + 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 + by the \tt onMessage() handler of \tt myWorker. + + + \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 + 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. + + 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. + + \sa {declarative/threading/workerscript}{WorkerScript example}, + {declarative/threading/threadedlistmodel}{Threaded ListModel example} +*/ +QQuickWorkerScript::QQuickWorkerScript(QObject *parent) +: QObject(parent), m_engine(0), m_scriptId(-1), m_componentComplete(true) +{ +} + +QQuickWorkerScript::~QQuickWorkerScript() +{ + if (m_scriptId != -1) m_engine->removeWorkerScript(m_scriptId); +} + +/*! + \qmlproperty url WorkerScript::source + + This holds the url of the JavaScript file that implements the + \tt WorkerScript.onMessage() handler for threaded operations. +*/ +QUrl QQuickWorkerScript::source() const +{ + return m_source; +} + +void QQuickWorkerScript::setSource(const QUrl &source) +{ + if (m_source == source) + return; + + m_source = source; + + if (engine()) + m_engine->executeUrl(m_scriptId, m_source); + + emit sourceChanged(); +} + +/*! + \qmlmethod WorkerScript::sendMessage(jsobject message) + + Sends the given \a message to a worker script handler in another + thread. The other worker script handler can receive this message + through the onMessage() handler. + + The \c message object may only contain values of the following + types: + + \list + \li boolean, number, string + \li JavaScript objects and arrays + \li ListModel objects (any other type of QObject* is not allowed) + \endlist + + All objects and arrays are copied to the \c message. With the exception + of ListModel objects, any modifications by the other thread to an object + passed in \c message will not be reflected in the original object. +*/ +void QQuickWorkerScript::sendMessage(QQmlV8Function *args) +{ + if (!engine()) { + qWarning("QQuickWorkerScript: Attempt to send message before WorkerScript establishment"); + return; + } + + v8::Handle<v8::Value> argument = v8::Undefined(); + if (args->Length() != 0) + argument = (*args)[0]; + + m_engine->sendMessage(m_scriptId, QV8Worker::serialize(argument, args->engine())); +} + +void QQuickWorkerScript::classBegin() +{ + m_componentComplete = false; +} + +QQuickWorkerScriptEngine *QQuickWorkerScript::engine() +{ + if (m_engine) return m_engine; + if (m_componentComplete) { + QQmlEngine *engine = qmlEngine(this); + if (!engine) { + qWarning("QQuickWorkerScript: engine() called without qmlEngine() set"); + return 0; + } + + m_engine = QQmlEnginePrivate::get(engine)->getWorkerScriptEngine(); + m_scriptId = m_engine->registerWorkerScript(this); + + if (m_source.isValid()) + m_engine->executeUrl(m_scriptId, m_source); + + return m_engine; + } + return 0; +} + +void QQuickWorkerScript::componentComplete() +{ + m_componentComplete = true; + engine(); // Get it started now. +} + +/*! + \qmlsignal WorkerScript::onMessage(jsobject msg) + + This handler is called when a message \a msg is received from a worker + script in another thread through a call to sendMessage(). +*/ + +bool QQuickWorkerScript::event(QEvent *event) +{ + if (event->type() == (QEvent::Type)WorkerDataEvent::WorkerData) { + QQmlEngine *engine = qmlEngine(this); + if (engine) { + WorkerDataEvent *workerEvent = static_cast<WorkerDataEvent *>(event); + QV8Engine *v8engine = QQmlEnginePrivate::get(engine)->v8engine(); + v8::HandleScope handle_scope; + v8::Context::Scope scope(v8engine->context()); + v8::Handle<v8::Value> value = QV8Worker::deserialize(workerEvent->data(), v8engine); + emit message(QQmlV8Handle::fromHandle(value)); + } + return true; + } else if (event->type() == (QEvent::Type)WorkerErrorEvent::WorkerError) { + WorkerErrorEvent *workerEvent = static_cast<WorkerErrorEvent *>(event); + QQmlEnginePrivate::warning(qmlEngine(this), workerEvent->error()); + return true; + } else { + return QObject::event(event); + } +} + +QT_END_NAMESPACE + +#include <qquickworkerscript.moc> + diff --git a/src/qml/types/qquickworkerscript_p.h b/src/qml/types/qquickworkerscript_p.h new file mode 100644 index 0000000000..1ab5208e45 --- /dev/null +++ b/src/qml/types/qquickworkerscript_p.h @@ -0,0 +1,126 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** 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 Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/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 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKWORKERSCRIPT_P_H +#define QQUICKWORKERSCRIPT_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 "qqml.h" + +#include <QtQml/qqmlparserstatus.h> +#include <QtCore/qthread.h> +#include <QtQml/qjsvalue.h> +#include <QtCore/qurl.h> + +QT_BEGIN_NAMESPACE + + +class QQuickWorkerScript; +class QQuickWorkerScriptEnginePrivate; +class QQuickWorkerScriptEngine : public QThread +{ +Q_OBJECT +public: + QQuickWorkerScriptEngine(QQmlEngine *parent = 0); + virtual ~QQuickWorkerScriptEngine(); + + int registerWorkerScript(QQuickWorkerScript *); + void removeWorkerScript(int); + void executeUrl(int, const QUrl &); + void sendMessage(int, const QByteArray &); + +protected: + virtual void run(); + +private: + QQuickWorkerScriptEnginePrivate *d; +}; + +class QQmlV8Function; +class QQmlV8Handle; +class Q_AUTOTEST_EXPORT QQuickWorkerScript : public QObject, public QQmlParserStatus +{ + Q_OBJECT + Q_PROPERTY(QUrl source READ source WRITE setSource NOTIFY sourceChanged) + + Q_INTERFACES(QQmlParserStatus) +public: + QQuickWorkerScript(QObject *parent = 0); + virtual ~QQuickWorkerScript(); + + QUrl source() const; + void setSource(const QUrl &); + +public slots: + void sendMessage(QQmlV8Function*); + +signals: + void sourceChanged(); + void message(const QQmlV8Handle &messageObject); + +protected: + virtual void classBegin(); + virtual void componentComplete(); + virtual bool event(QEvent *); + +private: + QQuickWorkerScriptEngine *engine(); + QQuickWorkerScriptEngine *m_engine; + int m_scriptId; + QUrl m_source; + bool m_componentComplete; +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickWorkerScript) + +#endif // QQUICKWORKERSCRIPT_P_H diff --git a/src/qml/types/types.pri b/src/qml/types/types.pri new file mode 100644 index 0000000000..22e62ea8de --- /dev/null +++ b/src/qml/types/types.pri @@ -0,0 +1,26 @@ +SOURCES += \ + $$PWD/qqmlbind.cpp \ + $$PWD/qqmlconnections.cpp \ + $$PWD/qqmldelegatemodel.cpp \ + $$PWD/qqmllistmodel.cpp \ + $$PWD/qqmllistmodelworkeragent.cpp \ + $$PWD/qqmlmodelsmodule.cpp \ + $$PWD/qqmlobjectmodel.cpp \ + $$PWD/qqmltimer.cpp \ + $$PWD/qquickpackage.cpp \ + $$PWD/qquickworkerscript.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/qqmlobjectmodel_p.h \ + $$PWD/qqmltimer_p.h \ + $$PWD/qquickpackage_p.h \ + $$PWD/qquickworkerscript_p.h + |