Hello community,

here is the log from the commit of package kitemmodels for openSUSE:Factory 
checked in at 2020-10-12 13:55:20
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/kitemmodels (Old)
 and      /work/SRC/openSUSE:Factory/.kitemmodels.new.3486 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "kitemmodels"

Mon Oct 12 13:55:20 2020 rev:84 rq:840830 version:5.75.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/kitemmodels/kitemmodels.changes  2020-09-18 
14:41:40.368016928 +0200
+++ /work/SRC/openSUSE:Factory/.kitemmodels.new.3486/kitemmodels.changes        
2020-10-12 13:57:05.362160427 +0200
@@ -1,0 +2,12 @@
+Mon Oct  5 08:33:20 UTC 2020 - Christophe Giboudeaux <[email protected]>
+
+- Update to 5.75.0
+  * New feature release
+  * For more details please see:
+  * https://kde.org/announcements/kde-frameworks-5.75.0
+- Changes since 5.74.0:
+  * add reference to Qt bug
+  * ignore sourceDataChanged on invalid indexes
+  * Support for KDescendantProxyModel "collapsing" nodes
+
+-------------------------------------------------------------------

Old:
----
  kitemmodels-5.74.0.tar.xz
  kitemmodels-5.74.0.tar.xz.sig

New:
----
  kitemmodels-5.75.0.tar.xz
  kitemmodels-5.75.0.tar.xz.sig

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ kitemmodels.spec ++++++
--- /var/tmp/diff_new_pack.qUBo9I/_old  2020-10-12 13:57:07.722161412 +0200
+++ /var/tmp/diff_new_pack.qUBo9I/_new  2020-10-12 13:57:07.726161413 +0200
@@ -17,7 +17,7 @@
 
 
 %define lname   libKF5ItemModels5
-%define _tar_path 5.74
+%define _tar_path 5.75
 # Full KF5 version (e.g. 5.33.0)
 %{!?_kf5_version: %global _kf5_version %{version}}
 # Last major and minor KF5 version (e.g. 5.33)
@@ -26,7 +26,7 @@
 # Only needed for the package signature condition
 %bcond_without lang
 Name:           kitemmodels
-Version:        5.74.0
+Version:        5.75.0
 Release:        0
 Summary:        Set of item models extending the Qt model-view framework
 License:        LGPL-2.1-or-later


++++++ kitemmodels-5.74.0.tar.xz -> kitemmodels-5.75.0.tar.xz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kitemmodels-5.74.0/CMakeLists.txt 
new/kitemmodels-5.75.0/CMakeLists.txt
--- old/kitemmodels-5.74.0/CMakeLists.txt       2020-09-06 11:23:39.000000000 
+0200
+++ new/kitemmodels-5.75.0/CMakeLists.txt       2020-10-04 11:53:39.000000000 
+0200
@@ -1,10 +1,10 @@
 cmake_minimum_required(VERSION 3.5)
 
-set(KF5_VERSION "5.74.0") # handled by release scripts
+set(KF5_VERSION "5.75.0") # handled by release scripts
 project(KItemModels VERSION ${KF5_VERSION})
 
 include(FeatureSummary)
