Hello community,

here is the log from the commit of package kitemmodels for openSUSE:Factory 
checked in at 2015-09-24 07:12:26
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/kitemmodels (Old)
 and      /work/SRC/openSUSE:Factory/.kitemmodels.new (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "kitemmodels"

Changes:
--------
--- /work/SRC/openSUSE:Factory/kitemmodels/kitemmodels.changes  2015-09-02 
07:46:40.000000000 +0200
+++ /work/SRC/openSUSE:Factory/.kitemmodels.new/kitemmodels.changes     
2015-09-24 07:12:27.000000000 +0200
@@ -1,0 +2,13 @@
+Tue Sep  8 17:13:15 UTC 2015 - hrvoje.sen...@gmail.com
+
+- Update to 5.14.0
+  * New proxymodel: KConcatenateRowsProxyModel
+  * KConcatenateRowsProxyModelPrivate: fix handling of
+    layoutChanged.
+  * More checking on the selection after sorting.
+  * KExtraColumnsProxyModel: fix bug in sibling() which broke
+    e.g. selections
+  * For more details please see:
+    https://www.kde.org/announcements/kde-frameworks-5.14.0.php
+
+-------------------------------------------------------------------

Old:
----
  kitemmodels-5.13.0.tar.xz

New:
----
  kitemmodels-5.14.0.tar.xz

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

Other differences:
------------------
++++++ kitemmodels.spec ++++++
--- /var/tmp/diff_new_pack.hhXLO8/_old  2015-09-24 07:12:28.000000000 +0200
+++ /var/tmp/diff_new_pack.hhXLO8/_new  2015-09-24 07:12:28.000000000 +0200
@@ -17,9 +17,9 @@
 
 
 %define lname   libKF5ItemModels5
-%define _tar_path 5.13
+%define _tar_path 5.14
 Name:           kitemmodels
-Version:        5.13.0
+Version:        5.14.0
 Release:        0
 BuildRequires:  cmake >= 2.8.12
 BuildRequires:  extra-cmake-modules >= %{_tar_path}

++++++ kitemmodels-5.13.0.tar.xz -> kitemmodels-5.14.0.tar.xz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kitemmodels-5.13.0/CMakeLists.txt 
new/kitemmodels-5.14.0/CMakeLists.txt
--- old/kitemmodels-5.13.0/CMakeLists.txt       2015-08-01 14:41:18.000000000 
+0200
+++ new/kitemmodels-5.14.0/CMakeLists.txt       2015-09-04 22:14:38.000000000 
+0200
@@ -3,7 +3,7 @@
 project(KItemModels)
 
 include(FeatureSummary)
-find_package(ECM 5.13.0  NO_MODULE)
+find_package(ECM 5.14.0  NO_MODULE)
 set_package_properties(ECM PROPERTIES TYPE REQUIRED DESCRIPTION "Extra CMake 
Modules." URL 
"https://projects.kde.org/projects/kdesupport/extra-cmake-modules";)
 feature_summary(WHAT REQUIRED_PACKAGES_NOT_FOUND 
FATAL_ON_MISSING_REQUIRED_PACKAGES)
 
@@ -23,7 +23,7 @@
 include(ECMSetupVersion)
 include(ECMGenerateHeaders)
 
-set(KF5_VERSION "5.13.0") # handled by release scripts
+set(KF5_VERSION "5.14.0") # handled by release scripts
 
 ecm_setup_version(${KF5_VERSION} VARIABLE_PREFIX KITEMMODELS
                         VERSION_HEADER 
"${CMAKE_CURRENT_BINARY_DIR}/kitemmodels_version.h"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kitemmodels-5.13.0/README.md 
new/kitemmodels-5.14.0/README.md
--- old/kitemmodels-5.13.0/README.md    2015-08-01 14:41:18.000000000 +0200
+++ new/kitemmodels-5.14.0/README.md    2015-09-04 22:14:38.000000000 +0200
@@ -9,6 +9,7 @@
 * KBreadcrumbSelectionModel - Selects the parents of selected items to create
   breadcrumbs
 * KCheckableProxyModel - Adds a checkable capability to a source model
+* KConcatenateRowsProxyModel - Concatenates rows from multiple source models
 * KDescendantsProxyModel - Proxy Model for restructuring a Tree into a list
 * KExtraColumnsProxyModel - Adds columns after existing columns
 * KLinkItemSelectionModel - Share a selection in multiple views which do not
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kitemmodels-5.13.0/autotests/CMakeLists.txt 
new/kitemmodels-5.14.0/autotests/CMakeLists.txt
--- old/kitemmodels-5.13.0/autotests/CMakeLists.txt     2015-08-01 
14:41:18.000000000 +0200
+++ new/kitemmodels-5.14.0/autotests/CMakeLists.txt     2015-09-04 
22:14:38.000000000 +0200
@@ -15,6 +15,7 @@
 include(ECMAddTests)
 
 ecm_add_tests(
+  kconcatenaterowsproxymodeltest.cpp
   kdescendantsproxymodel_smoketest.cpp
   kextracolumnsproxymodeltest.cpp
   klinkitemselectionmodeltest.cpp
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/kitemmodels-5.13.0/autotests/kconcatenaterowsproxymodeltest.cpp 
new/kitemmodels-5.14.0/autotests/kconcatenaterowsproxymodeltest.cpp
--- old/kitemmodels-5.13.0/autotests/kconcatenaterowsproxymodeltest.cpp 
1970-01-01 01:00:00.000000000 +0100
+++ new/kitemmodels-5.14.0/autotests/kconcatenaterowsproxymodeltest.cpp 
2015-09-04 22:14:38.000000000 +0200
@@ -0,0 +1,425 @@
+#include <QSignalSpy>
+#include <QSortFilterProxyModel>
+#include <QTest>
+#include <QStandardItemModel>
+#include <QIdentityProxyModel>
+#include <QItemSelectionModel>
+
+#include <kconcatenaterowsproxymodel.h>
+#include "test_model_helpers.h"
+using namespace TestModelHelpers;
+
+Q_DECLARE_METATYPE(QModelIndex)
+
+class tst_KConcatenateRowsProxyModel : public QObject
+{
+    Q_OBJECT
+
+private Q_SLOTS:
+
+    void initTestCase()
+    {
+    }
+
+    void init()
+    {
+        // Prepare some source models to use later on
+        mod.clear();
+        mod.insertRow(0, makeStandardItems(QStringList() << "A" << "B" << 
"C"));
+        mod.setHorizontalHeaderLabels(QStringList() << "H1" << "H2" << "H3");
+        mod.setVerticalHeaderLabels(QStringList() << "One");
+
+        mod2.clear();
+        mod2.insertRow(0, makeStandardItems(QStringList() << "D" << "E" << 
"F"));
+        mod2.setHorizontalHeaderLabels(QStringList() << "H1" << "H2" << "H3");
+        mod2.setVerticalHeaderLabels(QStringList() << "Two");
+    }
+
+    void shouldAggregateTwoModelsCorrectly()
+    {
+        // Given a combining proxy
+        KConcatenateRowsProxyModel pm;
+
+        // When adding two source models
+        pm.addSourceModel(&mod);
+        pm.addSourceModel(&mod2);
+
+        // Then the proxy should show 2 rows
+        QCOMPARE(pm.rowCount(), 2);
+        QCOMPARE(extractRowTexts(&pm, 0), QString("ABC"));
+        QCOMPARE(extractRowTexts(&pm, 1), QString("DEF"));
+
+        // ... and correct headers
+        QCOMPARE(pm.headerData(0, Qt::Horizontal).toString(), QString("H1"));
+        QCOMPARE(pm.headerData(1, Qt::Horizontal).toString(), QString("H2"));
+        QCOMPARE(pm.headerData(2, Qt::Horizontal).toString(), QString("H3"));
+        QCOMPARE(pm.headerData(0, Qt::Vertical).toString(), QString("One"));
+        QCOMPARE(pm.headerData(1, Qt::Vertical).toString(), QString("Two"));
+
+        QVERIFY(!pm.canFetchMore(QModelIndex()));
+    }
+
+    void shouldAggregateThenRemoveTwoEmptyModelsCorrectly()
+    {
+        // Given a combining proxy
+        KConcatenateRowsProxyModel pm;
+
+        // When adding two empty models
+        QSignalSpy rowATBISpy(&pm, 
SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)));
+        QSignalSpy rowInsertedSpy(&pm, 
SIGNAL(rowsInserted(QModelIndex,int,int)));
+        QSignalSpy rowATBRSpy(&pm, 
SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)));
+        QSignalSpy rowRemovedSpy(&pm, 
SIGNAL(rowsRemoved(QModelIndex,int,int)));
+        QIdentityProxyModel i1, i2;
+        pm.addSourceModel(&i1);
+        pm.addSourceModel(&i2);
+
+        // Then the proxy should still be empty (and no signals emitted)
+        QCOMPARE(pm.rowCount(), 0);
+        QCOMPARE(pm.columnCount(), 0);
+        QCOMPARE(rowATBISpy.count(), 0);
+        QCOMPARE(rowInsertedSpy.count(), 0);
+
+        // When removing the empty models
+        pm.removeSourceModel(&i1);
+        pm.removeSourceModel(&i2);
+
+        // Then the proxy should still be empty (and no signals emitted)
+        QCOMPARE(pm.rowCount(), 0);
+        QCOMPARE(pm.columnCount(), 0);
+        QCOMPARE(rowATBRSpy.count(), 0);
+        QCOMPARE(rowRemovedSpy.count(), 0);
+    }
+
+    void shouldAggregateTwoEmptyModelsWhichThenGetFilled()
+    {
+        // Given a combining proxy
+        KConcatenateRowsProxyModel pm;
+
+        // When adding two empty models
+        QIdentityProxyModel i1, i2;
+        pm.addSourceModel(&i1);
+        pm.addSourceModel(&i2);
+
+        QCOMPARE(pm.rowCount(), 0);
+        QCOMPARE(pm.columnCount(), 0);
+
+        i1.setSourceModel(&mod);
+        i2.setSourceModel(&mod2);
+
+        // Then the proxy should show 2 rows
+        QCOMPARE(pm.rowCount(), 2);
+        QCOMPARE(extractRowTexts(&pm, 0), QString("ABC"));
+        QCOMPARE(extractRowTexts(&pm, 1), QString("DEF"));
+
+        // ... and correct headers
+        QCOMPARE(pm.headerData(0, Qt::Horizontal).toString(), QString("H1"));
+        QCOMPARE(pm.headerData(1, Qt::Horizontal).toString(), QString("H2"));
+        QCOMPARE(pm.headerData(2, Qt::Horizontal).toString(), QString("H3"));
+        QCOMPARE(pm.headerData(0, Qt::Vertical).toString(), QString("One"));
+        QCOMPARE(pm.headerData(1, Qt::Vertical).toString(), QString("Two"));
+
+        QVERIFY(!pm.canFetchMore(QModelIndex()));
+    }
+
+    void shouldHandleDataChanged()
+    {
+        // Given two models combined
+        KConcatenateRowsProxyModel pm;
+        pm.addSourceModel(&mod);
+        pm.addSourceModel(&mod2);
+        QSignalSpy dataChangedSpy(&pm, 
SIGNAL(dataChanged(QModelIndex,QModelIndex)));
+
+        // When a cell in a source model changes
+        mod.item(0, 0)->setData("a", Qt::EditRole);
+
+        // Then the change should be notified to the proxy
+        QCOMPARE(dataChangedSpy.count(), 1);
+        QCOMPARE(dataChangedSpy.at(0).at(0).value<QModelIndex>(), pm.index(0, 
0));
+        QCOMPARE(extractRowTexts(&pm, 0), QString("aBC"));
+
+        // Same test with the other model
+        mod2.item(0, 2)->setData("f", Qt::EditRole);
+
+        QCOMPARE(dataChangedSpy.count(), 2);
+        QCOMPARE(dataChangedSpy.at(1).at(0).value<QModelIndex>(), pm.index(1, 
2));
+        QCOMPARE(extractRowTexts(&pm, 1), QString("DEf"));
+    }
+
+    void shouldHandleSetData()
+    {
+        // Given two models combined
+        KConcatenateRowsProxyModel pm;
+        pm.addSourceModel(&mod);
+        pm.addSourceModel(&mod2);
+        QSignalSpy dataChangedSpy(&pm, 
SIGNAL(dataChanged(QModelIndex,QModelIndex)));
+
+        // When changing a cell using setData
+        pm.setData(pm.index(0, 0), "a", Qt::EditRole);
+
+        // Then the change should be notified to the proxy
+        QCOMPARE(dataChangedSpy.count(), 1);
+        QCOMPARE(dataChangedSpy.at(0).at(0).value<QModelIndex>(), pm.index(0, 
0));
+        QCOMPARE(extractRowTexts(&pm, 0), QString("aBC"));
+
+        // Same test with the other model
+        pm.setData(pm.index(1, 2), "f", Qt::EditRole);
+
+        QCOMPARE(dataChangedSpy.count(), 2);
+        QCOMPARE(dataChangedSpy.at(1).at(0).value<QModelIndex>(), pm.index(1, 
2));
+        QCOMPARE(extractRowTexts(&pm, 1), QString("DEf"));
+    }
+
+    void shouldHandleRowInsertionAndRemoval()
+    {
+        // Given two models combined
+        KConcatenateRowsProxyModel pm;
+        pm.addSourceModel(&mod);
+        pm.addSourceModel(&mod2);
+        QSignalSpy rowATBISpy(&pm, 
SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)));
+        QSignalSpy rowInsertedSpy(&pm, 
SIGNAL(rowsInserted(QModelIndex,int,int)));
+
+        // When a source model inserts a new row
+        QList<QStandardItem *> row;
+        row.append(new QStandardItem("1"));
+        row.append(new QStandardItem("2"));
+        row.append(new QStandardItem("3"));
+        mod2.insertRow(0, row);
+
+        // Then the proxy should notify its users and show changes
+        QCOMPARE(rowSpyToText(rowATBISpy), QString("1,1"));
+        QCOMPARE(rowSpyToText(rowInsertedSpy), QString("1,1"));
+        QCOMPARE(pm.rowCount(), 3);
+        QCOMPARE(extractRowTexts(&pm, 0), QString("ABC"));
+        QCOMPARE(extractRowTexts(&pm, 1), QString("123"));
+        QCOMPARE(extractRowTexts(&pm, 2), QString("DEF"));
+
+        // When removing that row
+        QSignalSpy rowATBRSpy(&pm, 
SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)));
+        QSignalSpy rowRemovedSpy(&pm, 
SIGNAL(rowsRemoved(QModelIndex,int,int)));
+        mod2.removeRow(0);
+
+        // Then the proxy should notify its users and show changes
+        QCOMPARE(rowATBRSpy.count(), 1);
+        QCOMPARE(rowATBRSpy.at(0).at(1).toInt(), 1);
+        QCOMPARE(rowATBRSpy.at(0).at(2).toInt(), 1);
+        QCOMPARE(rowRemovedSpy.count(), 1);
+        QCOMPARE(rowRemovedSpy.at(0).at(1).toInt(), 1);
+        QCOMPARE(rowRemovedSpy.at(0).at(2).toInt(), 1);
+        QCOMPARE(pm.rowCount(), 2);
+        QCOMPARE(extractRowTexts(&pm, 0), QString("ABC"));
+        QCOMPARE(extractRowTexts(&pm, 1), QString("DEF"));
+
+        // When removing the last row from mod2
+        rowATBRSpy.clear();
+        rowRemovedSpy.clear();
+        mod2.removeRow(0);
+
+        // Then the proxy should notify its users and show changes
+        QCOMPARE(rowATBRSpy.count(), 1);
+        QCOMPARE(rowATBRSpy.at(0).at(1).toInt(), 1);
+        QCOMPARE(rowATBRSpy.at(0).at(2).toInt(), 1);
+        QCOMPARE(rowRemovedSpy.count(), 1);
+        QCOMPARE(rowRemovedSpy.at(0).at(1).toInt(), 1);
+        QCOMPARE(rowRemovedSpy.at(0).at(2).toInt(), 1);
+        QCOMPARE(pm.rowCount(), 1);
+        QCOMPARE(extractRowTexts(&pm, 0), QString("ABC"));
+    }
+
+    void shouldAggregateAnotherModelThenRemoveModels()
+    {
+        // Given two models combined, and a third model
+        KConcatenateRowsProxyModel pm;
+        pm.addSourceModel(&mod);
+        pm.addSourceModel(&mod2);
+
+        QStandardItemModel mod3;
+        mod3.appendRow(makeStandardItems(QStringList() << "1" << "2" << "3"));
+        mod3.appendRow(makeStandardItems(QStringList() << "4" << "5" << "6"));
+
+        QSignalSpy rowATBISpy(&pm, 
SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)));
+        QSignalSpy rowInsertedSpy(&pm, 
SIGNAL(rowsInserted(QModelIndex,int,int)));
+
+        // When adding the new source model
+        pm.addSourceModel(&mod3);
+
+        // Then the proxy should notify its users about the two rows inserted
+        QCOMPARE(rowSpyToText(rowATBISpy), QString("2,3"));
+        QCOMPARE(rowSpyToText(rowInsertedSpy), QString("2,3"));
+        QCOMPARE(pm.rowCount(), 4);
+        QCOMPARE(extractRowTexts(&pm, 0), QString("ABC"));
+        QCOMPARE(extractRowTexts(&pm, 1), QString("DEF"));
+        QCOMPARE(extractRowTexts(&pm, 2), QString("123"));
+        QCOMPARE(extractRowTexts(&pm, 3), QString("456"));
+
+        // When removing that source model again
+        QSignalSpy rowATBRSpy(&pm, 
SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)));
+        QSignalSpy rowRemovedSpy(&pm, 
SIGNAL(rowsRemoved(QModelIndex,int,int)));
+        pm.removeSourceModel(&mod3);
+
+        // Then the proxy should notify its users about the row removed
+        QCOMPARE(rowATBRSpy.count(), 1);
+        QCOMPARE(rowATBRSpy.at(0).at(1).toInt(), 2);
+        QCOMPARE(rowATBRSpy.at(0).at(2).toInt(), 3);
+        QCOMPARE(rowRemovedSpy.count(), 1);
+        QCOMPARE(rowRemovedSpy.at(0).at(1).toInt(), 2);
+        QCOMPARE(rowRemovedSpy.at(0).at(2).toInt(), 3);
+        QCOMPARE(pm.rowCount(), 2);
+        QCOMPARE(extractRowTexts(&pm, 0), QString("ABC"));
+        QCOMPARE(extractRowTexts(&pm, 1), QString("DEF"));
+
+        // When removing model 2
+        rowATBRSpy.clear();
+        rowRemovedSpy.clear();
+        pm.removeSourceModel(&mod2);
+        QCOMPARE(rowATBRSpy.count(), 1);
+        QCOMPARE(rowATBRSpy.at(0).at(1).toInt(), 1);
+        QCOMPARE(rowATBRSpy.at(0).at(2).toInt(), 1);
+        QCOMPARE(rowRemovedSpy.count(), 1);
+        QCOMPARE(rowRemovedSpy.at(0).at(1).toInt(), 1);
+        QCOMPARE(rowRemovedSpy.at(0).at(2).toInt(), 1);
+        QCOMPARE(pm.rowCount(), 1);
+        QCOMPARE(extractRowTexts(&pm, 0), QString("ABC"));
+
+        // When removing model 1
+        rowATBRSpy.clear();
+        rowRemovedSpy.clear();
+        pm.removeSourceModel(&mod);
+        QCOMPARE(rowATBRSpy.count(), 1);
+        QCOMPARE(rowATBRSpy.at(0).at(1).toInt(), 0);
+        QCOMPARE(rowATBRSpy.at(0).at(2).toInt(), 0);
+        QCOMPARE(rowRemovedSpy.count(), 1);
+        QCOMPARE(rowRemovedSpy.at(0).at(1).toInt(), 0);
+        QCOMPARE(rowRemovedSpy.at(0).at(2).toInt(), 0);
+        QCOMPARE(pm.rowCount(), 0);
+    }
+
+    void shouldHandleColumnInsertionAndRemoval()
+    {
+        // Given two models combined
+        KConcatenateRowsProxyModel pm;
+        pm.addSourceModel(&mod);
+        pm.addSourceModel(&mod2);
+        QSignalSpy colATBISpy(&pm, 
SIGNAL(columnsAboutToBeInserted(QModelIndex,int,int)));
+        QSignalSpy colInsertedSpy(&pm, 
SIGNAL(columnsInserted(QModelIndex,int,int)));
+
+        // When the first source model inserts a new column
+        QCOMPARE(mod.columnCount(), 3);
+        mod.setColumnCount(4);
+
+        // Then the proxy should notify its users and show changes
+        QCOMPARE(rowSpyToText(colATBISpy), QString("3,3"));
+        QCOMPARE(rowSpyToText(colInsertedSpy), QString("3,3"));
+        QCOMPARE(pm.rowCount(), 2);
+        QCOMPARE(pm.columnCount(), 4);
+        QCOMPARE(extractRowTexts(&pm, 0), QString("ABC "));
+        QCOMPARE(extractRowTexts(&pm, 1), QString("DEF "));
+    }
+
+    void shouldPropagateLayoutChanged()
+    {
+        // Given two source models, the second one being a QSFPM
+        KConcatenateRowsProxyModel pm;
+        pm.addSourceModel(&mod);
+
+        QStandardItemModel mod3;
+        QList<QStandardItem *> row;
+        row.append(new QStandardItem("1"));
+        row.append(new QStandardItem("2"));
+        row.append(new QStandardItem("3"));
+        mod3.insertRow(0, row);
+        row.clear();
+        row.append(new QStandardItem("4"));
+        row.append(new QStandardItem("5"));
+        row.append(new QStandardItem("6"));
+        mod3.appendRow(row);
+
+        QSortFilterProxyModel qsfpm;
+        qsfpm.setSourceModel(&mod3);
+        pm.addSourceModel(&qsfpm);
+
+        QCOMPARE(extractRowTexts(&pm, 0), QString("ABC"));
+        QCOMPARE(extractRowTexts(&pm, 1), QString("123"));
+        QCOMPARE(extractRowTexts(&pm, 2), QString("456"));
+
+        // And a selection (row 1)
+        QItemSelectionModel selection(&pm);
+        selection.select(pm.index(1, 0), QItemSelectionModel::Select | 
QItemSelectionModel::Rows);
+        const QModelIndexList lst = selection.selectedIndexes();
+        QCOMPARE(lst.count(), 3);
+        for (int col = 0; col < lst.count(); ++col) {
+            QCOMPARE(lst.at(col).row(), 1);
+            QCOMPARE(lst.at(col).column(), col);
+        }
+
+        QSignalSpy layoutATBCSpy(&pm, SIGNAL(layoutAboutToBeChanged()));
+        QSignalSpy layoutChangedSpy(&pm, SIGNAL(layoutChanged()));
+
+        // When changing the sorting in the QSFPM
+        qsfpm.sort(0, Qt::DescendingOrder);
+
+        // Then the proxy should emit the layoutChanged signals, and show 
re-sorted data
+        QCOMPARE(extractRowTexts(&pm, 0), QString("ABC"));
+        QCOMPARE(extractRowTexts(&pm, 1), QString("456"));
+        QCOMPARE(extractRowTexts(&pm, 2), QString("123"));
+        QCOMPARE(layoutATBCSpy.count(), 1);
+        QCOMPARE(layoutChangedSpy.count(), 1);
+
+        // And the selection should be updated accordingly (it became row 2)
+        const QModelIndexList lstAfter = selection.selectedIndexes();
+        QCOMPARE(lstAfter.count(), 3);
+        for (int col = 0; col < lstAfter.count(); ++col) {
+            QCOMPARE(lstAfter.at(col).row(), 2);
+            QCOMPARE(lstAfter.at(col).column(), col);
+        }
+    }
+
+    void shouldReactToModelReset()
+    {
+        // Given two source models, the second one being a QSFPM
+        KConcatenateRowsProxyModel pm;
+        pm.addSourceModel(&mod);
+
+        QStandardItemModel mod3;
+        QList<QStandardItem *> row;
+        row.append(new QStandardItem("1"));
+        row.append(new QStandardItem("2"));
+        row.append(new QStandardItem("3"));
+        mod3.insertRow(0, row);
+        row.clear();
+        row.append(new QStandardItem("4"));
+        row.append(new QStandardItem("5"));
+        row.append(new QStandardItem("6"));
+        mod3.appendRow(row);
+
+        QSortFilterProxyModel qsfpm;
+        qsfpm.setSourceModel(&mod3);
+        pm.addSourceModel(&qsfpm);
+
+        QCOMPARE(extractRowTexts(&pm, 0), QString("ABC"));
+        QCOMPARE(extractRowTexts(&pm, 1), QString("123"));
+        QCOMPARE(extractRowTexts(&pm, 2), QString("456"));
+        QSignalSpy rowATBRSpy(&pm, 
SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)));
+        QSignalSpy rowRemovedSpy(&pm, 
SIGNAL(rowsRemoved(QModelIndex,int,int)));
+        QSignalSpy rowATBISpy(&pm, 
SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)));
+        QSignalSpy rowInsertedSpy(&pm, 
SIGNAL(rowsInserted(QModelIndex,int,int)));
+
+        // When changing the source model of the QSFPM
+        qsfpm.setSourceModel(&mod2);
+
+        // Then the proxy should emit the row removed/inserted signals, and 
show the new data
+        QCOMPARE(extractRowTexts(&pm, 0), QString("ABC"));
+        QCOMPARE(extractRowTexts(&pm, 1), QString("DEF"));
+        QCOMPARE(rowSpyToText(rowATBRSpy), QString("1,2"));
+        QCOMPARE(rowSpyToText(rowRemovedSpy), QString("1,2"));
+        QCOMPARE(rowSpyToText(rowATBISpy), QString("1,1"));
+        QCOMPARE(rowSpyToText(rowInsertedSpy), QString("1,1"));
+    }
+
+private:
+    QStandardItemModel mod;
+    QStandardItemModel mod2;
+};
+
+QTEST_MAIN(tst_KConcatenateRowsProxyModel)
+
+#include "kconcatenaterowsproxymodeltest.moc"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/kitemmodels-5.13.0/autotests/kextracolumnsproxymodeltest.cpp 
new/kitemmodels-5.14.0/autotests/kextracolumnsproxymodeltest.cpp
--- old/kitemmodels-5.13.0/autotests/kextracolumnsproxymodeltest.cpp    
2015-08-01 14:41:18.000000000 +0200
+++ new/kitemmodels-5.14.0/autotests/kextracolumnsproxymodeltest.cpp    
2015-09-04 22:14:38.000000000 +0200
@@ -76,7 +76,8 @@
             }
             return KExtraColumnsProxyModel::setExtraColumnData(parent, row, 
extraColumn, data, role);
         }
