New proxy model: KRearrangeColumnsProxyModel.

It supports reordering and hiding columns from the source model.

REVIEW: 124156
wilder
David Faure 11 years ago
parent 7a94f65e16
commit 71d2e8c665
  1. 1
      autotests/CMakeLists.txt
  2. 209
      autotests/krearrangecolumnsproxymodeltest.cpp
  3. 62
      autotests/test_model_helpers.h
  4. 2
      src/CMakeLists.txt
  5. 129
      src/krearrangecolumnsproxymodel.cpp
  6. 96
      src/krearrangecolumnsproxymodel.h

@ -20,6 +20,7 @@ ecm_add_tests(
testmodelqueuedconnections.cpp
kselectionproxymodeltest.cpp
krecursivefilterproxymodeltest.cpp
krearrangecolumnsproxymodeltest.cpp
LINK_LIBRARIES KF5::ItemModels Qt5::Test Qt5::Widgets proxymodeltestsuite
)

@ -0,0 +1,209 @@
/*
Copyright (c) 2015 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com
Authors: David Faure <david.faure@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 <QSignalSpy>
#include <QSortFilterProxyModel>
#include <QTest>
#include <QDebug>
#include <QStandardItemModel>
#include <QTreeView>
#include <krearrangecolumnsproxymodel.h>
#include "test_model_helpers.h"
using namespace TestModelHelpers;
Q_DECLARE_METATYPE(QModelIndex)
class tst_KRearrangeColumnsProxyModel : public QObject
{
Q_OBJECT
private Q_SLOTS:
void initTestCase()
{
qRegisterMetaType<QModelIndex>();
}
void init()
{
// Prepare the source model to use later on
mod.clear();
mod.appendRow(makeStandardItems(QStringList() << "A" << "B" << "C" << "D" << "E"));
mod.item(0, 0)->appendRow(makeStandardItems(QStringList() << "m" << "n" << "o" << "p" << "-"));
mod.item(0, 0)->appendRow(makeStandardItems(QStringList() << "q" << "r" << "s" << "t" << "-"));
mod.appendRow(makeStandardItems(QStringList() << "E" << "F" << "G" << "H" << "I"));
mod.item(1, 0)->appendRow(makeStandardItems(QStringList() << "x" << "y" << "z" << "." << "-"));
mod.setHorizontalHeaderLabels(QStringList() << "H1" << "H2" << "H3" << "H4" << "H5");
QCOMPARE(extractRowTexts(&mod, 0), QString("ABCDE"));
QCOMPARE(extractRowTexts(&mod, 0, mod.index(0, 0)), QString("mnop-"));
QCOMPARE(extractRowTexts(&mod, 1, mod.index(0, 0)), QString("qrst-"));
QCOMPARE(extractRowTexts(&mod, 1), QString("EFGHI"));
QCOMPARE(extractRowTexts(&mod, 0, mod.index(1, 0)), QString("xyz.-"));
QCOMPARE(extractHorizontalHeaderTexts(&mod), QString("H1H2H3H4H5"));
// test code to see the model
// showModel(&mod);
}
void shouldShowNothingIfNoColumnSelection()
{
// Given a rearrange-columns proxy
KRearrangeColumnsProxyModel pm;
// When setting it to a source model
pm.setSourceModel(&mod);
// Then the proxy should show nothing (no columns selected)
QCOMPARE(pm.rowCount(), mod.rowCount());
QCOMPARE(pm.columnCount(), 0);
}
void shouldRearrangeColumns()
{
// Given a rearrange-columns proxy
KRearrangeColumnsProxyModel pm;
// When setting it to a source model, with columns rearranged
setup(pm);
// Then the proxy should show columns reordered
QCOMPARE(pm.rowCount(), 2);
// (verify that the mapFromSource(mapToSource(x)) == x roundtrip works)
for (int row = 0; row < pm.rowCount(); ++row) {
for (int col = 0; col < pm.columnCount(); ++col) {
//qDebug() << "row" << row << "col" << col;
QCOMPARE(pm.mapFromSource(pm.mapToSource(pm.index(row, col))), pm.index(row, col));
}
}
QCOMPARE(indexRowCol(pm.index(0, 0)), QString("0,0"));
QCOMPARE(pm.rowCount(pm.index(0, 0)), 2);
QCOMPARE(pm.index(0, 0).parent(), QModelIndex());
QCOMPARE(pm.mapToSource(pm.index(0, 0)).column(), 2); // column 0 points to C
QCOMPARE(pm.mapToSource(pm.index(0, 1)).column(), 3); // column 1 points to D
QCOMPARE(extractRowTexts(&pm, 0), QString("CDBA"));
QCOMPARE(extractRowTexts(&pm, 0, pm.index(0, 0)), QString("opnm"));
QCOMPARE(extractRowTexts(&pm, 1, pm.index(0, 0)), QString("strq"));
QCOMPARE(extractRowTexts(&pm, 1), QString("GHFE"));
QCOMPARE(extractRowTexts(&pm, 0, pm.index(1, 0)), QString("z.yx"));
QCOMPARE(extractHorizontalHeaderTexts(&pm), QString("H3H4H2H1"));
// Verify tree structure of proxy
const QModelIndex secondParent = pm.index(1, 0);
QVERIFY(!secondParent.parent().isValid());
QCOMPARE(indexToText(pm.index(0, 0, secondParent).parent()), indexToText(secondParent));
QCOMPARE(indexToText(pm.index(0, 3, secondParent).parent()), indexToText(secondParent));
QVERIFY(!pm.canFetchMore(QModelIndex()));
}
void shouldHandleDataChanged()
{
// Given a rearrange-columns proxy
KRearrangeColumnsProxyModel pm;
setup(pm);
QSignalSpy dataChangedSpy(&pm, SIGNAL(dataChanged(QModelIndex,QModelIndex)));
// When a cell in a source model changes
mod.item(0, 2)->setData("c", Qt::EditRole);
mod.item(0, 3)->setData("d", Qt::EditRole);
// Then the change should be notified to the proxy
QCOMPARE(dataChangedSpy.count(), 2);
QCOMPARE(indexToText(dataChangedSpy.at(0).at(0).value<QModelIndex>()), indexToText(pm.index(0, 0)));
QCOMPARE(indexToText(dataChangedSpy.at(1).at(0).value<QModelIndex>()), indexToText(pm.index(0, 1)));
QCOMPARE(extractRowTexts(&pm, 0), QString("cdBA"));
}
void shouldHandleDataChangedInChild()
{
// Given a rearrange-columns proxy
KRearrangeColumnsProxyModel pm;
setup(pm);
QSignalSpy dataChangedSpy(&pm, SIGNAL(dataChanged(QModelIndex,QModelIndex)));
// When a cell in a source model changes
mod.item(1, 0)->child(0, 3)->setData(",", Qt::EditRole);
// Then the change should be notified to the proxy
QCOMPARE(dataChangedSpy.count(), 1);
QCOMPARE(indexToText(dataChangedSpy.at(0).at(0).value<QModelIndex>()), indexToText(pm.index(1, 0).child(0, 1)));
QCOMPARE(extractRowTexts(&pm, 0, pm.index(1, 0)), QString("z,yx"));
}
void shouldSupportSetData()
{
// Given a rearrange-columns proxy
KRearrangeColumnsProxyModel pm;
setup(pm);
QSignalSpy dataChangedSpy(&pm, SIGNAL(dataChanged(QModelIndex,QModelIndex)));
// When changing data via the proxy
const QModelIndex idx = pm.index(0, 2);
QCOMPARE(idx.data().toString(), QString("B"));
pm.setData(idx, QString("Z"));
QCOMPARE(idx.data().toString(), QString("Z"));
QCOMPARE(extractRowTexts(&pm, 0), QString("CDZA"));
QCOMPARE(extractRowTexts(&mod, 0), QString("AZCDE"));
}
private:
// setup proxy
void setup(KRearrangeColumnsProxyModel &pm)
{
pm.setSourceColumns(QVector<int>() << 2 << 3 << 1 << 0);
pm.setSourceModel(&mod);
pm.sort(0); // don't forget this!
}
static QString indexRowCol(const QModelIndex &index)
{
if (!index.isValid()) {
return "invalid";
}
return QString::number(index.row()) + "," + QString::number(index.column());
}
static QString indexToText(const QModelIndex &index)
{
if (!index.isValid()) {
return "invalid";
}
return QString::number(index.row()) + "," + QString::number(index.column()) + ","
+ QString::number(reinterpret_cast<qulonglong>(index.internalPointer()), 16)
+ " in " + QString::number(reinterpret_cast<qulonglong>(index.model()), 16);
}
QStandardItemModel mod;
};
QTEST_MAIN(tst_KRearrangeColumnsProxyModel)
#include "krearrangecolumnsproxymodeltest.moc"

@ -0,0 +1,62 @@
/*
Copyright (c) 2015 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com
Authors: David Faure <david.faure@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 <QString>
namespace TestModelHelpers
{
// Prepares one row for a QStandardItemModel
inline QList<QStandardItem *> makeStandardItems(const QStringList &texts)
{
QList<QStandardItem *> items;
foreach (const QString &txt, texts) {
items << new QStandardItem(txt);
}
return items;
}
// Extracts a full row from a model as a string
// Works best if every cell contains only one character
inline QString extractRowTexts(QAbstractItemModel *model, int row, const QModelIndex &parent = QModelIndex())
{
QString result;
const int colCount = model->columnCount();
for (int col = 0; col < colCount; ++col) {
const QString txt = model->index(row, col, parent).data().toString();
result += txt.isEmpty() ? QString::fromLatin1(" ") : txt;
}
return result;
}
// Extracts all headers
inline QString extractHorizontalHeaderTexts(QAbstractItemModel *model)
{
QString result;
const int colCount = model->columnCount();
for (int col = 0; col < colCount; ++col) {
const QString txt = model->headerData(col, Qt::Horizontal).toString();
result += txt.isEmpty() ? QString::fromLatin1(" ") : txt;
}
return result;
}
}

@ -5,6 +5,7 @@ set(kitemmodels_SRCS
kdescendantsproxymodel.cpp
klinkitemselectionmodel.cpp
kmodelindexproxymapper.cpp
krearrangecolumnsproxymodel.cpp
krecursivefilterproxymodel.cpp
kselectionproxymodel.cpp
)
@ -27,6 +28,7 @@ ecm_generate_headers(KItemModels_HEADERS
KBreadcrumbSelectionModel
KCheckableProxyModel
KLinkItemSelectionModel
KRearrangeColumnsProxyModel
KRecursiveFilterProxyModel
KDescendantsProxyModel
KModelIndexProxyMapper

@ -0,0 +1,129 @@
/*
Copyright (c) 2015 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com
Authors: David Faure <david.faure@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 "krearrangecolumnsproxymodel.h"
class KRearrangeColumnsProxyModelPrivate
{
public:
QVector<int> m_sourceColumns;
};
KRearrangeColumnsProxyModel::KRearrangeColumnsProxyModel(QObject *parent)
: QIdentityProxyModel(parent),
d_ptr(new KRearrangeColumnsProxyModelPrivate)
{
}
KRearrangeColumnsProxyModel::~KRearrangeColumnsProxyModel()
{
}
void KRearrangeColumnsProxyModel::setSourceColumns(const QVector<int> &columns)
{
d_ptr->m_sourceColumns = columns;
}
int KRearrangeColumnsProxyModel::columnCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
return d_ptr->m_sourceColumns.count();
}
int KRearrangeColumnsProxyModel::rowCount(const QModelIndex &parent) const
{
Q_ASSERT(parent.isValid() ? parent.model() == this : true);
// The parent in the source model is on column 0, whatever swapping we are doing
const QModelIndex sourceParent = mapToSource(parent).sibling(parent.row(), 0);
return sourceModel()->rowCount(sourceParent);
}
// We derive from QIdentityProxyModel simply to be able to use
// its mapFromSource method which has friend access to createIndex() in the source model.
QModelIndex KRearrangeColumnsProxyModel::index(int row, int column, const QModelIndex &parent) const
{
Q_ASSERT(parent.isValid() ? parent.model() == this : true);
Q_ASSERT(row >= 0);
Q_ASSERT(column >= 0);
// The parent in the source model is on column 0, whatever swapping we are doing
const QModelIndex sourceParent = mapToSource(parent).sibling(parent.row(), 0);
// Find the child in the source model, we need its internal pointer
const QModelIndex sourceIndex = sourceModel()->index(row, sourceColumnForProxyColumn(column), sourceParent);
Q_ASSERT(sourceIndex.isValid());
return createIndex(row, column, sourceIndex.internalPointer());
}
QModelIndex KRearrangeColumnsProxyModel::parent(const QModelIndex &child) const
{
Q_ASSERT(child.isValid() ? child.model() == this : true);
const QModelIndex sourceIndex = mapToSource(child);
const QModelIndex sourceParent = sourceIndex.parent();
if (!sourceParent.isValid()) {
return QModelIndex();
}
return createIndex(sourceParent.row(), 0, sourceParent.internalPointer());
}
QVariant KRearrangeColumnsProxyModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (orientation == Qt::Horizontal) {
const int sourceCol = sourceColumnForProxyColumn(section);
return sourceModel()->headerData(sourceCol, orientation, role);
} else {
return QIdentityProxyModel::headerData(section, orientation, role);
}
}
QModelIndex KRearrangeColumnsProxyModel::mapFromSource(const QModelIndex &sourceIndex) const
{
if (!sourceIndex.isValid()) {
return QModelIndex();
}
Q_ASSERT(sourceIndex.model() == sourceModel());
const int proxyColumn = proxyColumnForSourceColumn(sourceIndex.column());
return createIndex(sourceIndex.row(), proxyColumn, sourceIndex.internalPointer());
}
QModelIndex KRearrangeColumnsProxyModel::mapToSource(const QModelIndex &proxyIndex) const
{
if (!proxyIndex.isValid()) {
return QModelIndex();
}
// This is just an indirect way to call sourceModel->createIndex(row, sourceColumn, pointer)
const QModelIndex fakeIndex = createIndex(proxyIndex.row(), sourceColumnForProxyColumn(proxyIndex.column()), proxyIndex.internalPointer());
return QIdentityProxyModel::mapToSource(fakeIndex);
}
int KRearrangeColumnsProxyModel::proxyColumnForSourceColumn(int sourceColumn) const
{
// If this is too slow, we could add a second QVector with index=logical_source_column value=desired_pos_in_proxy.
return d_ptr->m_sourceColumns.indexOf(sourceColumn);
}
int KRearrangeColumnsProxyModel::sourceColumnForProxyColumn(int proxyColumn) const
{
Q_ASSERT(proxyColumn >= 0);
Q_ASSERT(proxyColumn < d_ptr->m_sourceColumns.size());
return d_ptr->m_sourceColumns.at(proxyColumn);
}

@ -0,0 +1,96 @@
/*
Copyright (c) 2015 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com
Authors: David Faure <david.faure@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 REARRANGECOLUMNSPROXYMODEL_H
#define REARRANGECOLUMNSPROXYMODEL_H
#include <QIdentityProxyModel>
#include <QScopedPointer>
#include "kitemmodels_export.h"
class KRearrangeColumnsProxyModelPrivate;
/**
* This proxy shows specific columns from the source model, in any order.
* This allows to reorder columns, as well as not showing all of them.
*
* The proxy supports source models that have a tree structure.
* It also supports editing, and propagating changes from the source model.
*
* Showing the same source column more than once is not supported.
*
* Author: David Faure, KDAB
* @since 5.12
*/
class KITEMMODELS_EXPORT KRearrangeColumnsProxyModel : public QIdentityProxyModel
{
Q_OBJECT
public:
/**
* Creates a KRearrangeColumnsProxyModel proxy.
* Remember to call setSourceModel afterwards.
*/
explicit KRearrangeColumnsProxyModel(QObject *parent = 0);
/**
* Destructor.
*/
~KRearrangeColumnsProxyModel();
// API
/**
* Set the chosen source columns, in the desired order for the proxy columns
* columns[proxyColumn=0] is the source column to show in the first proxy column, etc.
*
* Example: QVector<int>() << 2 << 1;
* This examples configures the proxy to hide column 0, show column 2 from the source model,
* then show column 1 from the source model.
*/
void setSourceColumns(const QVector<int> &columns);
// Implementation
/// @reimp
int columnCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE;
/// @reimp
int rowCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE;
/// @reimp
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE;
/// @reimp
QModelIndex parent(const QModelIndex &child) const Q_DECL_OVERRIDE;
/// @reimp
QModelIndex mapFromSource(const QModelIndex &sourceIndex) const Q_DECL_OVERRIDE;
/// @reimp
QModelIndex mapToSource(const QModelIndex &proxyIndex) const Q_DECL_OVERRIDE;
/// @reimp
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE;
private:
int proxyColumnForSourceColumn(int sourceColumn) const;
int sourceColumnForProxyColumn(int proxyColumn) const;
private:
const QScopedPointer<KRearrangeColumnsProxyModelPrivate> d_ptr;
};
#endif
Loading…
Cancel
Save