-find_package(ECM 5.74.0  NO_MODULE)
+find_package(ECM 5.75.0  NO_MODULE)
 set_package_properties(ECM PROPERTIES TYPE REQUIRED DESCRIPTION "Extra CMake 
Modules." URL "https://commits.kde.org/extra-cmake-modules";)
 feature_summary(WHAT REQUIRED_PACKAGES_NOT_FOUND 
FATAL_ON_MISSING_REQUIRED_PACKAGES)
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/kitemmodels-5.74.0/autotests/kdescendantsproxymodeltest.cpp 
new/kitemmodels-5.75.0/autotests/kdescendantsproxymodeltest.cpp
--- old/kitemmodels-5.74.0/autotests/kdescendantsproxymodeltest.cpp     
2020-09-06 11:23:39.000000000 +0200
+++ new/kitemmodels-5.75.0/autotests/kdescendantsproxymodeltest.cpp     
2020-10-04 11:53:39.000000000 +0200
@@ -7,14 +7,235 @@
 #include "kdescendantsproxymodel.h"
 
 #include <QStandardItemModel>
+#include <QAbstractListModel>
 #include <QTest>
 #include <QSignalSpy>
 
+struct Node
+{
+    QString label;
+    Node *parent = nullptr;
+    QList<Node *> children;
+};
+
+class SimpleObjectModel: public QAbstractListModel
+{
+    Q_OBJECT
+public:
+    explicit SimpleObjectModel(QObject *parent = nullptr);
+    ~SimpleObjectModel();
+
+    QModelIndex index(int, int, const QModelIndex &parent = QModelIndex()) 
const override;
+    QModelIndex parent(const QModelIndex &) const override;
+    int rowCount(const QModelIndex &parent = QModelIndex()) const override;
+    bool hasChildren(const QModelIndex &parent = QModelIndex()) const override;
+    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const 
override;
+
+    bool insert(const QModelIndex &parent, int row, const QString &text);
+    bool removeRows(int row, int count, const QModelIndex &parent = 
QModelIndex()) override;
+    bool moveRows(const QModelIndex &sourceParent, int sourceRow, int count, 
const QModelIndex &destinationParent, int destinationRow) override;
+
+private:
+    Node *m_root;
+};
+
+SimpleObjectModel::SimpleObjectModel(QObject *parent)
+    : QAbstractListModel(parent)
+{
+    m_root = new Node;
+}
+
+SimpleObjectModel::~SimpleObjectModel()
+{}
+
+QModelIndex SimpleObjectModel::index(int row, int col, const QModelIndex 
&parent) const
+{
+    Node *parentItem;
+    if ( !parent.isValid() ) {
+        parentItem = static_cast<Node *>( m_root );
+    } else {
+        parentItem = static_cast<Node *>( parent.internalPointer() );
+    }
+
+    if (row < 0 || parentItem->children.size() <= row) {
+        return QModelIndex();
+    }
+    Node *childItem = parentItem->children[row];
+
+    return createIndex( row, col, childItem );
+}
+
+QModelIndex SimpleObjectModel::parent(const QModelIndex &index) const
+{
+    Node *childItem = static_cast<Node *>( index.internalPointer() );
+    if( !childItem ) {
+        return QModelIndex();
+    }
+
+    Node *parent = childItem->parent;
+    Node *grandParent = parent->parent;
+
+    int childRow = 0;
+    if( grandParent ) {
+        childRow = grandParent->children.indexOf( parent );
+    }
+
+    if ( parent == m_root ) {
+        return QModelIndex();
+    }
+    return createIndex( childRow, 0, parent );
+}
+
+int SimpleObjectModel::rowCount(const QModelIndex &index) const
+{
+    Node *item = static_cast<Node *>( index.internalPointer() );
+    if (!item) {
+        item = m_root;
+    }
+
+    return item->children.count();
+}
+
+bool SimpleObjectModel::hasChildren(const QModelIndex &index) const
+{
+    Node *item = static_cast<Node *>( index.internalPointer() );
+    if (!item) {
+        item = m_root;
+    }
+
+    return !item->children.isEmpty();
+}
+
+QVariant SimpleObjectModel::data(const QModelIndex &index, int role) const
+{
+    if (role != Qt::DisplayRole) {
+        return QVariant();
+    }
+
+    Node *node = static_cast<Node *>( index.internalPointer() );
+    if (!node) {
+        return QVariant();
+    }
+    return node->label;
+}
+
+bool SimpleObjectModel::insert(const QModelIndex &index, int row, const 
QString &text)
+{
+    if (row < 0) {
+        return false;
+    }
+
+    Node *parent = static_cast<Node *>( index.internalPointer() );
+    if (!parent) {
+        parent = m_root;
+    }
+
+    if (row > parent->children.count()) {
+        return false;
+    }
+
+    beginInsertRows(index, row, row);
+    Node *child = new Node;
+    child->parent = parent;
+    child->label = text;
+    parent->children.insert(row, child);
+    endInsertRows();
+
+    return true;
+}
+
+bool SimpleObjectModel::removeRows(int row, int count, const QModelIndex 
&index)
+{
+    if (row < 0) {
+        return false;
+    }
+
+    Node *parent = static_cast<Node *>( index.internalPointer() );
+    if (!parent) {
+        parent = m_root;
+    }
+
+    if (row >= parent->children.count()) {
+        return false;
+    }
+
+    beginRemoveRows(index, row, row);
+    Node *child = parent->children.takeAt(row);
+    delete child;
+    endRemoveRows();
+
+    return true;
+}
+
+bool SimpleObjectModel::moveRows(const QModelIndex &sourceParent, int 
sourceRow, int count, const QModelIndex &destinationParent, int destinationRow)
+{
+    Node *sourceNode = static_cast<Node *>( sourceParent.internalPointer() );
+    if (!sourceNode) {
+        sourceNode = m_root;
+    }
+    Node *destinationNode = static_cast<Node *>( 
destinationParent.internalPointer() );
+    if (!destinationNode) {
+        destinationNode = m_root;
+    }
+
+    const int sourceLast = sourceRow + count - 1;
+
+    if (sourceNode != destinationNode) {
+        if (count <= 0 || sourceRow < 0 || sourceRow >= 
sourceNode->children.count() ||
+            destinationRow < 0 || destinationRow > 
destinationNode->children.count()) {
+            return false;
+        }
+
+        if (!beginMoveRows(sourceParent, sourceRow, sourceLast, 
destinationParent, destinationRow)) {
+            return false;
+        }
+
+        Node *child = sourceNode->children.takeAt(sourceRow);
+        child->parent = destinationNode;
+        destinationNode->children.insert(destinationRow, child);
+
+        endMoveRows();
+        return true;
+    }
+
+    if (count <= 0 || sourceRow == destinationRow || sourceRow < 0 || 
sourceRow >= destinationNode->children.count() ||
+        destinationRow < 0 || destinationRow > 
destinationNode->children.count() || count - destinationRow > 
destinationNode->children.count() - sourceRow) {
+        return false;
+    }
+
+    //beginMoveRows wants indexes before the source rows are removed from the 
old order
+    if (!beginMoveRows(sourceParent, sourceRow, sourceLast, destinationParent, 
destinationRow)) {
+        return false;
+    }
+
+    if (sourceRow < destinationRow) {
+        for (int i = count - 1; i >= 0; --i) {
+            destinationNode->children.move(sourceRow + i, destinationRow - 
count + i);
+        }
+    } else {
+        for (int i = 0; i < count; ++i) {
+            destinationNode->children.move(sourceRow + i, destinationRow + i);
+        }
+    }
+
+    endMoveRows();
+    return true;
+}
+
+
 class tst_KDescendantProxyModel : public QObject
 {
     Q_OBJECT
     QAbstractItemModel *createTree(const QString &prefix)
     {
+        /*
+         * |- parent1
+         * |  |- child1
+         * |  `- child2
+         * `- parent2
+         *    |- child1
+         *    `- child2
+         */
         QStandardItemModel *model = new QStandardItemModel();
         for (int i = 0; i < 2 ; i++) {
             QStandardItem *item = new QStandardItem();
@@ -33,6 +254,12 @@
     void testChangeSeparator();
     void testChangeInvisibleSeparator();
     void testRemoveSeparator();
+
+    void testResetCollapsedModelContent();
+    void testInsertInCollapsedModel();
+    void testRemoveInCollapsedModel();
+    void testMoveInsideCollapsed();
+    void testExpandInsideCollapsed();
 };
 
 /// Tests that replacing the source model results in data getting changed
@@ -202,6 +429,242 @@
 
 }
 
+void tst_KDescendantProxyModel::testResetCollapsedModelContent()
+{
+    auto model1 = createTree("FirstModel");
+    KDescendantsProxyModel proxy;
+    proxy.setExpandsByDefault(false);
+    proxy.setSourceModel(model1);
+    QCOMPARE(proxy.rowCount(), 2);
+
+    {
+        QStringList results = QStringList()
+                              << "FirstModel0"
+                              << "FirstModel1";
+        QCOMPARE(proxy.rowCount(), results.count());
+        for (int i  = 0 ; i < proxy.rowCount() ; i++) {
+            QCOMPARE(proxy.index(i, 0).data(Qt::DisplayRole).toString(), 
results[i]);
+        }
+    }
+    {
+        QModelIndex idx = model1->index(0, 0);
+        proxy.expandSourceIndex(idx);
+        QStringList results = QStringList()
+                              << "FirstModel0"
+                              << "FirstModel0-0"
+                              << "FirstModel0-1"
+                              << "FirstModel1";
+        QCOMPARE(proxy.rowCount(), results.count());
+        for (int i  = 0 ; i < proxy.rowCount() ; i++) {
+            QCOMPARE(proxy.index(i, 0).data(Qt::DisplayRole).toString(), 
results[i]);
+        }
+    }
+    {
+        QModelIndex idx = model1->index(1, 0);
+        proxy.expandSourceIndex(idx);
+        QStringList results = QStringList()
+                              << "FirstModel0"
+                              << "FirstModel0-0"
+                              << "FirstModel0-1"
+                              << "FirstModel1"
+                              << "FirstModel1-0"
+                              << "FirstModel1-1";
+        QCOMPARE(proxy.rowCount(), results.count());
+        for (int i  = 0 ; i < proxy.rowCount() ; i++) {
+            QCOMPARE(proxy.index(i, 0).data(Qt::DisplayRole).toString(), 
results[i]);
+        }
+    }
+
+    auto model2 = createTree("SecondModel");
+    {
+        proxy.setSourceModel(model2);
+        QModelIndex idx = model2->index(0, 0);
+        proxy.expandSourceIndex(idx);
+        idx = model2->index(1, 0);
+        proxy.expandSourceIndex(idx);
+        QStringList results = QStringList()
+                              << "SecondModel0"
+                              << "SecondModel0-0"
+                              << "SecondModel0-1"
+                              << "SecondModel1"
+                              << "SecondModel1-0"
+                              << "SecondModel1-1";
+        QCOMPARE(proxy.rowCount(), results.count());
+        for (int i  = 0 ; i < proxy.rowCount() ; i++) {
+            QCOMPARE(proxy.index(i, 0).data(Qt::DisplayRole).toString(), 
results[i]);
+        }
+    }
+
+    delete model2;
+    delete model1;
+}
+
+void tst_KDescendantProxyModel::testInsertInCollapsedModel()
+{
+    QStandardItemModel *model1 = static_cast<QStandardItemModel 
*>(createTree("Model"));
+    KDescendantsProxyModel proxy;
+    proxy.setExpandsByDefault(false);
+    proxy.setSourceModel(model1);
+    QCOMPARE(proxy.rowCount(), 2);
+
+    QSignalSpy insertSpy(&proxy, &QAbstractItemModel::rowsInserted);
+    QCOMPARE(insertSpy.count(), 0);
+
+    QStandardItem *parent = model1->item(0, 0);
+    QVERIFY(parent);
+
+    QStandardItem *child = new QStandardItem();
+    child->setData(QString(QStringLiteral("Model") + QString::number(0) + "-" 
+ QString::number(2)), Qt::DisplayRole);
+    parent->appendRow(child);
+
+    // Adding a child to the collapsed parent doesn't have an effect to the 
proxy 
+    QCOMPARE(proxy.rowCount(), 2);
+    QCOMPARE(insertSpy.count(), 0);
+
+    // If we expand everything inserted should be here
+    QModelIndex idx = model1->index(0, 0);
+    proxy.expandSourceIndex(idx);
+
+    QCOMPARE(proxy.rowCount(), 5);
+    QCOMPARE(insertSpy.count(), 1);
+
+    QCOMPARE(proxy.index(3, 0).data(Qt::DisplayRole).toString(), 
QStringLiteral("Model0-2"));
+
+    // Add another child to the expanded node, now the proxy is affected 
immediately
+    child = new QStandardItem();
+    child->setData(QString(QStringLiteral("Model") + QString::number(0) + "-" 
+ QString::number(3)), Qt::DisplayRole);
+    parent->appendRow(child);
+
+    QCOMPARE(proxy.rowCount(), 6);
+    QCOMPARE(insertSpy.count(), 2);
+}
+
+void tst_KDescendantProxyModel::testRemoveInCollapsedModel()
+{
+    QStandardItemModel *model1 = static_cast<QStandardItemModel 
*>(createTree("Model"));
+    KDescendantsProxyModel proxy;
+    proxy.setExpandsByDefault(false);
+    proxy.setSourceModel(model1);
+    QCOMPARE(proxy.rowCount(), 2);
+
+    QSignalSpy removeSpy(&proxy, &QAbstractItemModel::rowsRemoved);
+    QCOMPARE(removeSpy.count(), 0);
+
+    QStandardItem *parent = model1->item(0, 0);
+    QVERIFY(parent);
+
+    parent->removeRow(0);
+
+    // Adding a child to the collapsed parent doesn't have an effect to the 
proxy 
+    QCOMPARE(proxy.rowCount(), 2);
+    QCOMPARE(removeSpy.count(), 0);
+
+    // If we expand everything inserted should be here
+    QModelIndex idx = model1->index(0, 0);
+    proxy.expandSourceIndex(idx);
+
+    QCOMPARE(proxy.rowCount(), 3);
+
+    QCOMPARE(proxy.index(1, 0).data(Qt::DisplayRole).toString(), 
QStringLiteral("Model0-1"));
+    parent->removeRow(0);
+
+    QCOMPARE(proxy.rowCount(), 2);
+    QCOMPARE(removeSpy.count(), 1);
+
+    idx = model1->index(1, 0);
+    proxy.expandSourceIndex(idx);
+    QCOMPARE(proxy.rowCount(), 4);
+}
+
+void tst_KDescendantProxyModel::testMoveInsideCollapsed()
+{
+    SimpleObjectModel *model = new SimpleObjectModel;
+    model->insert(QModelIndex(), 0, QStringLiteral("Model0"));
+    model->insert(QModelIndex(), 1, QStringLiteral("Model1"));
+    model->insert(QModelIndex(), 2, QStringLiteral("Model2"));
+
+    model->insert(model->index(0, 0, QModelIndex()), 0, 
QStringLiteral("Model0-0"));
+    model->insert(model->index(1, 0, QModelIndex()), 0, 
QStringLiteral("Model1-0"));
+
+    QCOMPARE(model->rowCount(), 3);
+
+    KDescendantsProxyModel proxy;
+    proxy.setExpandsByDefault(false);
+    proxy.setSourceModel(model);
+    QCOMPARE(proxy.rowCount(), 3);
+
+
+    QSignalSpy removeSpy(&proxy, &QAbstractItemModel::rowsRemoved);
+    QCOMPARE(removeSpy.count(), 0);
+    QSignalSpy insertSpy(&proxy, &QAbstractItemModel::rowsInserted);
+    QCOMPARE(insertSpy.count(), 0);
+
+    model->moveRows(QModelIndex(), 2, 1, model->index(0, 0, QModelIndex()), 1);
+
+    QCOMPARE(removeSpy.count(), 1);
+
+    QVERIFY(!removeSpy.first()[0].value<QModelIndex>().isValid());
+    QCOMPARE(removeSpy.first()[1].toInt(), 2);
+    QCOMPARE(removeSpy.first()[2].toInt(), 2);
+
+    QCOMPARE(model->rowCount(), 2);
+    QCOMPARE(proxy.rowCount(), 2);
+
+    model->moveRows(model->index(0, 0, QModelIndex()), 0, 1, QModelIndex(), 1);
+    QCOMPARE(insertSpy.count(), 1);
+    QCOMPARE(model->rowCount(), 3);
+    QCOMPARE(proxy.rowCount(), 3);
+
+    QVERIFY(!insertSpy.first()[0].value<QModelIndex>().isValid());
+    QCOMPARE(insertSpy.first()[1].toInt(), 1);
+    QCOMPARE(insertSpy.first()[2].toInt(), 1);
+
+    QModelIndex idx = model->index(0, 0);
+    proxy.expandSourceIndex(idx);
+    idx = model->index(1, 0);
+    proxy.expandSourceIndex(idx);
+    idx = model->index(2, 0);
+    proxy.expandSourceIndex(idx);
+    QStringList results = QStringList()
+                            << "Model0"
+                            << "Model2"
+                            << "Model0-0"
+                            << "Model1"
+                            << "Model1-0";
+    QCOMPARE(proxy.rowCount(), results.count());
+    for (int i  = 0 ; i < proxy.rowCount() ; i++) {
+        QCOMPARE(proxy.index(i, 0).data(Qt::DisplayRole).toString(), 
results[i]);
+    }
+}
+
+void tst_KDescendantProxyModel::testExpandInsideCollapsed()
+{
+    SimpleObjectModel *model = new SimpleObjectModel;
+    model->insert(QModelIndex(), 0, QStringLiteral("Model0"));
+    model->insert(QModelIndex(), 1, QStringLiteral("Model1"));
+    model->insert(QModelIndex(), 2, QStringLiteral("Model2"));
+
+    auto parentIndex = model->index(0, 0, QModelIndex());
+    model->insert(parentIndex, 0, QStringLiteral("Model0-0"));
+    auto childIndex = model->index(0, 0, parentIndex);
+    model->insert(childIndex, 0, QStringLiteral("Model0-0-0"));
+
+    QCOMPARE(model->rowCount(), 3);
+
+    KDescendantsProxyModel proxy;
+    proxy.setExpandsByDefault(false);
+    proxy.setSourceModel(model);
+    QCOMPARE(proxy.rowCount(), 3);
+
+    proxy.expandSourceIndex(childIndex);
+    QVERIFY(proxy.isSourceIndexExpanded(childIndex));
+    QVERIFY(!proxy.isSourceIndexExpanded(parentIndex));
+    QCOMPARE(proxy.rowCount(), 3);
+    proxy.expandSourceIndex(parentIndex);
+    QCOMPARE(proxy.rowCount(), 5);
+}
+
+
 QTEST_MAIN(tst_KDescendantProxyModel)
 
 #include "kdescendantsproxymodeltest.moc"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/kitemmodels-5.74.0/src/core/kdescendantsproxymodel.cpp 
new/kitemmodels-5.75.0/src/core/kdescendantsproxymodel.cpp
--- old/kitemmodels-5.74.0/src/core/kdescendantsproxymodel.cpp  2020-09-06 
11:23:39.000000000 +0200
+++ new/kitemmodels-5.75.0/src/core/kdescendantsproxymodel.cpp  2020-10-04 
11:53:39.000000000 +0200
@@ -41,6 +41,7 @@
 
     void resetInternalData();
 
+    void notifyhasSiblings(const QModelIndex &parent);
     void sourceRowsAboutToBeInserted(const QModelIndex &, int, int);
     void sourceRowsInserted(const QModelIndex &, int, int);
     void sourceRowsAboutToBeRemoved(const QModelIndex &, int, int);
@@ -59,6 +60,7 @@
     QPair<int, int> m_removePair;
     QPair<int, int> m_insertPair;
 
+    bool m_expandsByDefault = true;
     bool m_ignoreNextLayoutAboutToBeChanged;
     bool m_ignoreNextLayoutChanged;
     bool m_relayouting;
@@ -66,6 +68,9 @@
     bool m_displayAncestorData;
     QString m_ancestorSeparator;
 
+    QSet<QPersistentModelIndex> m_expandedSourceIndexes;
+    QSet<QPersistentModelIndex> m_collapsedSourceIndexes;
+
     QList<QPersistentModelIndex> m_layoutChangePersistentIndexes;
     QModelIndexList m_proxyIndexes;
 };
@@ -115,9 +120,19 @@
             it = m_pendingParents.erase(it);
             continue;
         }