-        void changeExtraColumnData() {
+        void changeExtraColumnData()
+        {
             m_extraColumnData = '<';
             extraColumnDataChanged(QModelIndex(), 0, 0, QVector<int>() << 
Qt::EditRole);
         }
@@ -169,6 +170,9 @@
         QVERIFY(indexToText(pm.index(0, 5)).startsWith("0,5,"));
         QCOMPARE(indexToText(pm.index(0, 5, secondParent).parent()), 
indexToText(secondParent));
 
+        QCOMPARE(pm.index(0, 0).sibling(0, 4).column(), 4);
+        QCOMPARE(pm.index(0, 4).sibling(0, 1).column(), 1);
+
         QVERIFY(!pm.canFetchMore(QModelIndex()));
     }
 
@@ -303,15 +307,33 @@
         // And a QSFPM underneath
         QSortFilterProxyModel proxy;
         proxy.setSourceModel(&mod);
+        QCOMPARE(proxy.columnCount(), 4);
         pm.setSourceModel(&proxy);
+        QCOMPARE(pm.columnCount(), 6);
+        QCOMPARE(extractRowTexts(&pm, 0), QString("ABCDZ0"));
         // And a selection
         QItemSelectionModel selection(&pm);
         selection.select(pm.index(0, 0), QItemSelectionModel::Select | 
QItemSelectionModel::Rows);
+        const QModelIndexList lst = selection.selectedIndexes();
+        QCOMPARE(lst.count(), 6);
+        for (int col = 0; col < lst.count(); ++col) {
+            QCOMPARE(lst.at(col).row(), 0);
+            QCOMPARE(lst.at(col).column(), col);
+        }
 
         // When sorting
         pm.sort(0, Qt::DescendingOrder);
