Add Q_GADGET wrappers for QModelIndex & Co.
authorGabriel de Dietrich <gabriel.dedietrich@theqtcompany.com>
Thu, 15 Jan 2015 18:46:56 +0000 (19:46 +0100)
committerCaroline Chao <caroline.chao@theqtcompany.com>
Thu, 12 Feb 2015 11:10:04 +0000 (11:10 +0000)
The complete list of types is,
  * QModelIndex
  * QModelIndexList
  * QPersistentModelIndex
  * QItemSelection
  * QItemSelectionRange

These wrapper types follow the QQmlValueType conventions and
allow us to expose the wrapped types without introducing
meta-type changes. They also allow to customize the string
type representation.

We also extend QQmlValueTypeFactory to return the meta-object
for those types.

Finally, we add two-way meta-type conversion between QModelIndex
and QPersistentModelIndex to get the same interoperability as
in C++ when passing an object of one type to a function requir-
ing an object of the other type.

Change-Id: Iaa7089ea576c901f12715ffa21e4d94603d53755
Reviewed-by: Caroline Chao <caroline.chao@theqtcompany.com>
16 files changed:
src/qml/qml/qqmlvaluetype.cpp
src/qml/types/qqmlitemmodels.qdoc [new file with mode: 0644]
src/qml/types/qqmlmodelindexvaluetype.cpp [new file with mode: 0644]
src/qml/types/qqmlmodelindexvaluetype_p.h [new file with mode: 0644]
src/qml/types/types.pri
tests/auto/qml/qml.pro
tests/auto/qml/qqmlitemmodels/data/itemselection.qml [new file with mode: 0644]
tests/auto/qml/qqmlitemmodels/data/itemselectionrange.qml [new file with mode: 0644]
tests/auto/qml/qqmlitemmodels/data/modelindex.qml [new file with mode: 0644]
tests/auto/qml/qqmlitemmodels/data/modelindexconversion.qml [new file with mode: 0644]
tests/auto/qml/qqmlitemmodels/data/modelindexlist.qml [new file with mode: 0644]
tests/auto/qml/qqmlitemmodels/data/persistentmodelindex.qml [new file with mode: 0644]
tests/auto/qml/qqmlitemmodels/qqmlitemmodels.pro [new file with mode: 0644]
tests/auto/qml/qqmlitemmodels/qtestmodel.h [new file with mode: 0644]
tests/auto/qml/qqmlitemmodels/testtypes.h [new file with mode: 0644]
tests/auto/qml/qqmlitemmodels/tst_qqmlitemmodels.cpp [new file with mode: 0644]

index be14eb44c2bd3fa51409114b462a2a594da76405..1528ebda373a9ef22b2d3127688541ed4c71a68d 100644 (file)
@@ -37,6 +37,7 @@
 #include <private/qqmlglobal_p.h>
 #include <QtCore/qdebug.h>
 #include <private/qmetaobjectbuilder_p.h>
+#include <private/qqmlmodelindexvaluetype_p.h>
 
 QT_BEGIN_NAMESPACE
 
@@ -61,6 +62,14 @@ QQmlValueTypeFactoryImpl::QQmlValueTypeFactoryImpl()
 {
     for (unsigned int ii = 0; ii < QVariant::UserType; ++ii)
         valueTypes[ii] = 0;
+
+    // See types wrapped in qqmlmodelindexvaluetype_p.h
+    qRegisterMetaType<QModelIndexList>();
+    qRegisterMetaType<QPersistentModelIndex>();
+    qRegisterMetaType<QItemSelectionRange>();
+    qRegisterMetaType<QItemSelection>();
+    QMetaType::registerConverter<QModelIndex, QPersistentModelIndex>(&QQmlModelIndexValueType::toPersistentModelIndex);
+    QMetaType::registerConverter<QPersistentModelIndex, QModelIndex>(&QQmlPersistentModelIndexValueType::toModelIndex);
 }
 
 QQmlValueTypeFactoryImpl::~QQmlValueTypeFactoryImpl()
@@ -101,12 +110,23 @@ const QMetaObject *QQmlValueTypeFactoryImpl::metaObjectForMetaType(int t)
         return &QQmlRectFValueType::staticMetaObject;
     case QVariant::EasingCurve:
         return &QQmlEasingValueType::staticMetaObject;
+    case QVariant::ModelIndex:
+        return &QQmlModelIndexValueType::staticMetaObject;
     default:
         if (const QMetaObject *mo = QQml_valueTypeProvider()->metaObjectForMetaType(t))
             return mo;
         break;
     }
 
+    if (t == qMetaTypeId<QPersistentModelIndex>())
+        return &QQmlPersistentModelIndexValueType::staticMetaObject;
+    else if (t == qMetaTypeId<QModelIndexList>())
+        return &QQmlModelIndexListValueType::staticMetaObject;
+    else if (t == qMetaTypeId<QItemSelectionRange>())
+        return &QQmlItemSelectionRangeValueType::staticMetaObject;
+    else if (t == qMetaTypeId<QItemSelection>())
+        return &QQmlItemSelectionValueType::staticMetaObject;
+
     QMetaType metaType(t);
     if (metaType.flags() & QMetaType::IsGadget)
         return metaType.metaObject();