+        if (!q->isSourceIndexVisible(sourceParent)) {
+            // It's a collapsed node, or its parents are collapsed, ignore.
+            it = m_pendingParents.erase(it);
+            continue;
+        }
+
         const int rowCount = q->sourceModel()->rowCount(sourceParent);
 
-        Q_ASSERT(rowCount > 0);
+        // A node can be marked as collapsed or expanded even if it doesn'0t 
have children
+        if (rowCount == 0) {
+            it = m_pendingParents.erase(it);
+            continue;
+        }
         const QPersistentModelIndex sourceIndex = 
q->sourceModel()->index(rowCount - 1, 0, sourceParent);
 
         Q_ASSERT(sourceIndex.isValid());
@@ -148,7 +163,9 @@
 
             if (q->sourceModel()->hasChildren(child)) {
                 Q_ASSERT(q->sourceModel()->rowCount(child) > 0);
-                newPendingParents.append(child);
+                if (q->isSourceIndexExpanded(child)) {
+                    newPendingParents.append(child);
+                }
             }
         }
     }
@@ -194,6 +211,143 @@
     delete d_ptr;
 }
 
+QHash<int, QByteArray> KDescendantsProxyModel::roleNames() const
+{
+    QHash<int, QByteArray> roleNames = QAbstractProxyModel::roleNames();
+
+    roleNames[LevelRole] = "kDescendantLevel";
+    roleNames[ExpandableRole] = "kDescendantExpandable";
+    roleNames[ExpandedRole] = "kDescendantExpanded";
+    roleNames[HasSiblingsRole] = "kDescendantHasSiblings";
+    return roleNames;
+}
+
+void KDescendantsProxyModel::setExpandsByDefault(bool expand)
+{
+    if (d_ptr->m_expandsByDefault == expand) {
+        return;
+    }
+
+    beginResetModel();
+    d_ptr->m_expandsByDefault = expand;
+    d_ptr->m_expandedSourceIndexes.clear();
+    d_ptr->m_collapsedSourceIndexes.clear();
+    endResetModel();
+}
+
+bool KDescendantsProxyModel::expandsByDefault() const
+{
+    return d_ptr->m_expandsByDefault;
+}
+
+bool KDescendantsProxyModel::isSourceIndexExpanded(const QModelIndex 
&sourceIndex) const
+{
+    // Root is always expanded
+    if (!sourceIndex.isValid()) {
+        return true;
+    } else if (d_ptr->m_expandsByDefault)  {
+        return 
!d_ptr->m_collapsedSourceIndexes.contains(QPersistentModelIndex(sourceIndex));
+    } else {
+        return 
d_ptr->m_expandedSourceIndexes.contains(QPersistentModelIndex(sourceIndex));
+    }
+}
+
+bool KDescendantsProxyModel::isSourceIndexVisible(const QModelIndex 
&sourceIndex) const
+{
+    // Root is always visible
+    if (!sourceIndex.isValid()) {
+        return true;
+    }
+
+    QModelIndex index(sourceIndex);
+    do {
+        index = index.parent();
+        if (!index.isValid()) {
+            return true;
+        }
+    } while (isSourceIndexExpanded(index));
+
+    return false;
+}
+
+void KDescendantsProxyModel::expandSourceIndex(const QModelIndex &sourceIndex)
+{
+    if (!sourceIndex.isValid() || isSourceIndexExpanded(sourceIndex)) {
+        return;
+    }
+
+    if (d_ptr->m_expandsByDefault) {
+        d_ptr->m_collapsedSourceIndexes.remove( 
QPersistentModelIndex(sourceIndex));
+    } else {
+        d_ptr->m_expandedSourceIndexes << QPersistentModelIndex(sourceIndex);
+    }
+
+    d_ptr->m_pendingParents << sourceIndex;
+    d_ptr->scheduleProcessPendingParents();
+    Q_EMIT sourceIndexExpanded(sourceIndex);
+
+    const QModelIndex index = mapFromSource(sourceIndex);
+    Q_EMIT dataChanged(index, index, {ExpandedRole});
+}
+
+void KDescendantsProxyModel::collapseSourceIndex(const QModelIndex 
&sourceIndex)
+{
+    if (!sourceIndex.isValid() || !isSourceIndexExpanded(sourceIndex)) {
+        return;
+    }
+
+    const int row = mapFromSource(sourceIndex).row();
+    const int rowStart = row + 1;
+    int rowEnd = row;
+
+    QList <QModelIndex> toVisit = {sourceIndex};
+    QSet <QModelIndex> visited;
+    while (!toVisit.isEmpty()) {
+        QModelIndex index = toVisit.takeLast();
+        if (!visited.contains(index)) {
+            visited << index;
+            const int nRows = sourceModel()->rowCount(index);
+            rowEnd += nRows;
+            for (int i = 0; i < nRows; ++i) {
+                QModelIndex child = sourceModel()->index(i, 0, index);
+                if (isSourceIndexExpanded(child)) {
+                    toVisit << child;
+                }
+            }
+        }
+    }
+
+    if (d_ptr->m_expandsByDefault) {
+        d_ptr->m_collapsedSourceIndexes << QPersistentModelIndex(sourceIndex);
+    } else {
+        d_ptr->m_expandedSourceIndexes.remove( 
QPersistentModelIndex(sourceIndex));
+    }
+
+    {
+        Mapping::right_iterator it = 
d_ptr->m_mapping.rightLowerBound(rowStart);
+        const Mapping::right_iterator endIt = 
d_ptr->m_mapping.rightUpperBound(rowEnd);
+
+        if (endIt != d_ptr->m_mapping.rightEnd())
+            while (it != endIt) {
+                it = d_ptr->m_mapping.eraseRight(it);
+            }
+        else
+            while (it != d_ptr->m_mapping.rightUpperBound(rowEnd)) {
+                it = d_ptr->m_mapping.eraseRight(it);
+            }
+    }
+
+    d_ptr->m_removePair = qMakePair(rowStart, rowEnd);
+    
+    beginRemoveRows(QModelIndex(), rowStart, rowEnd);
+    d_ptr->synchronousMappingRefresh();
+    endRemoveRows();
+    Q_EMIT sourceIndexCollapsed(sourceIndex);
+
+    const QModelIndex ownIndex = mapFromSource(sourceIndex);
+    Q_EMIT dataChanged(ownIndex, ownIndex, {ExpandedRole});
+}
+
 #if KITEMMODELS_BUILD_DEPRECATED_SINCE(4, 8)
 void KDescendantsProxyModel::setRootIndex(const QModelIndex &index)
 {
@@ -291,6 +445,7 @@
     }
 
     QAbstractProxyModel::setSourceModel(_sourceModel);