+
+        // Then the proxy should be sorted
         QCOMPARE(extractRowTexts(&pm, 0), QString("EFGHZ0"));
         QCOMPARE(extractRowTexts(&pm, 1), QString("ABCDZ1"));
+        // And the selection should be updated accordingly
+        const QModelIndexList lstAfter = selection.selectedIndexes();
+        QCOMPARE(lstAfter.count(), 6);
+        for (int col = 0; col < lstAfter.count(); ++col) {
+            QCOMPARE(lstAfter.at(col).row(), 1);
+            QCOMPARE(lstAfter.at(col).column(), col);
+        }
     }
 
 private:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kitemmodels-5.13.0/src/CMakeLists.txt 
new/kitemmodels-5.14.0/src/CMakeLists.txt
--- old/kitemmodels-5.13.0/src/CMakeLists.txt   2015-08-01 14:41:18.000000000 
+0200
+++ new/kitemmodels-5.14.0/src/CMakeLists.txt   2015-09-04 22:14:38.000000000 
+0200
@@ -2,6 +2,7 @@
 set(kitemmodels_SRCS
   kbreadcrumbselectionmodel.cpp
   kcheckableproxymodel.cpp
+  kconcatenaterowsproxymodel.cpp
   kdescendantsproxymodel.cpp
   kextracolumnsproxymodel.cpp
   klinkitemselectionmodel.cpp
@@ -27,6 +28,7 @@
 ecm_generate_headers(KItemModels_HEADERS
   HEADER_NAMES
   KBreadcrumbSelectionModel
+  KConcatenateRowsProxyModel
   KCheckableProxyModel
   KExtraColumnsProxyModel
   KLinkItemSelectionModel
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/kitemmodels-5.13.0/src/kconcatenaterowsproxymodel.cpp 
new/kitemmodels-5.14.0/src/kconcatenaterowsproxymodel.cpp
--- old/kitemmodels-5.13.0/src/kconcatenaterowsproxymodel.cpp   1970-01-01 
01:00:00.000000000 +0100
+++ new/kitemmodels-5.14.0/src/kconcatenaterowsproxymodel.cpp   2015-09-04 
22:14:38.000000000 +0200
@@ -0,0 +1,413 @@
+/*
+    Copyright (c) 2015 Klarälvdalens Datakonsult AB, a KDAB Group company, 
i...@kdab.com
+    Authors: David Faure <david.fa...@kdab.com>
+
+    This library is free software; you can redistribute it and/or modify it
+    under the terms of the GNU Library General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or (at your
+    option) any later version.
+
+    This library is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
+    License for more details.
+
+    You should have received a copy of the GNU Library General Public License
+    along with this library; see the file COPYING.LIB.  If not, write to the
+    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+    02110-1301, USA.
+*/
+
+#include "kconcatenaterowsproxymodel.h"
+
+class KConcatenateRowsProxyModelPrivate
+{
+public:
+    KConcatenateRowsProxyModelPrivate(KConcatenateRowsProxyModel* model)
+        : q(model),
+          m_rowCount(0)
+    {}
+
+    int computeRowsPrior(const QAbstractItemModel *sourceModel) const;
+    QAbstractItemModel *sourceModelForRow(int row, int *sourceRow) const;
+
+    void slotRowsAboutToBeInserted(const QModelIndex &, int start, int end);
+    void slotRowsInserted(const QModelIndex &, int start, int end);
+    void slotRowsAboutToBeRemoved(const QModelIndex &, int start, int end);
+    void slotRowsRemoved(const QModelIndex &, int start, int end);
+    void slotColumnsAboutToBeInserted(const QModelIndex &parent, int start, 
int end);
+    void slotColumnsInserted(const QModelIndex &parent, int, int);
+    void slotColumnsAboutToBeRemoved(const QModelIndex &parent, int start, int 
end);
+    void slotColumnsRemoved(const QModelIndex &parent, int, int);
+    void slotDataChanged(const QModelIndex &from, const QModelIndex &to, const 
QVector<int> &roles);
+    void slotSourceLayoutAboutToBeChanged(const QList<QPersistentModelIndex> 
&sourceParents, QAbstractItemModel::LayoutChangeHint hint);
+    void slotSourceLayoutChanged(const QList<QPersistentModelIndex> 
&sourceParents, QAbstractItemModel::LayoutChangeHint hint);
+    void slotModelAboutToBeReset();
+    void slotModelReset();
+
+    KConcatenateRowsProxyModel *q;
+    QList<QAbstractItemModel *> m_models;
+    int m_rowCount; // have to maintain it here since we can't compute during 
model destruction
+
+    // for layoutAboutToBeChanged/layoutChanged
+    QVector<QPersistentModelIndex> layoutChangePersistentIndexes;
+    QModelIndexList proxyIndexes;
+};
+
+KConcatenateRowsProxyModel::KConcatenateRowsProxyModel(QObject *parent)
+    : QAbstractItemModel(parent),
+      d(new KConcatenateRowsProxyModelPrivate(this))
+{
+}
+
+KConcatenateRowsProxyModel::~KConcatenateRowsProxyModel()
+{
+}
+
+QModelIndex KConcatenateRowsProxyModel::mapFromSource(const QModelIndex 
&sourceIndex) const
+{
+    const QAbstractItemModel *sourceModel = sourceIndex.model();
+    int rowsPrior = d->computeRowsPrior(sourceModel);
+    return createIndex(rowsPrior + sourceIndex.row(), sourceIndex.column());
+}
+
+QModelIndex KConcatenateRowsProxyModel::mapToSource(const QModelIndex 
&proxyIndex) const
+{
+    if (!proxyIndex.isValid()) {
+        return QModelIndex();
+    }
+    const int row = proxyIndex.row();
+    int sourceRow;
+    QAbstractItemModel *sourceModel = d->sourceModelForRow(row, &sourceRow);
+    if (!sourceModel) {
+        return QModelIndex();
+    }
+    return sourceModel->index(sourceRow, proxyIndex.column());
+}
+
+QVariant KConcatenateRowsProxyModel::data(const QModelIndex &index, int role) 
const
+{
+    const QModelIndex sourceIndex = mapToSource(index);
+    if (!sourceIndex.isValid()) {
+        return QVariant();
+    }
+    return sourceIndex.model()->data(sourceIndex, role);
+}
+
+bool KConcatenateRowsProxyModel::setData(const QModelIndex &index, const 
QVariant &value, int role)
+{
+    const QModelIndex sourceIndex = mapToSource(index);
+    if (!sourceIndex.isValid()) {
+        return false;
+    }
+    QAbstractItemModel *sourceModel = const_cast<QAbstractItemModel 
*>(sourceIndex.model());
+    return sourceModel->setData(sourceIndex, value, role);
+}
+
+QMap<int, QVariant> KConcatenateRowsProxyModel::itemData(const QModelIndex 
&proxyIndex) const
+{
+    const QModelIndex sourceIndex = mapToSource(proxyIndex);
+    return sourceIndex.model()->itemData(sourceIndex);
+}
+
+Qt::ItemFlags KConcatenateRowsProxyModel::flags(const QModelIndex &index) const
+{
+    const QModelIndex sourceIndex = mapToSource(index);
+    return sourceIndex.model()->flags(sourceIndex);
+}
+
+QVariant KConcatenateRowsProxyModel::headerData(int section, Qt::Orientation 
orientation, int role) const
+{
+    if (d->m_models.isEmpty()) {
+        return QVariant();
+    }
+    if (orientation == Qt::Horizontal) {
+        return d->m_models.at(0)->headerData(section, orientation, role);
+    } else {
+        int sourceRow;
+        QAbstractItemModel *sourceModel = d->sourceModelForRow(section, 
&sourceRow);
+        if (!sourceModel) {
+            return QVariant();
+        }
+        return sourceModel->headerData(sourceRow, orientation, role);
+    }
+}
+
+int KConcatenateRowsProxyModel::columnCount(const QModelIndex &parent) const
+{
+    if (d->m_models.isEmpty()) {
+        return 0;
+    }
+    if (parent.isValid()) {
+        return 0; // flat model;
+    }
+    return d->m_models.at(0)->columnCount(QModelIndex());
+}
+
+QModelIndex KConcatenateRowsProxyModel::index(int row, int column, const 
QModelIndex &parent) const
+{
+    Q_ASSERT(row >= 0);
+    Q_ASSERT(column >= 0);
+    int sourceRow;
+    QAbstractItemModel *sourceModel = d->sourceModelForRow(row, &sourceRow);
+    if (!sourceModel) {
+        return QModelIndex();
+    }
+    return mapFromSource(sourceModel->index(sourceRow, column, parent));
+}
+
+QModelIndex KConcatenateRowsProxyModel::parent(const QModelIndex &) const
+{
+    return QModelIndex(); // we are flat, no hierarchy
+}
+
+int KConcatenateRowsProxyModel::rowCount(const QModelIndex &parent) const
+{
+    if (parent.isValid()) {
+        return 0;    // flat model
+    }
+
+    return d->m_rowCount;
+}
+
+void KConcatenateRowsProxyModel::addSourceModel(QAbstractItemModel 
*sourceModel)
+{
+    Q_ASSERT(sourceModel);
+    Q_ASSERT(!d->m_models.contains(sourceModel));
+    connect(sourceModel, 
SIGNAL(dataChanged(QModelIndex,QModelIndex,QVector<int>)), this, 
SLOT(slotDataChanged(QModelIndex,QModelIndex,QVector<int>)));
+    connect(sourceModel, SIGNAL(rowsInserted(QModelIndex,int,int)), this, 
SLOT(slotRowsInserted(QModelIndex,int,int)));
+    connect(sourceModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), this, 
SLOT(slotRowsRemoved(QModelIndex,int,int)));
+    connect(sourceModel, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)), 
this, SLOT(slotRowsAboutToBeInserted(QModelIndex,int,int)));
+    connect(sourceModel, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), 
this, SLOT(slotRowsAboutToBeRemoved(QModelIndex,int,int)));
+
+    connect(sourceModel, SIGNAL(columnsInserted(QModelIndex,int,int)), this, 
SLOT(slotColumnsInserted(QModelIndex,int,int)));
+    connect(sourceModel, SIGNAL(columnsRemoved(QModelIndex,int,int)), this, 
SLOT(slotColumnsRemoved(QModelIndex,int,int)));
+    connect(sourceModel, 
SIGNAL(columnsAboutToBeInserted(QModelIndex,int,int)), this, 
SLOT(slotColumnsAboutToBeInserted(QModelIndex,int,int)));
+    connect(sourceModel, SIGNAL(columnsAboutToBeRemoved(QModelIndex,int,int)), 
this, SLOT(slotColumnsAboutToBeRemoved(QModelIndex,int,int)));
+
+    connect(sourceModel, 
SIGNAL(layoutAboutToBeChanged(QList<QPersistentModelIndex>, 
QAbstractItemModel::LayoutChangeHint)),
+            this, 
SLOT(slotSourceLayoutAboutToBeChanged(QList<QPersistentModelIndex>, 
QAbstractItemModel::LayoutChangeHint)));
+    connect(sourceModel, SIGNAL(layoutChanged(QList<QPersistentModelIndex>, 
QAbstractItemModel::LayoutChangeHint)),
+            this, SLOT(slotSourceLayoutChanged(QList<QPersistentModelIndex>, 
QAbstractItemModel::LayoutChangeHint)));
+    connect(sourceModel, SIGNAL(modelAboutToBeReset()), this, 
SLOT(slotModelAboutToBeReset()));
+    connect(sourceModel, SIGNAL(modelReset()), this, SLOT(slotModelReset()));
+
+    const int newRows = sourceModel->rowCount();
+    if (newRows > 0) {
+        beginInsertRows(QModelIndex(), d->m_rowCount, d->m_rowCount + newRows 
- 1);
+    }
+    d->m_rowCount += newRows;
+    d->m_models.append(sourceModel);
+    if (newRows > 0) {
+        endInsertRows();
+    }
+}
+
+void KConcatenateRowsProxyModel::removeSourceModel(QAbstractItemModel 
*sourceModel)
+{
+    Q_ASSERT(d->m_models.contains(sourceModel));
+    disconnect(sourceModel, 0, this, 0);
+
+    const int rowsRemoved = sourceModel->rowCount();
+    const int rowsPrior = d->computeRowsPrior(sourceModel);   // location of 
removed section
+
+    if (rowsRemoved > 0) {
+        beginRemoveRows(QModelIndex(), rowsPrior, rowsPrior + rowsRemoved - 1);
+    }
+    d->m_models.removeOne(sourceModel);
+    d->m_rowCount -= rowsRemoved;
+    if (rowsRemoved > 0) {
+        endRemoveRows();
+    }
+}
+
+void KConcatenateRowsProxyModelPrivate::slotRowsAboutToBeInserted(const 
QModelIndex &, int start, int end)
+{
+    const QAbstractItemModel *model = qobject_cast<QAbstractItemModel 
*>(q->sender());
+    const int rowsPrior = computeRowsPrior(model);
+    q->beginInsertRows(QModelIndex(), rowsPrior + start, rowsPrior + end);
+}
+
+void KConcatenateRowsProxyModelPrivate::slotRowsInserted(const QModelIndex &, 
int start, int end)
+{
+    m_rowCount += end - start + 1;
+    q->endInsertRows();
+}
+
+void KConcatenateRowsProxyModelPrivate::slotRowsAboutToBeRemoved(const 
QModelIndex &, int start, int end)
+{
+    const QAbstractItemModel *model = qobject_cast<QAbstractItemModel 
*>(q->sender());
+    const int rowsPrior = computeRowsPrior(model);
+    q->beginRemoveRows(QModelIndex(), rowsPrior + start, rowsPrior + end);
+}
+
+void KConcatenateRowsProxyModelPrivate::slotRowsRemoved(const QModelIndex &, 
int start, int end)
+{
+    m_rowCount -= end - start + 1;
+    q->endRemoveRows();
+}
+
+void KConcatenateRowsProxyModelPrivate::slotColumnsAboutToBeInserted(const 
QModelIndex &parent, int start, int end)
+{
+    if (parent.isValid()) { // we are flat
+        return;
+    }
+    const QAbstractItemModel *model = qobject_cast<QAbstractItemModel 
*>(q->sender());
+    if (m_models.at(0) == model) {
+        q->beginInsertColumns(QModelIndex(), start, end);
+    }
+}
+
+void KConcatenateRowsProxyModelPrivate::slotColumnsInserted(const QModelIndex 
&parent, int, int)
+{
+    if (parent.isValid()) { // we are flat
+        return;
+    }
+    const QAbstractItemModel *model = qobject_cast<QAbstractItemModel 
*>(q->sender());
+    if (m_models.at(0) == model) {
+        q->endInsertColumns();
+    }
+}
+
+void KConcatenateRowsProxyModelPrivate::slotColumnsAboutToBeRemoved(const 
QModelIndex &parent, int start, int end)
+{
+    if (parent.isValid()) { // we are flat
+        return;
+    }
+    const QAbstractItemModel *model = qobject_cast<QAbstractItemModel 
*>(q->sender());
+    if (m_models.at(0) == model) {
+        q->beginRemoveColumns(QModelIndex(), start, end);
+    }
+}
+
+void KConcatenateRowsProxyModelPrivate::slotColumnsRemoved(const QModelIndex 
&parent, int, int)
+{
+    if (parent.isValid()) { // we are flat
+        return;
+    }
+    const QAbstractItemModel *model = qobject_cast<QAbstractItemModel 
*>(q->sender());
+    if (m_models.at(0) == model) {
+        q->endRemoveColumns();
+    }
+}
+
+void KConcatenateRowsProxyModelPrivate::slotDataChanged(const QModelIndex 
&from, const QModelIndex &to, const QVector<int> &roles)
+{
+    if (!from.isValid()) { // QSFPM bug, it emits dataChanged(invalid, 
invalid) if a cell in a hidden column changes
+        return;
+    }
+    const QModelIndex myFrom = q->mapFromSource(from);
+    const QModelIndex myTo = q->mapFromSource(to);
+    emit q->dataChanged(myFrom, myTo, roles);
+}
+
+void KConcatenateRowsProxyModelPrivate::slotSourceLayoutAboutToBeChanged(const 
QList<QPersistentModelIndex> &sourceParents, 
QAbstractItemModel::LayoutChangeHint hint)
+{
+    const QModelIndexList persistentIndexList = q->persistentIndexList();
+    layoutChangePersistentIndexes.reserve(persistentIndexList.size());
+
+    foreach (QPersistentModelIndex proxyPersistentIndex, persistentIndexList) {
+        proxyIndexes << proxyPersistentIndex;
+        Q_ASSERT(proxyPersistentIndex.isValid());
+        const QPersistentModelIndex srcPersistentIndex = 
q->mapToSource(proxyPersistentIndex);
+        Q_ASSERT(srcPersistentIndex.isValid());
+        layoutChangePersistentIndexes << srcPersistentIndex;
+    }
+
+    QList<QPersistentModelIndex> parents;
+    parents.reserve(sourceParents.size());
+    foreach (const QPersistentModelIndex &parent, sourceParents) {
+        if (!parent.isValid()) {
+            parents << QPersistentModelIndex();
+            continue;
+        }
+        const QModelIndex mappedParent = q->mapFromSource(parent);
+        Q_ASSERT(mappedParent.isValid());
+        parents << mappedParent;
+    }
+
+    emit q->layoutAboutToBeChanged(parents, hint);
+}
+
+void KConcatenateRowsProxyModelPrivate::slotSourceLayoutChanged(const 
QList<QPersistentModelIndex> &sourceParents, 
QAbstractItemModel::LayoutChangeHint hint)
+{
+    for (int i = 0; i < proxyIndexes.size(); ++i) {
+        const QModelIndex proxyIdx = proxyIndexes.at(i);
+        QModelIndex newProxyIdx = 
q->mapFromSource(layoutChangePersistentIndexes.at(i));
+        q->changePersistentIndex(proxyIdx, newProxyIdx);
+    }
+
+    layoutChangePersistentIndexes.clear();
+    proxyIndexes.clear();
+
+    QList<QPersistentModelIndex> parents;
+    parents.reserve(sourceParents.size());
+    foreach (const QPersistentModelIndex &parent, sourceParents) {
+        if (!parent.isValid()) {
+            parents << QPersistentModelIndex();
+            continue;
+        }
+        const QModelIndex mappedParent = q->mapFromSource(parent);
+        Q_ASSERT(mappedParent.isValid());
+        parents << mappedParent;
+    }
+    emit q->layoutChanged(parents, hint);
+}
+
+void KConcatenateRowsProxyModelPrivate::slotModelAboutToBeReset()
+{
+        const QAbstractItemModel *sourceModel = qobject_cast<const 
QAbstractItemModel *>(q->sender());
+    Q_ASSERT(m_models.contains(const_cast<QAbstractItemModel *>(sourceModel)));
+    const int oldRows = sourceModel->rowCount();
+    if (oldRows > 0) {
+        slotRowsAboutToBeRemoved(QModelIndex(), 0, oldRows - 1);
+        slotRowsRemoved(QModelIndex(), 0, oldRows - 1);
+    }
+    if (m_models.at(0) == sourceModel) {
+        q->beginResetModel();
+    }
+}
+
+void KConcatenateRowsProxyModelPrivate::slotModelReset()
+{
+    const QAbstractItemModel *sourceModel = qobject_cast<const 
QAbstractItemModel *>(q->sender());
+    Q_ASSERT(m_models.contains(const_cast<QAbstractItemModel *>(sourceModel)));
+    if (m_models.at(0) == sourceModel) {
+        q->endResetModel();
+    }
+    const int newRows = sourceModel->rowCount();
+    if (newRows > 0) {
+        slotRowsAboutToBeInserted(QModelIndex(), 0, newRows - 1);
+        slotRowsInserted(QModelIndex(), 0, newRows - 1);
+    }
+}
+
+int KConcatenateRowsProxyModelPrivate::computeRowsPrior(const 
QAbstractItemModel *sourceModel) const
+{
+    int rowsPrior = 0;
+    foreach (const QAbstractItemModel *model, m_models) {
+        if (model == sourceModel) {
+            break;
+        }
+        rowsPrior += model->rowCount();
+    }
+    return rowsPrior;
+}
+
+QAbstractItemModel *KConcatenateRowsProxyModelPrivate::sourceModelForRow(int 
row, int *sourceRow) const
+{
+    int rowCount = 0;
+    QAbstractItemModel *selection = NULL;
+    foreach (QAbstractItemModel *model, m_models) {
+        const int subRowCount = model->rowCount();
+        if (rowCount + subRowCount > row) {
+            selection = model;
+            break;
+        }
+        rowCount += subRowCount;
+    }
+    *sourceRow = row - rowCount;
+    return selection;
+}
+
+#include "moc_kconcatenaterowsproxymodel.cpp"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kitemmodels-5.13.0/src/kconcatenaterowsproxymodel.h 
new/kitemmodels-5.14.0/src/kconcatenaterowsproxymodel.h
--- old/kitemmodels-5.13.0/src/kconcatenaterowsproxymodel.h     1970-01-01 
01:00:00.000000000 +0100
+++ new/kitemmodels-5.14.0/src/kconcatenaterowsproxymodel.h     2015-09-04 
22:14:38.000000000 +0200
@@ -0,0 +1,151 @@
+/*
+    Copyright (c) 2015 Klarälvdalens Datakonsult AB, a KDAB Group company, 
i...@kdab.com
+    Authors: David Faure <david.fa...@kdab.com>
+
+    This library is free software; you can redistribute it and/or modify it
+    under the terms of the GNU Library General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or (at your
+    option) any later version.
+
+    This library is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
+    License for more details.
+
+    You should have received a copy of the GNU Library General Public License
+    along with this library; see the file COPYING.LIB.  If not, write to the
+    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+    02110-1301, USA.
+*/
+
+#ifndef KCONCATENATEROWSPROXYMODEL_H
+#define KCONCATENATEROWSPROXYMODEL_H
+
+#include <QAbstractItemModel>
+#include <QScopedPointer>
+#include "kitemmodels_export.h"
+
+class KConcatenateRowsProxyModelPrivate;
+
+/**
+ * This proxy takes multiple source models and concatenates their rows.
+ *
+ * In other words, the proxy will have all rows of the first source model,
+ * followed by all rows of the second source model, etc.
+ *
+ * Only flat models (lists and tables) are supported, no trees.
+ *
+ * All models are assumed to have the same number of columns.
+ * More precisely, the number of columns of the first source model is used,
+ * so all source models should have at least as many columns as the first 
source model,
+ * and additional columns in other source models will simply be ignored.
+ *
+ * Source models can be added and removed at runtime, including the first 
source
+ * model (but it should keep the same number of columns).
+ *
+ * Dynamic insertion and removal of rows and columns in any source model is 
supported.
+ * dataChanged, layoutChanged and reset coming from the source models are 
supported.
+ *
+ * At the moment this model doesn't support editing, drag-n-drop.
+ * It could be added though, nothing prevents it.
+ *
+ * This proxy does not inherit from QAbstractProxyModel because it uses 
multiple source
+ * models, rather than a single one.
+ *
+ * Author: David Faure, KDAB
+ * @since 5.14
+ */
+class KITEMMODELS_EXPORT KConcatenateRowsProxyModel : public QAbstractItemModel
+{
+    Q_OBJECT
+
+public:
+    /**
+     * Creates a KConcatenateRowsProxyModel.
+     * @param parent optional parent
+     */
+    KConcatenateRowsProxyModel(QObject *parent = 0);
+    /**
+     * Destructor.
+     */
+    virtual ~KConcatenateRowsProxyModel();
+
+    /**
+     * Adds a source model @p sourceModel, after all existing source models.
+     * @param sourceModel the source model
+     *
+     * The ownership of @p sourceModel is not affected by this.
+     * The same source model cannot be added more than once.
+     */
+    void addSourceModel(QAbstractItemModel *sourceModel);
+
+    /**
+     * Removes the source model @p sourceModel.
+     * @param sourceModel a source model previously added to this proxy
+     *
+     * The ownership of @sourceModel is not affected by this.
+     */
+    void removeSourceModel(QAbstractItemModel *sourceModel);
+
+    /**
+     * Returns the proxy index for a given source index
+     * @param sourceIndex an index coming from any of the source models
+     * @return a proxy index
+     * Calling this method with an index not from a source model is undefined 
behavior.
+     */
+    QModelIndex mapFromSource(const QModelIndex &sourceIndex) const;
+
+    /**
+     * Returns the source index for a given proxy index.
+     * @param proxyIndex an index for this proxy model
+     * @return a source index
+     */
+    QModelIndex mapToSource(const QModelIndex &proxyIndex) const;
+
+    /// @reimp
+    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const 
Q_DECL_OVERRIDE;
+    /// @reimp
+    bool setData(const QModelIndex &index, const QVariant &value, int role = 
Qt::DisplayRole) Q_DECL_OVERRIDE;
+    /// @reimp
+    QMap<int, QVariant> itemData(const QModelIndex &proxyIndex) const 
Q_DECL_OVERRIDE;
+    /// @reimp
+    Qt::ItemFlags flags(const QModelIndex &index) const Q_DECL_OVERRIDE;
+    /// @reimp
+    QModelIndex index(int row, int column, const QModelIndex &parent = 
QModelIndex()) const Q_DECL_OVERRIDE;
+    /// @reimp
+    QModelIndex parent(const QModelIndex &index) const Q_DECL_OVERRIDE;
+    /// @reimp
+    int rowCount(const QModelIndex &parent = QModelIndex()) const 
Q_DECL_OVERRIDE;
+
+    /**
+     * The horizontal header data for the first source model is returned here.
+     * @reimp
+     */
+    QVariant headerData(int section, Qt::Orientation orientation, int role = 
Qt::DisplayRole) const Q_DECL_OVERRIDE;
+    /**
+     * The column count for the first source model is returned here.
+     * @reimp
+     */
+    int columnCount(const QModelIndex &parent = QModelIndex()) const 
Q_DECL_OVERRIDE;
+
+private:
+    Q_PRIVATE_SLOT(d, void slotRowsAboutToBeInserted(const QModelIndex &, int 
start, int end))
+    Q_PRIVATE_SLOT(d, void slotRowsInserted(const QModelIndex &, int start, 
int end))
+    Q_PRIVATE_SLOT(d, void slotRowsAboutToBeRemoved(const QModelIndex &, int 
start, int end))
+    Q_PRIVATE_SLOT(d, void slotRowsRemoved(const QModelIndex &, int start, int 
end))
+    Q_PRIVATE_SLOT(d, void slotColumnsAboutToBeInserted(const QModelIndex 
&parent, int start, int end))
+    Q_PRIVATE_SLOT(d, void slotColumnsInserted(const QModelIndex &parent, int, 
int))
+    Q_PRIVATE_SLOT(d, void slotColumnsAboutToBeRemoved(const QModelIndex 
&parent, int start, int end))
+    Q_PRIVATE_SLOT(d, void slotColumnsRemoved(const QModelIndex &parent, int, 
int))
+    Q_PRIVATE_SLOT(d, void slotDataChanged(const QModelIndex &from, const 
QModelIndex &to, const QVector<int> &roles))
+    Q_PRIVATE_SLOT(d, void 
slotSourceLayoutAboutToBeChanged(QList<QPersistentModelIndex>, 
QAbstractItemModel::LayoutChangeHint))
+    Q_PRIVATE_SLOT(d, void slotSourceLayoutChanged(const 
QList<QPersistentModelIndex> &, QAbstractItemModel::LayoutChangeHint))
+    Q_PRIVATE_SLOT(d, void slotModelAboutToBeReset())
+    Q_PRIVATE_SLOT(d, void slotModelReset())
+
+private:
+    friend class KConcatenateRowsProxyModelPrivate;
+    const QScopedPointer<KConcatenateRowsProxyModelPrivate> d;
+};
+
+#endif
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kitemmodels-5.13.0/src/kextracolumnsproxymodel.cpp 
new/kitemmodels-5.14.0/src/kextracolumnsproxymodel.cpp
--- old/kitemmodels-5.13.0/src/kextracolumnsproxymodel.cpp      2015-08-01 
14:41:18.000000000 +0200
+++ new/kitemmodels-5.14.0/src/kextracolumnsproxymodel.cpp      2015-09-04 
22:14:38.000000000 +0200
@@ -39,7 +39,7 @@
     // Configuration (doesn't change once source model is plugged in)
     QVector<QString> m_extraHeaders;
 