diff --git a/src/qml/types/qqmlitemmodels.qdoc b/src/qml/types/qqmlitemmodels.qdoc
new file mode 100644 (file)
index 0000000..25c9321
--- /dev/null
@@ -0,0 +1,115 @@
+/****************************************************************************
+**
+** Copyright (C) 2015 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the documentation of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:FDL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Free Documentation License Usage
+** Alternatively, this file may be used under the terms of the GNU Free
+** Documentation License version 1.3 as published by the Free Software
+** Foundation and appearing in the file included in the packaging of
+** this file. Please review the following information to ensure
+** the GNU Free Documentation License version 1.3 requirements
+** will be met: http://www.gnu.org/copyleft/fdl.html.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+/*!
+    \chapter QModelIndex & Co. in QML
+
+    Since Qt 5.5, QModelIndex and QPersistentModelIndex are exposed in QML as
+    value-based types. Also exposed in a similar fashion are QModelIndexList,
+    QItemSelectionRange and QItemSelection. All objects from these types can
+    be passed back and forth between QML and C++ as \c var properties or plain
+    JavaScript variables.
+
+    We detail here which API all these classes get exposed in QML. Please refer
+    to the C++ documentation for more information.
+
+    \note Since all these types are exposed as gadgets, there are no property
+    change notification signals emitted. Therefore binding to their properties
+    may not give the expected results. This is especially true for QPersistentModelIndex.
+    It is perfectly possible to bind to properties holding any of those types.
+
+    \section1 \l QModelIndex and \l QPersistentModelIndex
+
+    \list
+    \li \b row : int
+    \li \b column : int
+    \li \b parent : QModelIndex
+    \li \b valid : bool
+    \li \b model : QAbstractItemModel
+    \li \b internalId : quint64
+    \endlist
+
+    All these properties are read-only, as their C++ counterpart.
+
+    \note The usual caveats apply to QModelIndex in QML. If the underlying model changes
+    or gets deleted, it may become dangerous to access its properties. Therefore, you
+    should not store any QModelIndex. You can, however, store QPersistentModelIndexes
+    in a safe way.
+
+    \section1 \l QItemSelectionRange
+
+    \list
+    \li \b top : int
+    \li \b left : int
+    \li \b bottom : int
+    \li \b right : int
+    \li \b width : int
+    \li \b height : int
+    \li \b topLeft : QPersistentModelIndex
+    \li \b bottomRight : QPersistentModelIndex
+    \li \b parent : QModelIndex
+    \li \b valid : bool
+    \li \b empty : bool
+    \li \b model : QAbstractItemModel
+    \endlist
+
+    All these properties are read-only, as their C++ counterpart. In addition,
+    we also expose the following functions:
+
+    \list
+    \li bool \b{contains}(QModelIndex index)
+    \li bool \b{contains}(int row, int column, QModelIndex parentIndex)
+    \li bool \b{intersects}(QItemSelectionRange other)
+    \li QItemSelectionRange \b{intersected}(QItemSelectionRange other)
+    \endlist
+
+    \section1 \l QModelIndexList and \l QItemSelection
+
+    Both \l QModelIndexList and \l QItemSelection expose the following properties
+    and functions as part of their \l QList API:
+
+    \list
+    \li \b length : int
+    \li object \b{at}(int i)
+    \li void \b{append}(object o)
+    \li void \b{prepend}(o)
+    \li void \b{insert}(int i, object o)
+    \li void \b{removeFirst}()
+    \li void \b{removeLast}()
+    \li void \b{removeAt}(int i)
+    \endlist
+
+    In addition, \l QItemSelection also exposes the following functions:
+
+    \list
+    \li void \b{select}(QModelIndex topLeft, QModelIndex bottomRight)
+    \li bool \b{contains}(QModelIndex index)
+    \li void \b{merge}(QItemSelection other, QItemSelectionModel::SelectionFlags command)
+    \endlist
+
+    \sa ItemSelectionModel
+*/
diff --git a/src/qml/types/qqmlmodelindexvaluetype.cpp b/src/qml/types/qqmlmodelindexvaluetype.cpp
new file mode 100644 (file)
index 0000000..03ae05b
--- /dev/null
@@ -0,0 +1,60 @@
+/****************************************************************************
+**
+** Copyright (C) 2015 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the QtQml module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL21$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 or version 3 as published by the Free
+** Software Foundation and appearing in the file LICENSE.LGPLv21 and
+** LICENSE.LGPLv3 included in the packaging of this file. Please review the
+** following information to ensure the GNU Lesser General Public License
+** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** As a special exception, The Qt Company gives you certain additional
+** rights. These rights are described in The Qt Company LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qqmlmodelindexvaluetype_p.h"
+
+QT_BEGIN_NAMESPACE
+
+/*!
+    \internal
+*/
+QString QQmlModelIndexValueType::propertiesString(const QModelIndex &idx)
+{
+    if (!idx.isValid())
+        return QLatin1String("()");
+    return QString(QLatin1String("(%1,%2,0x%3,%4(0x%5))"))
+            .arg(idx.row()).arg(idx.column()).arg(idx.internalId(), 0, 16)
+            .arg(idx.model()->metaObject()->className()).arg(quintptr(idx.model()), 0, 16);
+}
+
+/*!
+    \internal
+*/
+QString QQmlItemSelectionRangeValueType::toString() const
+{
+    return QString(QLatin1String("QItemSelectionRange(%1,%2)"))
+            .arg(reinterpret_cast<const QQmlPersistentModelIndexValueType *>(&v.topLeft())->toString())
+            .arg(reinterpret_cast<const QQmlPersistentModelIndexValueType *>(&v.bottomRight())->toString());
+}
+
+QT_END_NAMESPACE
diff --git a/src/qml/types/qqmlmodelindexvaluetype_p.h b/src/qml/types/qqmlmodelindexvaluetype_p.h
new file mode 100644 (file)
index 0000000..0e655ab
--- /dev/null
@@ -0,0 +1,215 @@
+/****************************************************************************
+**
+** Copyright (C) 2015 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the QtQml module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL21$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 or version 3 as published by the Free
+** Software Foundation and appearing in the file LICENSE.LGPLv21 and
+** LICENSE.LGPLv3 included in the packaging of this file. Please review the
+** following information to ensure the GNU Lesser General Public License
+** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** As a special exception, The Qt Company gives you certain additional
+** rights. These rights are described in The Qt Company LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QQMLMODELINDEXVALUETYPE_P_H
+#define QQMLMODELINDEXVALUETYPE_P_H
+
+//
+//  W A R N I N G
+//  -------------
+//
+// This file is not part of the Qt API.  It exists purely as an
+// implementation detail.  This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtCore/qabstractitemmodel.h>
+#include <QtCore/qitemselectionmodel.h>
+
+QT_BEGIN_NAMESPACE
+
+struct QQmlModelIndexValueType
+{
+    QModelIndex v;
+
+    Q_PROPERTY(int row READ row CONSTANT FINAL)
+    Q_PROPERTY(int column READ column CONSTANT FINAL)
+    Q_PROPERTY(QModelIndex parent READ parent FINAL)
+    Q_PROPERTY(bool valid READ isValid CONSTANT FINAL)
+    Q_PROPERTY(QAbstractItemModel *model READ model CONSTANT FINAL)
+    Q_PROPERTY(quint64 internalId READ internalId CONSTANT FINAL)
+    Q_GADGET
+
+public:
+    Q_INVOKABLE QString toString() const
+    { return QLatin1String("QModelIndex") + propertiesString(v); }
+
+    inline int row() const Q_DECL_NOTHROW { return v.row(); }
+    inline int column() const Q_DECL_NOTHROW { return v.column(); }
+    inline QModelIndex parent() const { return v.parent(); }
+    inline bool isValid() const Q_DECL_NOTHROW { return v.isValid(); }
+    inline QAbstractItemModel *model() const Q_DECL_NOTHROW
+    { return const_cast<QAbstractItemModel *>(v.model()); }
+    quint64 internalId() const { return v.internalId(); }
+
+    static QString propertiesString(const QModelIndex &idx);
+
+    static QPersistentModelIndex toPersistentModelIndex(const QModelIndex &index)
+    { return QPersistentModelIndex(index); }
+};
+
+struct QQmlPersistentModelIndexValueType
+{
+    QPersistentModelIndex v;
+
+    Q_PROPERTY(int row READ row FINAL)
+    Q_PROPERTY(int column READ column FINAL)
+    Q_PROPERTY(QModelIndex parent READ parent FINAL)
+    Q_PROPERTY(bool valid READ isValid FINAL)
+    Q_PROPERTY(QAbstractItemModel *model READ model FINAL)
+    Q_PROPERTY(quint64 internalId READ internalId FINAL)
+    Q_GADGET
+
+public:
+    Q_INVOKABLE QString toString() const
+    { return QLatin1String("QPersistentModelIndex") + QQmlModelIndexValueType::propertiesString(v); }
+
+    inline int row() const { return v.row(); }
+    inline int column() const { return v.column(); }
+    inline QModelIndex parent() const { return v.parent(); }
+    inline bool isValid() const { return v.isValid(); }
+    inline QAbstractItemModel *model() const { return const_cast<QAbstractItemModel *>(v.model()); }
+    inline quint64 internalId() const { return v.internalId(); }
+
+    static const QModelIndex &toModelIndex(const QPersistentModelIndex &index)
+    { return index; }
+};
+
+struct QQmlItemSelectionRangeValueType
+{
+    QItemSelectionRange v;
+
+    Q_PROPERTY(int top READ top FINAL)
+    Q_PROPERTY(int left READ left FINAL)
+    Q_PROPERTY(int bottom READ bottom FINAL)
+    Q_PROPERTY(int right READ right FINAL)
+    Q_PROPERTY(int width READ width FINAL)
+    Q_PROPERTY(int height READ height FINAL)
+    Q_PROPERTY(QPersistentModelIndex topLeft READ topLeft FINAL)
+    Q_PROPERTY(QPersistentModelIndex bottomRight READ bottomRight FINAL)
+    Q_PROPERTY(QModelIndex parent READ parent FINAL)
+    Q_PROPERTY(bool valid READ isValid FINAL)
+    Q_PROPERTY(bool empty READ isEmpty FINAL)
+    Q_PROPERTY(QAbstractItemModel *model READ model FINAL)
+    Q_GADGET
+
+public:
+    Q_INVOKABLE QString toString() const;
+    Q_INVOKABLE inline bool contains(const QModelIndex &index) const
+    { return v.contains(index); }
+    Q_INVOKABLE inline bool contains(int row, int column, const QModelIndex &parentIndex) const
+    { return v.contains(row, column, parentIndex); }
+    Q_INVOKABLE inline bool intersects(const QItemSelectionRange &other) const
+    { return v.intersects(other); }
+    Q_INVOKABLE QItemSelectionRange intersected(const QItemSelectionRange &other) const
+    { return v.intersected(other); }
+
+    inline int top() const { return v.top(); }
+    inline int left() const { return v.left(); }
+    inline int bottom() const { return v.bottom(); }
+    inline int right() const { return v.right(); }
+    inline int width() const { return v.width(); }
+    inline int height() const { return v.height(); }
+    inline QPersistentModelIndex &topLeft() const { return const_cast<QPersistentModelIndex &>(v.topLeft()); }
+    inline QPersistentModelIndex &bottomRight() const { return const_cast<QPersistentModelIndex &>(v.bottomRight()); }
+    inline QModelIndex parent() const { return v.parent(); }
+    inline QAbstractItemModel *model() const { return const_cast<QAbstractItemModel *>(v.model()); }
+    inline bool isValid() const { return v.isValid(); }
+    inline bool isEmpty() const { return v.isEmpty(); }
+};
+
+template<typename V, typename T>
+QString q_listToString(const QList<T> &list, const QLatin1String &typeName)
+{
+    QString result = typeName;
+    result.append(QLatin1Char('('));
+    for (typename QList<T>::size_type i = 0; i < list.count(); ++i) {
+        if (i)
+            result.append(QLatin1String(", "));
+        result.append(reinterpret_cast<const V *>(&list.at(i))->toString());
+    }
+    return result.append(QLatin1Char(')'));
+}
+
+// Invokable QList<T> API forwarding for value types
+#define QLISTVALUETYPE_QML_API(T) \
+    Q_PROPERTY(int length READ length FINAL) \
+    Q_INVOKABLE T at(int i) { return v.at(i); } \
+    Q_INVOKABLE void append(const T &o) { v.append(o); } \
+    Q_INVOKABLE void prepend(const T &o) { v.prepend(o); } \
+    Q_INVOKABLE void insert(int i, const T &o) { v.insert(i, o); } \
+    Q_INVOKABLE void removeFirst() { v.removeFirst(); } \
+    Q_INVOKABLE void removeLast() { v.removeLast(); } \
+    Q_INVOKABLE void removeAt(int i) { v.removeAt(i); } \
+    int length() const { return v.length(); }
+
+struct QQmlModelIndexListValueType
+{
+    QModelIndexList v;
+
+    Q_GADGET
+
+public:
+    Q_INVOKABLE QString toString()
+    { return q_listToString<QQmlModelIndexValueType>(v, QLatin1String("")); }
+
+    QLISTVALUETYPE_QML_API(QModelIndex)
+};
+
+struct QQmlItemSelectionValueType
+{
+    QItemSelection v;
+
+    Q_GADGET
+
+public:
+    Q_INVOKABLE QString toString()
+    { return q_listToString<QQmlItemSelectionRangeValueType>(v, QLatin1String("QItemSelection")); }
+    Q_INVOKABLE void select(const QModelIndex &topLeft, const QModelIndex &bottomRight)
+    { v.select(topLeft, bottomRight); }
+    Q_INVOKABLE bool contains(const QModelIndex &index) const
+    { return v.contains(index); }
+    Q_INVOKABLE void merge(const QItemSelection &other, int command)
+    { v.merge(other, QItemSelectionModel::SelectionFlags(command)); }
+
+    QLISTVALUETYPE_QML_API(QItemSelectionRange)
+};
+
+#undef QLISTVALUETYPE_INVOKABLE_API
+
+QT_END_NAMESPACE
+
+#endif // QQMLMODELINDEXVALUETYPE_P_H
+
index 3e6153759d1d5447d8f2e162d546e4c2bf50811c..d2e50207380a94d3ebb6e2a0e2201695b6773cd4 100644 (file)
@@ -5,6 +5,7 @@ SOURCES += \
     $$PWD/qqmllistmodel.cpp \
     $$PWD/qqmllistmodelworkeragent.cpp \
     $$PWD/qqmlmodelsmodule.cpp \
+    $$PWD/qqmlmodelindexvaluetype.cpp \
     $$PWD/qqmlobjectmodel.cpp \
     $$PWD/qqmltimer.cpp \
     $$PWD/qquickpackage.cpp \
@@ -20,6 +21,7 @@ HEADERS += \
     $$PWD/qqmllistmodel_p_p.h \
     $$PWD/qqmllistmodelworkeragent_p.h \
     $$PWD/qqmlmodelsmodule_p.h \
+    $$PWD/qqmlmodelindexvaluetype_p.h \
     $$PWD/qqmlobjectmodel_p.h \
     $$PWD/qqmltimer_p.h \
     $$PWD/qquickpackage_p.h \
index 3449050fe8cdd0237f6aa820754b47725dfe18c3..a5c3211f0db2896b70a36a35df84308fc780aecb 100644 (file)
@@ -48,6 +48,7 @@ PRIVATETESTS += \
     qqmllistcompositor \
     qqmllistmodel \
     qqmllistmodelworkerscript \
+    qqmlitemmodels \
     qqmltypeloader \
     qqmlparser \
     qquickworkerscript \
diff --git a/tests/auto/qml/qqmlitemmodels/data/itemselection.qml b/tests/auto/qml/qqmlitemmodels/data/itemselection.qml
new file mode 100644 (file)
index 0000000..57cb643
--- /dev/null
@@ -0,0 +1,36 @@
+import Test 1.0
+
+ItemModelsTest {
+    property var itemSelection
+    property int count
+    property bool contains: false
+
+    function range(top, bottom, left, right, parent) {
+        if (parent === undefined)
+            parent = invalidModelIndex()
+        var topLeft = model.index(top, left, parent)
+        var bottomRight = model.index(bottom, right, parent)
+        return createItemSelectionRange(topLeft, bottomRight)
+    }
+
+    onModelChanged: {
+        itemSelection = createItemSelection()
+        itemSelection.prepend(range(0, 0, 0, 5))
+        itemSelection.append(range(0, 5, 0, 0))
+        for (var i = 0; i < 3; i++)
+            itemSelection.insert(i, range(i, i + 1, i + 2, i + 3))
+
+        var itemSelection2 = createItemSelection()
+        for (i = 3; i < 6; i++)
+            itemSelection2.select(model.index(i, i + 1), model.index(i + 2, i + 3))
+
+        itemSelection.merge(itemSelection2, 2 /*ItemSelectionModel.Select*/)
+
+        count = itemSelection.length
+        contains = itemSelection.contains(model.index(0, 0))
+
+        itemSelection.removeAt(3)
+        itemSelection.removeFirst()
+        itemSelection.removeLast()
+    }
+}
diff --git a/tests/auto/qml/qqmlitemmodels/data/itemselectionrange.qml b/tests/auto/qml/qqmlitemmodels/data/itemselectionrange.qml
new file mode 100644 (file)
index 0000000..72f732a
--- /dev/null
@@ -0,0 +1,32 @@
+import Test 1.0
+
+ItemModelsTest {
+    property var itemSelectionRange: createItemSelectionRange(invalidModelIndex(), invalidModelIndex())
+    property int top: itemSelectionRange.top
+    property int left: itemSelectionRange.left
+    property int bottom: itemSelectionRange.bottom
+    property int right: itemSelectionRange.right
+    property int width: itemSelectionRange.width
+    property int height: itemSelectionRange.height
+    property bool isValid: itemSelectionRange.valid
+    property bool isEmpty: itemSelectionRange.empty
+    property var isrModel: itemSelectionRange.model
+    property bool contains1: false
+    property bool contains2: false
+    property bool intersects: false
+    property var intersected
+
+    onModelChanged: {
+        if (model) {
+            var parentIndex = model.index(0, 0)
+            var index1 = model.index(3, 0, parentIndex)
+            var index2 = model.index(5, 6, parentIndex)
+            itemSelectionRange = createItemSelectionRange(index1, index2)
+
+            contains1 = itemSelectionRange.contains(index1)
+            contains2 = itemSelectionRange.contains(4, 3, parentIndex)
+            intersects = itemSelectionRange.intersects(createItemSelectionRange(parentIndex, parentIndex))
+            intersected = itemSelectionRange.intersected(createItemSelectionRange(parentIndex, parentIndex))
+        }
+    }
+}
diff --git a/tests/auto/qml/qqmlitemmodels/data/modelindex.qml b/tests/auto/qml/qqmlitemmodels/data/modelindex.qml
new file mode 100644 (file)
index 0000000..0d6e362
--- /dev/null
@@ -0,0 +1,19 @@
+import Test 1.0
+
+ItemModelsTest {
+    property bool isValid: modelIndex.valid
+    property int row: modelIndex.row
+    property int column: modelIndex.column
+    property var parent: modelIndex.parent
+    property var model: modelIndex.model
+    property var internalId: modelIndex.internalId
+
+    onSignalWithModelIndex: {
+        isValid = index.valid
+        row = index.row
+        column = index.column
+        parent = index.parent
+        model = index.model
+        internalId = index.internalId
+    }
+}
diff --git a/tests/auto/qml/qqmlitemmodels/data/modelindexconversion.qml b/tests/auto/qml/qqmlitemmodels/data/modelindexconversion.qml
new file mode 100644 (file)
index 0000000..91ee05e
--- /dev/null
@@ -0,0 +1,9 @@
+import Test 1.0
+
+ItemModelsTest {
+
+    onModelChanged: {
+        modelIndex = createPersistentModelIndex(model.index(0, 0))
+        persistentModelIndex = model.index(1, 1)
+    }
+}
diff --git a/tests/auto/qml/qqmlitemmodels/data/modelindexlist.qml b/tests/auto/qml/qqmlitemmodels/data/modelindexlist.qml
new file mode 100644 (file)
index 0000000..4439339
--- /dev/null
@@ -0,0 +1,21 @@
+import Test 1.0
+
+ItemModelsTest {
+    property var modelIndexList
+    property int count
+
+    onModelChanged: {
+        modelIndexList = createModelIndexList()
+        modelIndexList.prepend(model.index(0, 0))
+        modelIndexList.append(model.index(1, 1))
+        for (var i = 0; i < 3; i++)
+            modelIndexList.insert(i, model.index(2 + i, 2 + i))
+
+        count = modelIndexList.length
+        modelIndex = modelIndexList.at(0)
+
+        modelIndexList.removeAt(3)
+        modelIndexList.removeFirst()
+        modelIndexList.removeLast()
+    }
+}
diff --git a/tests/auto/qml/qqmlitemmodels/data/persistentmodelindex.qml b/tests/auto/qml/qqmlitemmodels/data/persistentmodelindex.qml
new file mode 100644 (file)
index 0000000..1303706
--- /dev/null
@@ -0,0 +1,23 @@
+import Test 1.0
+
+ItemModelsTest {
+    property bool isValid: persistentModelIndex.valid
+    property int row: persistentModelIndex.row
+    property int column: persistentModelIndex.column
+    property var parent: persistentModelIndex.parent
+    property var model: persistentModelIndex.model
+    property var internalId: persistentModelIndex.internalId
+
+    property var pmi
+
+    onSignalWithPersistentModelIndex: {
+        isValid = index.valid
+        row = index.row
+        column = index.column
+        parent = index.parent
+        model = index.model
+        internalId = index.internalId
+
+        pmi = createPersistentModelIndex(model.index(0, 0))
+    }
+}
diff --git a/tests/auto/qml/qqmlitemmodels/qqmlitemmodels.pro b/tests/auto/qml/qqmlitemmodels/qqmlitemmodels.pro
new file mode 100644 (file)
index 0000000..f76c6d0
--- /dev/null
@@ -0,0 +1,23 @@
+CONFIG += testcase
+TARGET = tst_qqmlitemmodels
+macx:CONFIG -= app_bundle
+
+HEADERS = qtestmodel.h testtypes.h
+SOURCES += tst_qqmlitemmodels.cpp
+
+include (../../shared/util.pri)
+
+TESTDATA = data/*
+
+CONFIG += parallel_test
+
+QT += core qml testlib
+DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0
+
+DISTFILES += \
+    data/modelindex.qml \
+    data/persistentmodelindex.qml \
+    data/itemselectionrange.qml \
+    data/modelindexlist.qml \
+    data/itemselection.qml \
+    data/modelindexconversion.qml
diff --git a/tests/auto/qml/qqmlitemmodels/qtestmodel.h b/tests/auto/qml/qqmlitemmodels/qtestmodel.h
new file mode 100644 (file)
index 0000000..bb0a169
--- /dev/null
@@ -0,0 +1,322 @@
+/****************************************************************************
+**
+** Copyright (C) 2015 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL21$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 or version 3 as published by the Free
+** Software Foundation and appearing in the file LICENSE.LGPLv21 and
+** LICENSE.LGPLv3 included in the packaging of this file. Please review the
+** following information to ensure the GNU Lesser General Public License
+** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** As a special exception, The Qt Company gives you certain additional
+** rights. These rights are described in The Qt Company LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef Q_TEST_MODEL_H
+#define Q_TEST_MODEL_H
+
+#include <QtCore/qabstractitemmodel.h>
+
+class TestModel: public QAbstractItemModel
+{
+    Q_OBJECT
+
+public:
+    TestModel(QObject *parent = 0): QAbstractItemModel(parent),
+       fetched(false), rows(10), cols(1), levels(INT_MAX), wrongIndex(false) { init(); }
+
+    TestModel(int _rows, int _cols, QObject *parent = 0): QAbstractItemModel(parent),
+       fetched(false), rows(_rows), cols(_cols), levels(INT_MAX), wrongIndex(false) { init(); }
+
+    void init() {
+        decorationsEnabled = false;
+        alternateChildlessRows = true;
+        tree = new Node(rows);
+    }
+
+    inline qint32 level(const QModelIndex &index) const {
+        Node *n = (Node *)index.internalPointer();
+        if (!n)
+            return -1;
+        int l = -1;
+        while (n != tree) {
+            n = n->parent;
+            ++l;
+        }
+        return l;
+    }
+
+    void resetModel()
+    {
+        beginResetModel();
+        fetched = false;
+        delete tree;
+        tree = new Node(rows);
+        endResetModel();
+    }
+
+    QString displayData(const QModelIndex &idx) const
+    {
+        return QString("[%1,%2,%3,%4]").arg(idx.row()).arg(idx.column()).arg(idx.internalId()).arg(hasChildren(idx));
+    }
+
+    bool canFetchMore(const QModelIndex &) const {
+        return !fetched;
+    }
+
+    void fetchMore(const QModelIndex &) {
+        fetched = true;
+    }
+
+    bool hasChildren(const QModelIndex &parent = QModelIndex()) const {
+        bool hasFetched = fetched;
+        fetched = true;
+        bool r = QAbstractItemModel::hasChildren(parent);
+        fetched = hasFetched;
+        return r;
+    }
+
+    int rowCount(const QModelIndex& parent = QModelIndex()) const {
+        if (!fetched)
+            qFatal("%s: rowCount should not be called before fetching", Q_FUNC_INFO);
+        if ((parent.column() > 0) || (level(parent) > levels)
+            || (alternateChildlessRows && parent.row() > 0 && (parent.row() & 1)))
+            return 0;
+        Node *n = (Node*)parent.internalPointer();
+        if (!n)
+            n = tree;
+        return n->children.count();
+    }
+
+    int columnCount(const QModelIndex& parent = QModelIndex()) const {
+        if ((parent.column() > 0) || (level(parent) > levels)
+            || (alternateChildlessRows && parent.row() > 0 && (parent.row() & 1)))
+            return 0;
+        return cols;
+    }
+
+    bool isEditable(const QModelIndex &index) const {
+        if (index.isValid())
+            return true;
+        return false;
+    }
+
+    Q_INVOKABLE QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const
+    {
+        if (row < 0 || column < 0 || (level(parent) > levels) || column >= cols)
+            return QModelIndex();
+        Node *pn = (Node*)parent.internalPointer();
+        if (!pn)
+            pn = tree;
+        if (row >= pn->children.count())
+            return QModelIndex();
+
+        Node *n = pn->children.at(row);
+        if (!n) {
+            n = new Node(rows, pn);
+            pn->children[row] = n;
+        }
+        return createIndex(row, column, n);
+    }
+
+    QModelIndex parent(const QModelIndex &index) const
+    {
+        Node *n = (Node *)index.internalPointer();
+        if (!n || n->parent == tree)
+            return QModelIndex();
+        Q_ASSERT(n->parent->parent);
+        int parentRow = n->parent->parent->children.indexOf(n->parent);
+        Q_ASSERT(parentRow != -1);
+        return createIndex(parentRow, 0, n->parent);
+    }
+
+    QVariant data(const QModelIndex &idx, int role) const
+    {
+        if (!idx.isValid())
+            return QVariant();
+
+        Node *pn = (Node *)idx.internalPointer();
+        if (!pn)
+            pn = tree;
+        if (pn != tree)
+            pn = pn->parent;
+        if (idx.row() < 0 || idx.column() < 0 || idx.column() >= cols
+            || idx.row() >= pn->children.count()) {
+            wrongIndex = true;
+            qWarning("Invalid modelIndex [%d,%d,%p]", idx.row(), idx.column(),
+                     idx.internalPointer());
+            return QVariant();
+        }
+
+        if (role == Qt::DisplayRole) {
+            return displayData(idx);
+        }
+
+        return QVariant();
+    }
+
+    bool setData(const QModelIndex &index, const QVariant &value, int role)
+    {
+        Q_UNUSED(value);
+        QVector<int> changedRole(1, role);
+        emit dataChanged(index, index, changedRole);
+        return true;
+    }
+
+    void groupedSetData(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles)
+    {
+        emit dataChanged(topLeft, bottomRight, roles);
+    }
+
+    void changeLayout(const QList<QPersistentModelIndex> &parents = QList<QPersistentModelIndex>())
+    {
+        emit layoutAboutToBeChanged(parents);
+        emit layoutChanged(parents);
+    }
+
+    bool removeRows(int row, int count, const QModelIndex &parent)
+    {
+        beginRemoveRows(parent, row, row + count - 1);
+        Node *n = (Node *)parent.internalPointer();
+        if (!n)
+            n = tree;
+        n->removeRows(row, count);
+        endRemoveRows();
+        return true;
+    }
+
+    void removeLastColumn()
+    {
+        beginRemoveColumns(QModelIndex(), cols - 1, cols - 1);
+        --cols;
+        endRemoveColumns();
+    }
+
+    void removeAllColumns()
+    {
+        beginRemoveColumns(QModelIndex(), 0, cols - 1);
+        cols = 0;
+        endRemoveColumns();
+    }
+
+    bool insertRows(int row, int count, const QModelIndex &parent)
+    {
+        beginInsertRows(parent, row, row + count - 1);
+        Node *n = (Node *)parent.internalPointer();
+        if (!n)
+            n = tree;
+        n->addRows(row, count);
+        endInsertRows();
+        return true;
+    }
+
+    bool moveRows(const QModelIndex &sourceParent, int sourceRow, int count, const QModelIndex &destinationParent, int destinationChild)
+    {
+        Q_ASSERT_X(sourceRow >= 0 && sourceRow < rowCount(sourceParent)
+                   && count > 0 && sourceRow + count < rowCount(sourceParent)
+                   && destinationChild >= 0 && destinationChild <= rowCount(destinationParent),
+                   Q_FUNC_INFO, "Rows out of range.");
+        Q_ASSERT_X(!(sourceParent == destinationParent && destinationChild >= sourceRow && destinationChild < sourceRow + count),
+                   Q_FUNC_INFO, "Moving rows onto themselves.");
+        if (!beginMoveRows(sourceParent, sourceRow, sourceRow + count - 1, destinationParent, destinationChild))
+            return false;
+        Node *src = (Node *)sourceParent.internalPointer();
+        if (!src)
+            src = tree;
+        Node *dest = (Node *)destinationParent.internalPointer();
+        if (!dest)
+            dest = tree;
+        QVector<Node *> buffer = src->children.mid(sourceRow, count);
+        if (src != dest) {
+            src->removeRows(sourceRow, count, true /* keep alive */);
+            dest->addRows(destinationChild, count);
+        } else {
+            QVector<Node *> &c = dest->children;
+            if (sourceRow < destinationChild) {
+                memmove(&c[sourceRow], &c[sourceRow + count], sizeof(Node *) * (destinationChild - sourceRow - count));
+                destinationChild -= count;
+            } else {
+                memmove(&c[destinationChild + count], &c[destinationChild], sizeof(Node *) * (sourceRow - destinationChild));
+            }
+        }
+        for (int i = 0; i < count; i++) {
+            Node *n = buffer[i];
+            n->parent = dest;
+            dest->children[i + destinationChild] = n;
+        }
+
+        endMoveRows();
+        return true;
+    }
+
+    void setDecorationsEnabled(bool enable)
+    {
+        decorationsEnabled = enable;
+    }
+
+    mutable bool fetched;
+    bool decorationsEnabled;
+    bool alternateChildlessRows;
+    int rows, cols;
+    int levels;
+    mutable bool wrongIndex;
+
+    struct Node {
+        Node *parent;
+        QVector<Node *> children;
+
+        Node(int rows, Node *p = 0) : parent(p)
+        {
+            addRows(0, rows);
+        }
+
+        ~Node()
+        {
+            foreach (Node *n, children)
+               delete n;
+        }
+
+        void addRows(int row, int count)
+        {
+            if (count > 0) {
+                children.reserve(children.count() + count);
+                children.insert(row, count, (Node *)0);
+            }
+        }
+
+        void removeRows(int row, int count, bool keepAlive = false)
+        {
+            int newCount = qMax(children.count() - count, 0);
+            int effectiveCountDiff = children.count() - newCount;
+            if (effectiveCountDiff > 0) {
+                if (!keepAlive)
+                    for (int i = 0; i < effectiveCountDiff; i++)
+                        delete children[i + row];
+                children.remove(row, effectiveCountDiff);
+            }
+        }
+    };
+
+    Node *tree;
+};
+
+#endif // Q_TEST_MODEL_H
diff --git a/tests/auto/qml/qqmlitemmodels/testtypes.h b/tests/auto/qml/qqmlitemmodels/testtypes.h
new file mode 100644 (file)
index 0000000..5345609
--- /dev/null
@@ -0,0 +1,148 @@
+/****************************************************************************
+**
+** Copyright (C) 2015 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL21$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 or version 3 as published by the Free
+** Software Foundation and appearing in the file LICENSE.LGPLv21 and
+** LICENSE.LGPLv3 included in the packaging of this file. Please review the
+** following information to ensure the GNU Lesser General Public License
+** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** As a special exception, The Qt Company gives you certain additional
+** rights. These rights are described in The Qt Company LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef TESTTYPES_H
+#define TESTTYPES_H
+
+#include <QtCore/qabstractitemmodel.h>
+#include <QtCore/qitemselectionmodel.h>
+#include "qdebug.h"
+
+class ItemModelsTest : public QObject
+{
+    Q_OBJECT
+
+    Q_PROPERTY(QAbstractItemModel *model READ model WRITE setModel NOTIFY modelChanged)
+    Q_PROPERTY(QModelIndex modelIndex READ modelIndex WRITE setModelIndex NOTIFY changed)
+    Q_PROPERTY(QPersistentModelIndex persistentModelIndex READ persistentModelIndex WRITE setPersistentModelIndex NOTIFY changed)
+
+public:
+    QModelIndex modelIndex() const
+    {
+        return m_modelIndex;
+    }
+
+    QPersistentModelIndex persistentModelIndex() const
+    {
+        return m_persistentModelIndex;
+    }
+
+    void emitChanged()
+    {
+        emit changed();
+    }
+
+    void emitSignalWithModelIndex(const QModelIndex &index)
+    {
+        emit signalWithModelIndex(index);
+    }
+
+    void emitSignalWithPersistentModelIndex(const QPersistentModelIndex &index)
+    {
+        emit signalWithPersistentModelIndex(index);
+    }
+
+    QAbstractItemModel * model() const
+    {
+        return m_model;
+    }
+
+    Q_INVOKABLE QModelIndex invalidModelIndex() const
+    {
+        return QModelIndex();
+    }
+
+    Q_INVOKABLE QModelIndexList createModelIndexList() const
+    {
+        return QModelIndexList();
+    }
+
+    Q_INVOKABLE QItemSelectionRange createItemSelectionRange(const QModelIndex &tl, const QModelIndex &br) const
+    {
+        return QItemSelectionRange(tl, br);
+    }
+
+    Q_INVOKABLE QItemSelection createItemSelection()
+    {
+        return QItemSelection();
+    }
+
+    Q_INVOKABLE QPersistentModelIndex createPersistentModelIndex(const QModelIndex &index)
+    {
+        return QPersistentModelIndex(index);
+    }
+
+public slots:
+    void setModelIndex(const QModelIndex &arg)
+    {
+        if (m_modelIndex == arg)
+            return;
+
+        m_modelIndex = arg;
+        emit changed();
+    }
+
+    void setPersistentModelIndex(const QPersistentModelIndex &arg)
+    {
+        if (m_persistentModelIndex == arg)
+            return;
+
+        m_persistentModelIndex = arg;
+        emit changed();
+    }
+
+    void setModel(QAbstractItemModel *arg)
+    {
+        if (m_model == arg)
+            return;
+
+        m_model = arg;
+        emit modelChanged(arg);
+    }
+
+signals:
+    void changed();
+
+    void signalWithModelIndex(QModelIndex index);
+    void signalWithPersistentModelIndex(QPersistentModelIndex index);
+
+    void modelChanged(QAbstractItemModel * arg);
+
+private:
+    QModelIndex m_modelIndex;
+    QPersistentModelIndex m_persistentModelIndex;
+    QAbstractItemModel *m_model;
+};
+
+#endif // TESTTYPES_H
+
diff --git a/tests/auto/qml/qqmlitemmodels/tst_qqmlitemmodels.cpp b/tests/auto/qml/qqmlitemmodels/tst_qqmlitemmodels.cpp
new file mode 100644 (file)
index 0000000..d7e3931
--- /dev/null
@@ -0,0 +1,223 @@
+/****************************************************************************
+**
+** Copyright (C) 2015 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL21$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 or version 3 as published by the Free
+** Software Foundation and appearing in the file LICENSE.LGPLv21 and
+** LICENSE.LGPLv3 included in the packaging of this file. Please review the
+** following information to ensure the GNU Lesser General Public License
+** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** As a special exception, The Qt Company gives you certain additional
+** rights. These rights are described in The Qt Company LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <qtest.h>
+#include <QQmlEngine>
+#include <QQmlComponent>
+#include <QDebug>
+#include <QStringListModel>
+#include "../../shared/util.h"
+#include "testtypes.h"
+#include "qtestmodel.h"
+
+#define INIT_TEST_OBJECT(fileName, object) \
+    QQmlComponent component_##object(&engine, testFileUrl(fileName)); \
+    QScopedPointer<ItemModelsTest>object(qobject_cast<ItemModelsTest *>(component_##object.create())); \
+
+
+class tst_qqmlitemmodels : public QQmlDataTest
+{
+    Q_OBJECT
+
+public:
+    tst_qqmlitemmodels() {}
+
+private slots:
+    void initTestCase();
+
+    void modelIndex();
+    void persistentModelIndex();
+    void modelIndexConversion();
+    void itemSelectionRange();
+    void itemSelection();
+    void modelIndexList();
+
+private:
+    QQmlEngine engine;
+};
+
+void tst_qqmlitemmodels::initTestCase()
+{
+    QQmlDataTest::initTestCase();
+    qmlRegisterType<ItemModelsTest>("Test", 1, 0, "ItemModelsTest");
+}
+
+void tst_qqmlitemmodels::modelIndex()
+{
+    INIT_TEST_OBJECT("modelindex.qml", object);
+    TestModel model(10, 10);
+
+    QModelIndex index = object->modelIndex();
+    for (int i = 0; i < 5; i++) {
+        QCOMPARE(object->property("isValid").toBool(), index.isValid());
+        QCOMPARE(object->property("row").toInt(), index.row());
+        QCOMPARE(object->property("column").toInt(), index.column());
+        QCOMPARE(object->property("parent").toModelIndex(), index.parent());
+        QCOMPARE(object->property("model").value<QAbstractItemModel *>(), index.model());
+        QCOMPARE(object->property("internalId").toULongLong(), index.internalId());
+
+        if (i < 3) {
+            index = model.index(2 + i, 4 - i, index);
+            object->setModelIndex(index);
+        } else if (i < 4) {
+            index = model.index(2 + i, 4 - i);
+            object->emitSignalWithModelIndex(index);
+        }
+    }
+}
+
+void tst_qqmlitemmodels::persistentModelIndex()
+{
+    INIT_TEST_OBJECT("persistentmodelindex.qml", object);
+    TestModel model(10, 10);
+
+    QPersistentModelIndex index = object->persistentModelIndex();
+    for (int i = 0; i < 5; i++) {
+        QCOMPARE(object->property("isValid").toBool(), index.isValid());
+        QCOMPARE(object->property("row").toInt(), index.row());
+        QCOMPARE(object->property("column").toInt(), index.column());
+        QCOMPARE(object->property("parent").toModelIndex(), index.parent());
+        QCOMPARE(object->property("model").value<QAbstractItemModel *>(), index.model());
+        QCOMPARE(object->property("internalId").toULongLong(), index.internalId());
+
+        if (i < 2) {
+            index = model.index(2 + i, 4 - i, index);
+            object->setPersistentModelIndex(index);
+        } else if (i < 3) {
+            model.removeRow(2);
+            QVERIFY(!index.isValid()); // QPersistentModelIndex should update
+            object->emitChanged(); // Help QML get the new values as QPMI doesn't emit anything
+        } else if (i < 4) {
+            index = model.index(2 + i, 4 - i);
+            object->emitSignalWithPersistentModelIndex(index);
+        }
+    }
+
+    const QVariant &pmiVariant = object->property("pmi");
+    QCOMPARE(pmiVariant.type(), QVariant::UserType);
+    QCOMPARE(pmiVariant.userType(), qMetaTypeId<QPersistentModelIndex>());
+    QCOMPARE(pmiVariant.value<QPersistentModelIndex>(), QPersistentModelIndex(model.index(0, 0)));
+}
+
+void tst_qqmlitemmodels::itemSelectionRange()
+{
+    INIT_TEST_OBJECT("itemselectionrange.qml", object);
+    TestModel model(10, 10);
+
+    for (int i = 0; i < 2; i++) {
+        const QVariant &isrVariant = object->property("itemSelectionRange");
+        QCOMPARE(isrVariant.type(), QVariant::UserType);
+        QCOMPARE(isrVariant.userType(), qMetaTypeId<QItemSelectionRange>());
+        const QItemSelectionRange &isr = isrVariant.value<QItemSelectionRange>();
+        if (i > 0) {
+            QModelIndex parentIndex = model.index(0, 0);
+            QCOMPARE(QModelIndex(isr.topLeft()), model.index(3, 0, parentIndex));
+            QCOMPARE(QModelIndex(isr.bottomRight()), model.index(5, 6, parentIndex));
+        } else {
+            QCOMPARE(QModelIndex(isr.topLeft()), QModelIndex());
+            QCOMPARE(QModelIndex(isr.bottomRight()), QModelIndex());
+        }
+
+        QCOMPARE(object->property("top").toInt(), isr.top());
+        QCOMPARE(object->property("left").toInt(), isr.left());
+        QCOMPARE(object->property("bottom").toInt(), isr.bottom());
+        QCOMPARE(object->property("right").toInt(), isr.right());
+        QCOMPARE(object->property("width").toInt(), isr.width());
+        QCOMPARE(object->property("height").toInt(), isr.height());
+        QCOMPARE(object->property("isValid").toBool(), isr.isValid());
+        QCOMPARE(object->property("isEmpty").toBool(), isr.isEmpty());
+        QCOMPARE(object->property("isrModel").value<QAbstractItemModel *>(), isr.model());
+
+        // Set model for the 2nd iteration and test again
+        object->setModel(&model);
+    }
+
+    // Check API function calls
+    QVERIFY(object->property("contains1").toBool());
+    QVERIFY(object->property("contains2").toBool());
+    QVERIFY(!object->property("intersects").toBool());
+    const QVariant &isrVariant = object->property("intersected");
+    QCOMPARE(isrVariant.type(), QVariant::UserType);
+    QCOMPARE(isrVariant.userType(), qMetaTypeId<QItemSelectionRange>());
+}
+
+void tst_qqmlitemmodels::modelIndexConversion()
+{
+    INIT_TEST_OBJECT("modelindexconversion.qml", object);
+    TestModel model(10, 10);
+    object->setModel(&model);
+
+    QCOMPARE(object->modelIndex(), model.index(0, 0));
+    QCOMPARE(object->persistentModelIndex(), QPersistentModelIndex(model.index(1, 1)));
+}
+
+void tst_qqmlitemmodels::itemSelection()
+{
+    INIT_TEST_OBJECT("itemselection.qml", object);
+    TestModel model(10, 10);
+
+    object->setModel(&model);
+    QCOMPARE(object->property("count").toInt(), 8);
+    QCOMPARE(object->property("contains").toBool(), true);
+
+    QVariant milVariant = object->property("itemSelection");
+    QCOMPARE(milVariant.type(), QVariant::UserType);
+    QCOMPARE(milVariant.userType(), qMetaTypeId<QItemSelection>());
+
+    const QItemSelection &mil = milVariant.value<QItemSelection>();
+    QCOMPARE(mil.count(), 5);
+}
+
+void tst_qqmlitemmodels::modelIndexList()
+{
+    INIT_TEST_OBJECT("modelindexlist.qml", object);
+    TestModel model(10, 10);
+
+    object->setModel(&model);
+    QCOMPARE(object->property("count").toInt(), 5);
+
+    QVariant milVariant = object->property("modelIndexList");
+    QCOMPARE(milVariant.type(), QVariant::UserType);
+    QCOMPARE(milVariant.userType(), qMetaTypeId<QModelIndexList>());
+
+    const QModelIndexList &mil = milVariant.value<QModelIndexList>();
+    QCOMPARE(mil.count(), 2);
+    QCOMPARE(mil.at(0), model.index(3, 3));
+    QCOMPARE(mil.at(1), model.index(4, 4));
+}
+
+#undef INIT_TEST_OBJECT
+
+QTEST_MAIN(tst_qqmlitemmodels)
+
+#include "tst_qqmlitemmodels.moc"