+    d_ptr->m_expandedSourceIndexes.clear();
 
     if (_sourceModel) {
         for (int i = 0; i < int(sizeof modelSignals / sizeof * modelSignals); 
++i) {
@@ -426,6 +581,10 @@
         const QModelIndex sourceParent = sourceIndex.parent();
         Mapping::right_const_iterator result = end;
 
+        if (!isSourceIndexVisible(sourceIndex)) {
+            return QModelIndex();
+        }
+
         for (; it != end; ++it) {
             QModelIndex index = it.value();
             bool found_block = false;
@@ -500,6 +659,27 @@
             sourceIndex = sourceIndex.parent();
         }
         return displayData;
+    } else if (role == LevelRole) {
+        QModelIndex sourceIndex = mapToSource(index);
+        int level = 0;
+        while (sourceIndex.isValid()) {
+            sourceIndex = sourceIndex.parent();
+            ++level;
+        }
+        return level;
+    } else if (role == ExpandableRole) {
+        QModelIndex sourceIndex = mapToSource(index);
+        return sourceModel()->hasChildren(sourceIndex);
+    } else if (role == ExpandedRole) {
+        return isSourceIndexExpanded(mapToSource(index));
+    } else if (role == HasSiblingsRole) {
+        QModelIndex sourceIndex = mapToSource(index);
+        QList<bool> hasSibling;
+        while (sourceIndex.isValid()) {
+            hasSibling.prepend(sourceModel()->rowCount(sourceIndex.parent()) > 
sourceIndex.row() + 1);
+            sourceIndex = sourceIndex.parent();
+        }
+        return QVariant::fromValue(hasSibling);
     } else {
         return sourceIndex.data(role);
     }
@@ -525,10 +705,29 @@
     return sourceModel()->flags(srcIndex);
 }
 