-    // for layoutAboutToChanged/layoutChanged
+    // for layoutAboutToBeChanged/layoutChanged
     QVector<QPersistentModelIndex> layoutChangePersistentIndexes;
     QVector<int> layoutChangeProxyColumns;
     QModelIndexList proxyIndexes;
@@ -71,7 +71,7 @@
     return false;
 }
 
-void KExtraColumnsProxyModel::extraColumnDataChanged(const QModelIndex 
&parent, int row, int extraColumn, const QVector<int> & roles)
+void KExtraColumnsProxyModel::extraColumnDataChanged(const QModelIndex 
&parent, int row, int extraColumn, const QVector<int> &roles)
 {
     const QModelIndex idx = index(row, proxyColumnForExtraColumn(extraColumn), 
parent);
     emit dataChanged(idx, idx, roles);
@@ -81,9 +81,9 @@
 {
     if (sourceModel()) {
         disconnect(sourceModel(), 
SIGNAL(layoutAboutToBeChanged(QList<QPersistentModelIndex>,QAbstractItemModel::LayoutChangeHint)),
-                this, 
SLOT(_ec_sourceLayoutAboutToBeChanged(QList<QPersistentModelIndex>,QAbstractItemModel::LayoutChangeHint)));
+                   this, 
SLOT(_ec_sourceLayoutAboutToBeChanged(QList<QPersistentModelIndex>,QAbstractItemModel::LayoutChangeHint)));
         disconnect(sourceModel(), 
SIGNAL(layoutChanged(QList<QPersistentModelIndex>,QAbstractItemModel::LayoutChangeHint)),
-                this, 
SLOT(_ec_sourceLayoutChanged(QList<QPersistentModelIndex>,QAbstractItemModel::LayoutChangeHint)));
+                   this, 
SLOT(_ec_sourceLayoutChanged(QList<QPersistentModelIndex>,QAbstractItemModel::LayoutChangeHint)));
     }
 
     QIdentityProxyModel::setSourceModel(model);