+void KDescendantsProxyModelPrivate::notifyhasSiblings(const QModelIndex 
&parent)
+{
+    Q_Q(KDescendantsProxyModel);
+
+    if (!parent.isValid()) {
+        return;
+    }
+
+    QModelIndex localParent = q->mapFromSource(parent);
+    Q_EMIT q->dataChanged(localParent, localParent, 
{KDescendantsProxyModel::HasSiblingsRole});
+    for (int i = 0; i < q->sourceModel()->rowCount(parent); ++i) {
+        notifyhasSiblings(q->sourceModel()->index(i, 0, parent));
+    }
+}
+
 void KDescendantsProxyModelPrivate::sourceRowsAboutToBeInserted(const 
QModelIndex &parent, int start, int end)
 {
     Q_Q(KDescendantsProxyModel);
 
+    if (parent.isValid() && (!q->isSourceIndexExpanded(parent) || 
!q->isSourceIndexVisible(parent))) {
+        return;
+    }
+
     if (!q->sourceModel()->hasChildren(parent)) {
         Q_ASSERT(q->sourceModel()->rowCount(parent) == 0);
         // parent was not a parent before.
@@ -548,7 +747,7 @@
         Q_ASSERT(rowCount == start);
         static const int column = 0;
         QModelIndex idx = q->sourceModel()->index(rowCount - 1, column, 
parent);
-        while (q->sourceModel()->hasChildren(idx)) {
+        while (q->isSourceIndexExpanded(idx) && 
q->sourceModel()->hasChildren(idx)) {
             Q_ASSERT(q->sourceModel()->rowCount(idx) > 0);
             idx = q->sourceModel()->index(q->sourceModel()->rowCount(idx) - 1, 
column, idx);
         }
@@ -564,7 +763,14 @@
 void KDescendantsProxyModelPrivate::sourceRowsInserted(const QModelIndex 
&parent, int start, int end)
 {
     Q_Q(KDescendantsProxyModel);
-
+    if (parent.isValid() && (!q->isSourceIndexExpanded(parent) || 
!q->isSourceIndexVisible(parent))) {
+        const QModelIndex index = q->mapFromSource(parent);
+        Q_EMIT q->dataChanged(index, index, 
{KDescendantsProxyModel::ExpandableRole, KDescendantsProxyModel::ExpandedRole, 
KDescendantsProxyModel::HasSiblingsRole});
+        if (start > 0) {
+            notifyhasSiblings(q->sourceModel()->index(start - 1, 0, parent));
+        }
+        return;
+    }
     Q_ASSERT(q->sourceModel()->index(start, 0, parent).isValid());
 
     const int rowCount = q->sourceModel()->rowCount(parent);
@@ -573,9 +779,14 @@
     const int difference = end - start + 1;
 
     if (rowCount == difference) {
+        const QModelIndex index = q->mapFromSource(parent);
+        Q_EMIT q->dataChanged(index, index, 
{KDescendantsProxyModel::ExpandableRole, KDescendantsProxyModel::ExpandedRole, 
KDescendantsProxyModel::HasSiblingsRole});
         // @p parent was not a parent before.
         m_pendingParents.append(parent);
         scheduleProcessPendingParents();
+        if (start > 0) {
+            notifyhasSiblings(q->sourceModel()->index(start - 1, 0, parent));
+        }
         return;
     }
 
@@ -636,11 +847,11 @@
             // and we then insert D as a sibling of A below it, we need to 
remove the mapping for A,
             // and the row number used for D must take into account the 
descendants of A.
 
-            while (q->sourceModel()->hasChildren(indexAbove)) {
+            while (q->isSourceIndexExpanded(indexAbove) && 
q->sourceModel()->hasChildren(indexAbove)) {
                 Q_ASSERT(q->sourceModel()->rowCount(indexAbove) > 0);
                 indexAbove = 
q->sourceModel()->index(q->sourceModel()->rowCount(indexAbove) - 1,  column, 
indexAbove);
             }
-            Q_ASSERT(q->sourceModel()->rowCount(indexAbove) == 0);
+            Q_ASSERT(!q->isSourceIndexExpanded(indexAbove) || 
q->sourceModel()->rowCount(indexAbove) == 0);
         }
 
         Q_ASSERT(m_mapping.leftContains(indexAbove));
@@ -658,7 +869,8 @@
         static const int column = 0;
         const QModelIndex idx = q->sourceModel()->index(row, column, parent);
         Q_ASSERT(idx.isValid());
-        if (q->sourceModel()->hasChildren(idx)) {
+
+        if (q->isSourceIndexExpanded(idx) && 
q->sourceModel()->hasChildren(idx)) {
             Q_ASSERT(q->sourceModel()->rowCount(idx) > 0);
             m_pendingParents.append(idx);
         }
@@ -668,12 +880,22 @@
 
     q->endInsertRows();
     scheduleProcessPendingParents();
+    const QModelIndex index = q->mapFromSource(parent);
+    Q_EMIT q->dataChanged(index, index, 
{KDescendantsProxyModel::ExpandableRole, KDescendantsProxyModel::ExpandedRole, 
KDescendantsProxyModel::HasSiblingsRole});
+
+    if (start > 0) {
+        notifyhasSiblings(q->sourceModel()->index(start - 1, 0, parent));
+    }
 }
 
 void KDescendantsProxyModelPrivate::sourceRowsAboutToBeRemoved(const 
QModelIndex &parent, int start, int end)
 {
     Q_Q(KDescendantsProxyModel);
 
+    if (!q->isSourceIndexExpanded(parent) || !q->isSourceIndexVisible(parent)) 
{
+        return;
+    }
+
     const int proxyStart = q->mapFromSource(q->sourceModel()->index(start, 0, 
parent)).row();
 
     static const int column = 0;
@@ -684,6 +906,11 @@
     }
     const int proxyEnd = q->mapFromSource(idx).row();
 
+    for(int i = start; i <= end; ++i) {
+        QModelIndex idx = q->sourceModel()->index(i, column, parent);
+        m_expandedSourceIndexes.remove(QPersistentModelIndex(idx));
+    }
+
     m_removePair = qMakePair(proxyStart, proxyEnd);
 
     q->beginRemoveRows(QModelIndex(), proxyStart, proxyEnd);
@@ -710,6 +937,12 @@
     Q_Q(KDescendantsProxyModel);
     Q_UNUSED(end)
 
+    if (!q->isSourceIndexExpanded(parent) || !q->isSourceIndexVisible(parent)) 
{
+        const QModelIndex index = q->mapFromSource(parent);
+        Q_EMIT q->dataChanged(index, index, 
{KDescendantsProxyModel::ExpandableRole, KDescendantsProxyModel::ExpandedRole});
+        return;
+    }
+
     const int rowCount = q->sourceModel()->rowCount(parent);
 
     const int proxyStart = m_removePair.first;
@@ -738,6 +971,11 @@
 
     if (rowCount != start || rowCount == 0) {
         q->endRemoveRows();
+        const QModelIndex index = q->mapFromSource(parent);
+        Q_EMIT q->dataChanged(index, index, 
{KDescendantsProxyModel::ExpandableRole, KDescendantsProxyModel::ExpandedRole});
+        if (start > 0) {
+            notifyhasSiblings(q->sourceModel()->index(start - 1, 0, parent));
+        }
         return;
     }
 
@@ -748,6 +986,9 @@
     if (m_mapping.isEmpty()) {
         m_mapping.insert(newEnd, newEnd.row());
         q->endRemoveRows();
+        if (start > 0) {
+            notifyhasSiblings(q->sourceModel()->index(start - 1, 0, parent));
+        }
         return;
     }
     if (q->sourceModel()->hasChildren(newEnd)) {
@@ -758,6 +999,9 @@
 
         m_mapping.insert(newEnd, firstDeepestProxy - count);
         q->endRemoveRows();
+        if (start > 0) {
+            notifyhasSiblings(q->sourceModel()->index(start - 1, 0, parent));
+        }
         return;
     }
     Mapping::right_iterator lowerBound = m_mapping.rightLowerBound(proxyStart);
@@ -774,6 +1018,9 @@
         }
         m_mapping.insert(newEnd, proxyRow);
         q->endRemoveRows();
+        if (start > 0) {
+            notifyhasSiblings(q->sourceModel()->index(start - 1, 0, parent));
+        }
         return;
     } else if (lowerBound == m_mapping.rightBegin()) {
         int proxyRow = rowCount - 1;
@@ -784,6 +1031,9 @@
         }
         m_mapping.insert(newEnd, proxyRow);
         q->endRemoveRows();
+        if (start > 0) {
+            notifyhasSiblings(q->sourceModel()->index(start - 1, 0, parent));
+        }
         return;
     }
     const Mapping::right_iterator boundAbove = lowerBound - 1;
@@ -797,6 +1047,9 @@
             if (target == boundAbove.value()) {
                 m_mapping.insert(newEnd, count + boundAbove.key() + 
newEnd.row() + 1);
                 q->endRemoveRows();
+                if (start > 0) {
+                    notifyhasSiblings(q->sourceModel()->index(start - 1, 0, 
parent));
+                }
                 return;
             }
             count += (target.row() + 1);
@@ -839,26 +1092,58 @@
     }
     m_mapping.insert(newEnd, proxyRow + newEnd.row());
     q->endRemoveRows();
+
+    const QModelIndex index = q->mapFromSource(parent);
+    Q_EMIT q->dataChanged(index, index, 
{KDescendantsProxyModel::ExpandableRole});
+
+    if (!q->sourceModel()->hasChildren(parent)) {
+        Q_EMIT q->dataChanged(index, index, 
{KDescendantsProxyModel::ExpandedRole});
+    } else if (q->sourceModel()->rowCount(parent) <= start) {
+        const QModelIndex index = 
q->mapFromSource(q->sourceModel()->index(q->sourceModel()->rowCount(parent) - 
1, 0, parent));
+        Q_EMIT q->dataChanged(index, index, 
{KDescendantsProxyModel::ExpandedRole});
+    }
+    if (start > 0) {
+        notifyhasSiblings(q->sourceModel()->index(start - 1, 0, parent));
+    }
 }
 
 void KDescendantsProxyModelPrivate::sourceRowsAboutToBeMoved(const QModelIndex 
&srcParent, int srcStart, int srcEnd, const QModelIndex &destParent, int 
destStart)
 {
-    Q_UNUSED(srcParent)
-    Q_UNUSED(srcStart)
-    Q_UNUSED(srcEnd)
-    Q_UNUSED(destParent)
+    Q_Q(KDescendantsProxyModel);
+
     Q_UNUSED(destStart)
     sourceLayoutAboutToBeChanged();
 }
 
 void KDescendantsProxyModelPrivate::sourceRowsMoved(const QModelIndex 
&srcParent, int srcStart, int srcEnd, const QModelIndex &destParent, int 
destStart)
 {
+    Q_Q(KDescendantsProxyModel);
+
     Q_UNUSED(srcParent)
     Q_UNUSED(srcStart)
     Q_UNUSED(srcEnd)
     Q_UNUSED(destParent)
     Q_UNUSED(destStart)
     sourceLayoutChanged();
+
+    const QModelIndex index1 = q->mapFromSource(srcParent);
+    const QModelIndex index2 = q->mapFromSource(destParent);
+    Q_EMIT q->dataChanged(index1, index1, 
{KDescendantsProxyModel::ExpandableRole});
+    if (index1 != index2) {
+        Q_EMIT q->dataChanged(index2, index2, 
{KDescendantsProxyModel::ExpandableRole});
+        if (!q->sourceModel()->hasChildren(destParent)) {
+            Q_EMIT q->dataChanged(index2, index2, 
{KDescendantsProxyModel::ExpandableRole});
+        }
+    }
+    const QModelIndex lastIndex = 
q->mapFromSource(q->sourceModel()->index(q->sourceModel()->rowCount(srcParent) 
- 1, 0, srcParent));
+    Q_EMIT q->dataChanged(lastIndex, lastIndex, 
{KDescendantsProxyModel::ExpandableRole});
+
+    if (srcStart > 0) {
+        notifyhasSiblings(q->sourceModel()->index(srcStart - 1, 0, srcParent));
+    }
+    if (destStart > 0) {
+        notifyhasSiblings(q->sourceModel()->index(destStart - 1, 0, 
destParent));
+    }
 }
 
 void KDescendantsProxyModelPrivate::sourceModelAboutToBeReset()