@@ -92,9 +92,9 @@
         // The handling of persistent model indexes assumes mapToSource can be 
called for any index
         // This breaks for the extra column, so we'll have to do it ourselves
         disconnect(model, 
SIGNAL(layoutAboutToBeChanged(QList<QPersistentModelIndex>,QAbstractItemModel::LayoutChangeHint)),
-                this, 
SLOT(_q_sourceLayoutAboutToBeChanged(QList<QPersistentModelIndex>,QAbstractItemModel::LayoutChangeHint)));
+                   this, 
SLOT(_q_sourceLayoutAboutToBeChanged(QList<QPersistentModelIndex>,QAbstractItemModel::LayoutChangeHint)));
         disconnect(model, 
SIGNAL(layoutChanged(QList<QPersistentModelIndex>,QAbstractItemModel::LayoutChangeHint)),
-                this, 
SLOT(_q_sourceLayoutChanged(QList<QPersistentModelIndex>,QAbstractItemModel::LayoutChangeHint)));
+                   this, 
SLOT(_q_sourceLayoutChanged(QList<QPersistentModelIndex>,QAbstractItemModel::LayoutChangeHint)));
         connect(model, 
SIGNAL(layoutAboutToBeChanged(QList<QPersistentModelIndex>,QAbstractItemModel::LayoutChangeHint)),
                 this, 
SLOT(_ec_sourceLayoutAboutToBeChanged(QList<QPersistentModelIndex>,QAbstractItemModel::LayoutChangeHint)));
         connect(model, 
SIGNAL(layoutChanged(QList<QPersistentModelIndex>,QAbstractItemModel::LayoutChangeHint)),
@@ -117,15 +117,14 @@
 
 QModelIndex KExtraColumnsProxyModel::sibling(int row, int column, const 
QModelIndex &idx) const
 {
-    if (idx.column() >= sourceModel()->columnCount()) {
-        return index(row, column, parent(idx));
+    if (row == idx.row() && column == idx.column()) {
+        return idx;
     }
-    return mapFromSource(sourceModel()->sibling(row, column, 
mapToSource(idx)));
+    return index(row, column, parent(idx));
 }
 
 QItemSelection KExtraColumnsProxyModel::mapSelectionToSource(const 
QItemSelection &selection) const
 {
-    Q_D(const KExtraColumnsProxyModel);
     QItemSelection sourceSelection;
 
     if (!sourceModel()) {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/kitemmodels-5.13.0/src/krearrangecolumnsproxymodel.cpp 
new/kitemmodels-5.14.0/src/krearrangecolumnsproxymodel.cpp
--- old/kitemmodels-5.13.0/src/krearrangecolumnsproxymodel.cpp  2015-08-01 
14:41:18.000000000 +0200
+++ new/kitemmodels-5.14.0/src/krearrangecolumnsproxymodel.cpp  2015-09-04 
22:14:38.000000000 +0200
@@ -28,7 +28,7 @@
 
 KRearrangeColumnsProxyModel::KRearrangeColumnsProxyModel(QObject *parent)
     : QIdentityProxyModel(parent),
-    d_ptr(new KRearrangeColumnsProxyModelPrivate)
+      d_ptr(new KRearrangeColumnsProxyModelPrivate)
 {
 }
 


Reply via email to