@@ -935,14 +1220,27 @@
 void KDescendantsProxyModelPrivate::sourceDataChanged(const QModelIndex 
&topLeft, const QModelIndex &bottomRight)
 {
     Q_Q(KDescendantsProxyModel);
+    // It is actually possible in a real world scenario that the source model 
emits dataChanged
+    // with invalid indexes when the source model is a QSortFilterProxyModel
+    // because QSortFilterProxyModel doesn't check for mapped index validity 
when its
+    // source model emitted dataChanged on a column QSortFilterProxyModel 
doesn't accept.
+    // See https://bugreports.qt.io/browse/QTBUG-86850
+    if (!topLeft.isValid() || !bottomRight.isValid()) {
+        return;
+    }
     Q_ASSERT(topLeft.model() == q->sourceModel());
     Q_ASSERT(bottomRight.model() == q->sourceModel());
 
+    if (!q->isSourceIndexExpanded(topLeft.parent()) || 
!q->isSourceIndexVisible(topLeft.parent())) {
+        return;
+    }
+
     const int topRow = topLeft.row();
     const int bottomRow = bottomRight.row();
 
     for (int i = topRow; i <= bottomRow; ++i) {
         const QModelIndex sourceTopLeft = q->sourceModel()->index(i, 
topLeft.column(), topLeft.parent());
+
         Q_ASSERT(sourceTopLeft.isValid());
         const QModelIndex proxyTopLeft = q->mapFromSource(sourceTopLeft);
         // TODO. If an index does not have any descendants, then we can emit 
in blocks of rows.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kitemmodels-5.74.0/src/core/kdescendantsproxymodel.h 
new/kitemmodels-5.75.0/src/core/kdescendantsproxymodel.h
--- old/kitemmodels-5.74.0/src/core/kdescendantsproxymodel.h    2020-09-06 
11:23:39.000000000 +0200
+++ new/kitemmodels-5.75.0/src/core/kdescendantsproxymodel.h    2020-10-04 
11:53:39.000000000 +0200
@@ -72,8 +72,25 @@
      */
     Q_PROPERTY(QString ancestorSeparator READ ancestorSeparator WRITE 
setAncestorSeparator NOTIFY ancestorSeparatorChanged)
 
+    /**
+     * If true, all the nodes in the whole tree will be expanded upon loading 
and all items
+     * of the source model will be shown in the proxy.
+     * The default value is true.
+     * @since 5.74
+     */
+    Q_PROPERTY(bool expandsByDefault READ expandsByDefault WRITE 
setExpandsByDefault NOTIFY expandsByDefaultChanged)
+
 public:
 
+    enum AdditionalRoles {
+        // Note: use printf "0x%08X\n" $(($RANDOM*$RANDOM))
+        // to define additional roles.
+        LevelRole = 0x14823F9A,
+        ExpandableRole = 0x1CA894AD,
+        ExpandedRole = 0x1E413DA4,
+        HasSiblingsRole = 0x1633CE0C
+    };
+
     /**
      * Creates a new descendant entities proxy model.
      *
@@ -170,6 +187,46 @@
     QModelIndex index(int, int, const QModelIndex &parent = QModelIndex()) 
const override;
     QModelIndex parent(const QModelIndex &) const override;
     int columnCount(const QModelIndex &index = QModelIndex()) const override;
+    QHash<int, QByteArray> roleNames() const override;
+
+    /**
+     * If true, all the nodes in the whole tree will be expanded upon loading 
(default)
+     * @param expand whether we want everything expanded upon load
+     * @since 5.74
+     */
+    void setExpandsByDefault(bool expand);
+
+    /**
+     * @returns true if all the tree nodes are expanded by default upon loading
+     * @since 5.74
+     */
+    bool expandsByDefault() const;
+
+    /**
+     * @returns true if the source index is mapped in the proxy as expanded, 
therefore it will show its children
+     * @since 5.74
+     */
+    bool isSourceIndexExpanded(const QModelIndex &sourceIndex) const;
+
+    /**
+     * @returns true if the source index is visible in the proxy, meaning all 
its parent hyerarchy is expanded.
+     * @since 5.74
+     */
+    bool isSourceIndexVisible(const QModelIndex &sourceIndex) const;
+
+    /**
+     * Maps a source index as expanded in the proxy, all its children will 
become visible.
+     * @param sourceIndex an idex of the source model.
+     * @since 5.74
+     */
+    void expandSourceIndex(const QModelIndex &sourceIndex);
+
+    /**
+     * Maps a source index as collapsed in the proxy, all its children will be 
hidden.
+     * @param sourceIndex an idex of the source model.
+     * @since 5.74
+     */
+    void collapseSourceIndex(const QModelIndex &sourceIndex);
 
     Qt::DropActions supportedDropActions() const override;
 
@@ -183,6 +240,9 @@
     void sourceModelChanged();
     void displayAncestorDataChanged();
     void ancestorSeparatorChanged();
+    void expandsByDefaultChanged(bool expands);
+    void sourceIndexExpanded(const QModelIndex &sourceIndex);
+    void sourceIndexCollapsed(const QModelIndex &sourceIndex);
 
 private:
     Q_DECLARE_PRIVATE(KDescendantsProxyModel)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kitemmodels-5.74.0/src/qml/CMakeLists.txt 
new/kitemmodels-5.75.0/src/qml/CMakeLists.txt
--- old/kitemmodels-5.74.0/src/qml/CMakeLists.txt       2020-09-06 
11:23:39.000000000 +0200
+++ new/kitemmodels-5.75.0/src/qml/CMakeLists.txt       2020-10-04 
11:53:39.000000000 +0200
@@ -1,6 +1,7 @@
 set(corebindings_SRCS
     plugin.cpp
     kconcatenaterowsproxymodel_qml.cpp
+    kdescendantsproxymodel_qml.cpp
     ksortfilterproxymodel.cpp
 )
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/kitemmodels-5.74.0/src/qml/kdescendantsproxymodel_qml.cpp 
new/kitemmodels-5.75.0/src/qml/kdescendantsproxymodel_qml.cpp
--- old/kitemmodels-5.74.0/src/qml/kdescendantsproxymodel_qml.cpp       
1970-01-01 01:00:00.000000000 +0100
+++ new/kitemmodels-5.75.0/src/qml/kdescendantsproxymodel_qml.cpp       
2020-10-04 11:53:39.000000000 +0200
@@ -0,0 +1,45 @@
+/*
+    SPDX-FileCopyrightText: 2020 Marco Martin <[email protected]>
+
+    SPDX-License-Identifier: LGPL-2.0-or-later
+*/
+
+#include "kdescendantsproxymodel_qml.h"
+#include <QDebug>
+
+KDescendantsProxyModelQml::KDescendantsProxyModelQml(QObject *parent):
+    KDescendantsProxyModel(parent)
+{
+}
+
+KDescendantsProxyModelQml::~KDescendantsProxyModelQml()
+{}
+
+void KDescendantsProxyModelQml::expandChildren(int row)
+{
+    QModelIndex idx = mapToSource(index(row, 0));
+    expandSourceIndex(idx);
+}
+
+void KDescendantsProxyModelQml::collapseChildren(int row)
+{
+    QModelIndex idx = mapToSource(index(row, 0));
+    collapseSourceIndex(idx);
+}
+
+void KDescendantsProxyModelQml::toggleChildren(int row)
+{
+    QModelIndex sourceIndex = mapToSource(index(row, 0));
+
+    if (!sourceModel()->hasChildren(sourceIndex)) {
+        return;
+    }
+
+    if (isSourceIndexExpanded(sourceIndex)) {
+        collapseSourceIndex(sourceIndex);
+    } else {
+        expandSourceIndex(sourceIndex);
+    }
+}
+
+#include "moc_kdescendantsproxymodel_qml.cpp"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/kitemmodels-5.74.0/src/qml/kdescendantsproxymodel_qml.h 
new/kitemmodels-5.75.0/src/qml/kdescendantsproxymodel_qml.h
--- old/kitemmodels-5.74.0/src/qml/kdescendantsproxymodel_qml.h 1970-01-01 
01:00:00.000000000 +0100
+++ new/kitemmodels-5.75.0/src/qml/kdescendantsproxymodel_qml.h 2020-10-04 
11:53:39.000000000 +0200
@@ -0,0 +1,26 @@
+/*
+    SPDX-FileCopyrightText: 2020 Marco Martin <[email protected]>
+
+    SPDX-License-Identifier: LGPL-2.0-or-later
+*/
+
+//This class exposes KDescendantsProxyModel in a more QML friendly way
+
+#pragma once
+
+#include <QObject>
+#include <QPointer>
+#include <KDescendantsProxyModel>
+
+class KDescendantsProxyModelQml : public KDescendantsProxyModel
+{
+    Q_OBJECT
+
+public:
+    explicit KDescendantsProxyModelQml(QObject *parent = nullptr);
+    ~KDescendantsProxyModelQml();
+
+    Q_INVOKABLE void expandChildren(int row);
+    Q_INVOKABLE void collapseChildren(int row);
+    Q_INVOKABLE void toggleChildren(int row);
+};
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kitemmodels-5.74.0/src/qml/plugin.cpp 
new/kitemmodels-5.75.0/src/qml/plugin.cpp
--- old/kitemmodels-5.74.0/src/qml/plugin.cpp   2020-09-06 11:23:39.000000000 
+0200
+++ new/kitemmodels-5.75.0/src/qml/plugin.cpp   2020-10-04 11:53:39.000000000 
+0200
@@ -14,6 +14,7 @@
 #include <KColumnHeadersModel>
 #include "ksortfilterproxymodel.h"
 #include "kconcatenaterowsproxymodel_qml.h"
+#include "kdescendantsproxymodel_qml.h"
 
 void Plugin::initializeEngine(QQmlEngine *engine, const char *uri)
 {
@@ -29,7 +30,7 @@
     qmlRegisterType<QAbstractItemModel>();
 #endif
     
qmlRegisterExtendedType<KConcatenateRowsProxyModel,KConcatenateRowsProxyModelQml>(uri,
 1, 0, "KConcatenateRowsProxyModel");
-    qmlRegisterType<KDescendantsProxyModel>(uri, 1, 0, 
"KDescendantsProxyModel");
+    qmlRegisterType<KDescendantsProxyModelQml>(uri, 1, 0, 
"KDescendantsProxyModel");
     qmlRegisterType<KNumberModel>(uri, 1, 0, "KNumberModel");
     qmlRegisterType<KColumnHeadersModel>(uri, 1, 0, "KColumnHeadersModel");
     qmlRegisterType<KSortFilterProxyModel>(uri, 1, 0, "KSortFilterProxyModel");
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/kitemmodels-5.74.0/tests/proxymodeltestapp/CMakeLists.txt 
new/kitemmodels-5.75.0/tests/proxymodeltestapp/CMakeLists.txt
--- old/kitemmodels-5.74.0/tests/proxymodeltestapp/CMakeLists.txt       
2020-09-06 11:23:39.000000000 +0200
+++ new/kitemmodels-5.75.0/tests/proxymodeltestapp/CMakeLists.txt       
2020-10-04 11:53:39.000000000 +0200
@@ -16,6 +16,7 @@
     lessthanwidget.cpp
     modelcommanderwidget.cpp
     matchcheckingwidget.cpp
+    descendantqmltree.cpp
 )
 if(NOT EXCLUDE_DEPRECATED_BEFORE_AND_AT STREQUAL "CURRENT" AND
    EXCLUDE_DEPRECATED_BEFORE_AND_AT VERSION_LESS 5.65.0)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/kitemmodels-5.74.0/tests/proxymodeltestapp/descendantqmltree.cpp 
new/kitemmodels-5.75.0/tests/proxymodeltestapp/descendantqmltree.cpp
--- old/kitemmodels-5.74.0/tests/proxymodeltestapp/descendantqmltree.cpp        
1970-01-01 01:00:00.000000000 +0100
+++ new/kitemmodels-5.75.0/tests/proxymodeltestapp/descendantqmltree.cpp        
2020-10-04 11:53:39.000000000 +0200
@@ -0,0 +1,40 @@
+/*
+    This file is part of the proxy model test suite.
+
+    SPDX-FileCopyrightText: 2020 Marco Martin <[email protected]>
+
+    SPDX-License-Identifier: LGPL-2.1-or-later
+*/
+
+#include "descendantqmltree.h"
+
+#include <QSplitter>
+#include <QTreeView>
+#include <QQuickWidget>
+#include <QHBoxLayout>
+#include <QLabel>
+#include <QtQml>
+
+#include "dynamictreemodel.h"
+#include "dynamictreewidget.h"
+#include "kselectionproxymodel.h"
+
+DescendantQmlTreeWidget::DescendantQmlTreeWidget(QWidget *parent): 
QWidget(parent)
+{
+    QHBoxLayout *layout = new QHBoxLayout(this);
+    QSplitter *splitter = new QSplitter(this);
+    layout->addWidget(splitter);
+
+    m_rootModel = new DynamicTreeModel(this);
+
+    new DynamicTreeWidget(m_rootModel, splitter);
+
+
+    qmlRegisterType<KSelectionProxyModel>("KF5ItemModels", 1, 0, 
"SelectionProxyModel");
+
+    QQuickWidget *quickView = new QQuickWidget(splitter);
+
+    
quickView->engine()->rootContext()->setContextProperty(QStringLiteral("_model"),
 m_rootModel);
+
+    quickView->setSource(QUrl::fromLocalFile(QLatin1String(SRC_DIR 
"/tree.qml")));
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/kitemmodels-5.74.0/tests/proxymodeltestapp/descendantqmltree.h 
new/kitemmodels-5.75.0/tests/proxymodeltestapp/descendantqmltree.h
--- old/kitemmodels-5.74.0/tests/proxymodeltestapp/descendantqmltree.h  
1970-01-01 01:00:00.000000000 +0100
+++ new/kitemmodels-5.75.0/tests/proxymodeltestapp/descendantqmltree.h  
2020-10-04 11:53:39.000000000 +0200
@@ -0,0 +1,27 @@
+/*
+    This file is part of the proxy model test suite.
+
+    SPDX-FileCopyrightText: 2020 Marco Martin <[email protected]>
+
+    SPDX-License-Identifier: LGPL-2.1-or-later
+*/
+
+#ifndef DESCENDANTQMLTREE_H
+#define DESCENDANTQMLTREE_H
+
+#include <QWidget>
+
+class QTreeView;
+
+class DynamicTreeModel;
+
+class DescendantQmlTreeWidget : public QWidget
+{
+public:
+    DescendantQmlTreeWidget(QWidget *parent = nullptr);
+
+private:
+    DynamicTreeModel *m_rootModel;
+};
+
+#endif
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/kitemmodels-5.74.0/tests/proxymodeltestapp/mainwindow.cpp 
new/kitemmodels-5.75.0/tests/proxymodeltestapp/mainwindow.cpp
--- old/kitemmodels-5.74.0/tests/proxymodeltestapp/mainwindow.cpp       
2020-09-06 11:23:39.000000000 +0200
+++ new/kitemmodels-5.75.0/tests/proxymodeltestapp/mainwindow.cpp       
2020-10-04 11:53:39.000000000 +0200
@@ -21,6 +21,7 @@
 // #include "statesaverwidget.h"
 #include "proxymodeltestwidget.h"
 #include "proxyitemselectionwidget.h"
+#include "descendantqmltree.h"
 #ifdef QT_SCRIPT_LIB
 #include "reparentingpmwidget.h"
 #endif
@@ -57,6 +58,7 @@
 #if KITEMMODELS_BUILD_DEPRECATED_SINCE(5, 65)
     tabWidget->addTab(new RecursiveFilterProxyWidget(), 
QStringLiteral("Recursive Filter"));
 #endif
+    tabWidget->addTab(new DescendantQmlTreeWidget(), QStringLiteral("QML 
Trees"));
     tabWidget->addTab(new LessThanWidget(), QStringLiteral("Less Than"));
     tabWidget->addTab(new ProxyModelTestWidget(), QStringLiteral("Proxy Model 
Test"));
 //   tabWidget->addTab(new StateSaverWidget(), "State Saver Test");
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kitemmodels-5.74.0/tests/proxymodeltestapp/tree.qml 
new/kitemmodels-5.75.0/tests/proxymodeltestapp/tree.qml
--- old/kitemmodels-5.74.0/tests/proxymodeltestapp/tree.qml     1970-01-01 
01:00:00.000000000 +0100
+++ new/kitemmodels-5.75.0/tests/proxymodeltestapp/tree.qml     2020-10-04 
11:53:39.000000000 +0200
@@ -0,0 +1,96 @@
+ /*
+    SPDX-FileCopyrightText: 2029 Marco Martin <[email protected]>
+
+    SPDX-License-Identifier: LGPL-2.0-or-later
+*/
+import QtQuick 2.6
+import QtQuick.Layouts 1.4
+import QtQuick.Controls 2.2 as QQC2
+import org.kde.kirigami 2.13 as Kirigami
+import org.kde.kitemmodels 1.0 
+import org.kde.qqc2desktopstyle.private 1.0 as StylePrivate
+
+
+QQC2.ScrollView {
+    id: root
+    width: 600
+    height: 500
+
+    ListView {
+        spacing: 0
+        clip: true
+        add: Transition {
+            NumberAnimation {
+                property: "opacity"; from: 0; to: 1; duration: 250 }
+        }
+        addDisplaced: Transition {
+            NumberAnimation { properties: "y"; duration: 250 }
+        }
+        remove: Transition {
+            NumberAnimation {
+                property: "opacity"; from: 1; to: 0; duration: 250 }
+        }
+        removeDisplaced: Transition {
+            NumberAnimation { properties: "y"; duration: 250 }
+        }
+        model: KDescendantsProxyModel {
+            id: descendantsModel
+            expandsByDefault: false
+            model: _model
+        }
+
+        delegate: Kirigami.AbstractListItem {
+            id: delegate
+            highlighted: false
+            separatorVisible: false
+
+            contentItem: RowLayout {
+            RowLayout {
+                    Layout.topMargin: -delegate.topPadding
+                    Layout.bottomMargin: -delegate.bottomPadding
+                    Repeater {
+                        
+                        model: kDescendantLevel-1
+                        
+                        delegate: StylePrivate.StyleItem {
+                            Layout.preferredWidth: 
controlRoot.width//Kirigami.Units.gridUnit
+                            Layout.fillHeight: true
+                            visible: true
+                        // control: controlRoot
+                            elementType: "itembranchindicator"
+                            properties: {
+                                "isItem": false,
+                                "hasSibling": kDescendantHasSiblings[modelData]
+                            }
+                        }
+                    }
+                    QQC2.Button {
+                        id: controlRoot
+                        Layout.preferredWidth: 
background.pixelMetric("treeviewindentation")
+                        Layout.fillHeight: true
+                        enabled: model.kDescendantExpandable
+                        text: model.kDescendantExpanded ? "-" : "+"
+                        onClicked: descendantsModel.toggleChildren(index)
+                        background: StylePrivate.StyleItem {
+                            id: styleitem
+                            control: controlRoot
+                            hover: controlRoot.hovered
+                            elementType: "itembranchindicator"
+                            on: model.kDescendantExpanded 
+                            properties: {
+                                "isItem": true,
+                                "hasChildren": model.kDescendantExpandable,
+                                "hasSibling": 
model.kDescendantHasSiblings[model.kDescendantHasSiblings.length - 1]
+                            }
+                        }
+                    }
+            }
+                
+                QQC2.Label {
+                    Layout.fillWidth: true
+                    text: model.display
+                }
+            }
+        }
+    }
+}


Reply via email to