Branch 'kolab/integration/4.13.0' - korganizer/akonadicollectionview.cpp korganizer/views
Sandro Knauß
knauss at kolabsys.com
Mon Oct 13 17:41:53 CEST 2014
korganizer/akonadicollectionview.cpp | 2
korganizer/views/collectionview/controller.cpp | 46
korganizer/views/collectionview/controller.h | 2
korganizer/views/collectionview/reparentingmodel.cpp | 41
korganizer/views/collectionview/reparentingmodel.h | 1
korganizer/views/collectionview/tests/CMakeLists.txt | 1
korganizer/views/collectionview/tests/modeltest.cpp | 651 ++++++++++
korganizer/views/collectionview/tests/modeltest.h | 80 +
korganizer/views/collectionview/tests/reparentingmodeltest.cpp | 77 +
9 files changed, 860 insertions(+), 41 deletions(-)
New commits:
commit 4a8d069f7b32b4381202a05828c0573af0cfb2aa
Author: Sandro Knauà <knauss at kolabsys.com>
Date: Mon Oct 13 17:38:33 2014 +0200
Make it possible to update personnodes for the reparenting model
* show subcols again
* add tests to make sure that update works
KOLAB: #3743
diff --git a/korganizer/akonadicollectionview.cpp b/korganizer/akonadicollectionview.cpp
index 0de456e..8b12286 100644
--- a/korganizer/akonadicollectionview.cpp
+++ b/korganizer/akonadicollectionview.cpp
@@ -351,7 +351,7 @@ class CollectionFilter : public QSortFilterProxyModel
const Akonadi::Collection &col = sourceIndex.data(Akonadi::EntityTreeModel::CollectionRole).value<Akonadi::Collection>();
CollectionIdentificationAttribute *attr = col.attribute<CollectionIdentificationAttribute>();
//We filter the user folders because we insert person nodes for user folders.
- if ( (attr && attr->collectionNamespace().startsWith("user"))
+ if ( (attr && attr->collectionNamespace().startsWith("usertoplevel"))
|| col.name().contains(QLatin1String("Other Users"))) {
return false;
}
diff --git a/korganizer/views/collectionview/controller.cpp b/korganizer/views/collectionview/controller.cpp
index 7c83a3c..d2b903f 100644
--- a/korganizer/views/collectionview/controller.cpp
+++ b/korganizer/views/collectionview/controller.cpp
@@ -110,7 +110,6 @@ bool CollectionNode::isDuplicateOf(const QModelIndex& sourceIndex)
return (sourceIndex.data(Akonadi::EntityTreeModel::CollectionIdRole).value<Akonadi::Collection::Id>() == mCollection.id());
}
-
PersonNode::PersonNode(ReparentingModel& personModel, const Person& person, const QModelIndex &colIndex)
: Node(personModel),
mPerson(person),
@@ -121,7 +120,6 @@ PersonNode::PersonNode(ReparentingModel& personModel, const Person& person, cons
}
-
PersonNode::~PersonNode()
{
@@ -223,46 +221,48 @@ bool PersonNode::isDuplicateOf(const QModelIndex& sourceIndex)
return (sourceIndex.data(PersonRole).value<Person>().uid == mPerson.uid);
}
-void PersonNodeManager::checkSourceIndex(const QModelIndex &sourceIndex)
+Person PersonNodeManager::person(const QModelIndex &sourceIndex)
{
+ Person person;
const Akonadi::Collection col = sourceIndex.data(Akonadi::EntityTreeModel::CollectionRole).value<Akonadi::Collection>();
- // kDebug() << col.displayName() << col.enabled();
if (col.isValid()) {
CollectionIdentificationAttribute *attr = col.attribute<CollectionIdentificationAttribute>();
if (attr && attr->collectionNamespace() == "usertoplevel") {
- kDebug() << "Found user folder, creating person node " << col.displayName();
- Person person;
person.name = col.displayName();
person.mail = QString::fromUtf8(attr->mail());
person.ou = QString::fromUtf8(attr->ou());
person.uid = col.name();
person.rootCollection = col.id();
-
- model.addNode(ReparentingModel::Node::Ptr(new PersonNode(model, person, sourceIndex)));
}
}
+ return person;
}
-void PersonNodeManager::checkSourceIndexRemoval(const QModelIndex &sourceIndex)
+void PersonNodeManager::checkSourceIndex(const QModelIndex &sourceIndex)
{
- const Akonadi::Collection col = sourceIndex.data(Akonadi::EntityTreeModel::CollectionRole).value<Akonadi::Collection>();
- // kDebug() << col.displayName() << col.enabled();
- if (col.isValid()) {
- CollectionIdentificationAttribute *attr = col.attribute<CollectionIdentificationAttribute>();
- if (attr && attr->collectionNamespace() == "usertoplevel") {
- kDebug() << "Found user folder, removing person node " << col.displayName();
- Person person;
- person.name = col.displayName();
- person.mail = QString::fromUtf8(attr->mail());
- person.ou = QString::fromUtf8(attr->ou());
- person.uid = col.name();
- person.rootCollection = col.id();
- model.removeNode(PersonNode(model, person, sourceIndex));
- }
+ const Person &p = person(sourceIndex);
+ if (p.rootCollection > -1) {
+ model.addNode(ReparentingModel::Node::Ptr(new PersonNode(model, p, sourceIndex)));
+ }
+}
+
+void PersonNodeManager::updateSourceIndex(const QModelIndex &sourceIndex)
+{
+ const Person &p = person(sourceIndex);
+ if (p.rootCollection > -1) {
+ model.updateNode(ReparentingModel::Node::Ptr(new PersonNode(model, p, sourceIndex)));
}
}
+void PersonNodeManager::checkSourceIndexRemoval(const QModelIndex &sourceIndex)
+{
+ const Person &p = person(sourceIndex);
+ if (p.rootCollection > -1) {
+ model.removeNode(PersonNode(model, p, sourceIndex));
+ }
+}
+
CollectionSearchJob::CollectionSearchJob(const QString& searchString, QObject* parent)
: KJob(parent),
mSearchString(searchString)
diff --git a/korganizer/views/collectionview/controller.h b/korganizer/views/collectionview/controller.h
index 0f404a0..c7ea03b 100644
--- a/korganizer/views/collectionview/controller.h
+++ b/korganizer/views/collectionview/controller.h
@@ -133,8 +133,10 @@ class PersonNodeManager : public ReparentingModel::NodeManager
public:
PersonNodeManager(ReparentingModel &personModel) : ReparentingModel::NodeManager(personModel){};
private:
+ Person person(const QModelIndex &sourceIndex);
void checkSourceIndex(const QModelIndex &sourceIndex);
void checkSourceIndexRemoval(const QModelIndex &sourceIndex);
+ void updateSourceIndex(const QModelIndex &sourceIndex);
};
class CollectionSearchJob : public KJob
diff --git a/korganizer/views/collectionview/reparentingmodel.cpp b/korganizer/views/collectionview/reparentingmodel.cpp
index 824f31c..5ef1c89 100644
--- a/korganizer/views/collectionview/reparentingmodel.cpp
+++ b/korganizer/views/collectionview/reparentingmodel.cpp
@@ -279,7 +279,13 @@ void ReparentingModel::updateNode(const ReparentingModel::Node::Ptr &node)
Q_FOREACH(const ReparentingModel::Node::Ptr &existing, mProxyNodes) {
if (*existing == *node) {
node->parent = existing->parent;
+ node->children = existing->children;
+ Q_FOREACH(ReparentingModel::Node::Ptr child, existing->children) {
+ child->parent = node.data();
+ }
+ node->sourceIndex = existing->sourceIndex;
int r = row(existing.data());
+ mProxyNodes.replace(mProxyNodes.indexOf(existing), node);
existing->parent->children.replace(r, node);
const QModelIndex i = index(node.data());
Q_ASSERT(i.row() == r);
@@ -468,7 +474,7 @@ void ReparentingModel::onSourceRowsInserted(QModelIndex parent, int start, int e
Q_ASSERT(validateNode(parentNode));
}
Q_ASSERT(parentNode);
-
+
//Remove any duplicates that we are going to replace
removeDuplicates(sourceIndex);
@@ -588,9 +594,8 @@ void ReparentingModel::onSourceLayoutChanged()
void ReparentingModel::onSourceDataChanged(QModelIndex begin, QModelIndex end)
{
for (int row = begin.row(); row <= end.row(); row++) {
- mNodeManager->checkSourceIndex(sourceModel()->index(row, begin.column(), begin.parent()));
+ mNodeManager->updateSourceIndex(sourceModel()->index(row, begin.column(), begin.parent()));
}
- emit dataChanged(mapFromSource(begin), mapFromSource(end));
}
void ReparentingModel::onSourceModelAboutToBeReset()
@@ -614,6 +619,9 @@ ReparentingModel::Node *ReparentingModel::extractNode(const QModelIndex &index)
QModelIndex ReparentingModel::index(int row, int column, const QModelIndex& parent) const
{
+ if (row < 0 || column != 0) {
+ return QModelIndex();
+ }
// kDebug() << parent << row;
const Node *parentNode;
if (parent.isValid()) {
@@ -623,7 +631,7 @@ QModelIndex ReparentingModel::index(int row, int column, const QModelIndex& pare
}
//At least QAbstractItemView expects that we deal with this properly (see rowsAboutToBeRemoved "find the next visible and enabled item")
//Also QAbstractItemModel::match does all kinds of weird shit including passing row=-1
- if (parentNode->children.size() <= row || row < 0) {
+ if (parentNode->children.size() <= row) {
return QModelIndex();
}
Node *node = parentNode->children.at(row).data();
@@ -709,15 +717,14 @@ void ReparentingModel::reparentSourceNodes(const Node::Ptr &proxyNode)
//Reparent source nodes according to the provided rules
Q_FOREACH(Node *n, mSourceNodes) {
if (proxyNode->adopts(n->sourceIndex)) {
- const int oldRow = n->sourceIndex.row();
- beginRemoveRows(index(n->parent), oldRow, oldRow);
- //We lie about the row being removed already, but the view can deal with that better than if we call endRemoveRows after beginInsertRows
- endRemoveRows();
-
+ //kDebug() << "reparenting" << n->data(Qt::DisplayRole).toString() << "from" << n->parent->data(Qt::DisplayRole).toString()
+ // << "to" << proxyNode->data(Qt::DisplayRole).toString();
+ const int oldRow = row(n);
const int newRow = proxyNode->children.size();
- beginInsertRows(index(proxyNode.data()), newRow, newRow);
+ beginMoveRows(index(n->parent), oldRow, oldRow,
+ index(proxyNode.data()), newRow);
proxyNode->reparent(n);
- endInsertRows();
+ endMoveRows();
Q_ASSERT(validateNode(n));
}
}
@@ -745,7 +752,9 @@ void ReparentingModel::rebuildAll()
QVariant ReparentingModel::data(const QModelIndex& proxyIndex, int role) const
{
- Q_ASSERT(proxyIndex.isValid());
+ if (!proxyIndex.isValid()) {
+ return QVariant();
+ }
const Node *node = extractNode(proxyIndex);
if (node->isSourceNode()) {
return sourceModel()->data(mapToSource(proxyIndex), role);
@@ -755,6 +764,9 @@ QVariant ReparentingModel::data(const QModelIndex& proxyIndex, int role) const
bool ReparentingModel::setData(const QModelIndex& index, const QVariant& value, int role)
{
+ if (!index.isValid()) {
+ return false;
+ }
Q_ASSERT(index.isValid());
if (!sourceModel()) {
return false;
@@ -831,6 +843,11 @@ int ReparentingModel::rowCount(const QModelIndex& parent) const
if (!parent.isValid()) {
return mRootNode.children.size();
}
+
+ if (parent.column() != 0) {
+ return 0;
+ }
+
Node *node = extractNode(parent);
return node->children.size();
}
diff --git a/korganizer/views/collectionview/reparentingmodel.h b/korganizer/views/collectionview/reparentingmodel.h
index 4f88c84..ff66a4b 100644
--- a/korganizer/views/collectionview/reparentingmodel.h
+++ b/korganizer/views/collectionview/reparentingmodel.h
@@ -79,6 +79,7 @@ public:
//Allows the implementation to create proxy nodes as necessary
virtual void checkSourceIndex(const QModelIndex &/* sourceIndex */){};
virtual void checkSourceIndexRemoval(const QModelIndex &/* sourceIndex */){};
+ virtual void updateSourceIndex(const QModelIndex & sourceIndex ){checkSourceIndex(sourceIndex);};
};
public:
diff --git a/korganizer/views/collectionview/tests/CMakeLists.txt b/korganizer/views/collectionview/tests/CMakeLists.txt
index 1053df8..ddfa073 100644
--- a/korganizer/views/collectionview/tests/CMakeLists.txt
+++ b/korganizer/views/collectionview/tests/CMakeLists.txt
@@ -5,6 +5,7 @@ include_directories(
set(reparentingmodeltest_SRCS
reparentingmodeltest.cpp
../reparentingmodel.cpp
+ modeltest.cpp
)
kde4_add_unit_test(reparentingmodeltest NOGUI ${reparentingmodeltest_SRCS})
diff --git a/korganizer/views/collectionview/tests/modeltest.cpp b/korganizer/views/collectionview/tests/modeltest.cpp
new file mode 100644
index 0000000..25022d2
--- /dev/null
+++ b/korganizer/views/collectionview/tests/modeltest.cpp
@@ -0,0 +1,651 @@
+/****************************************************************************
+**
+** Copyright (C) 2007 Trolltech ASA. All rights reserved.
+**
+** This file is part of the Qt Concurrent project on Trolltech Labs.
+**
+** This file may be used under the terms of the GNU General Public
+** License version 2.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of
+** this file. Please review the following information to ensure GNU
+** General Public Licensing requirements will be met:
+** http://www.trolltech.com/products/qt/opensource.html
+**
+** If you are unsure which license is appropriate for your use, please
+** review the following information:
+** http://www.trolltech.com/products/qt/licensing.html or contact the
+** sales department at sales at trolltech.com.
+**
+** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
+** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+**
+****************************************************************************/
+//krazy:excludeall=style
+
+#include "modeltest.h"
+
+#include <QtGui>
+
+Q_DECLARE_METATYPE ( QModelIndex )
+
+/*!
+ Connect to all of the models signals. Whenever anything happens recheck everything.
+*/
+ModelTest::ModelTest ( QAbstractItemModel *_model, QObject *parent ) : QObject ( parent ), model ( _model ), fetchingMore ( false )
+{
+ Q_ASSERT ( model );
+
+ connect ( model, SIGNAL (columnsAboutToBeInserted(QModelIndex,int,int)),
+ this, SLOT (runAllTests()) );
+ connect ( model, SIGNAL (columnsAboutToBeRemoved(QModelIndex,int,int)),
+ this, SLOT (runAllTests()) );
+ connect ( model, SIGNAL (columnsInserted(QModelIndex,int,int)),
+ this, SLOT (runAllTests()) );
+ connect ( model, SIGNAL (columnsRemoved(QModelIndex,int,int)),
+ this, SLOT (runAllTests()) );
+ connect ( model, SIGNAL (dataChanged(QModelIndex,QModelIndex)),
+ this, SLOT (runAllTests()) );
+ connect ( model, SIGNAL (headerDataChanged(Qt::Orientation,int,int)),
+ this, SLOT (runAllTests()) );
+ connect ( model, SIGNAL (layoutAboutToBeChanged()), this, SLOT (runAllTests()) );
+ connect ( model, SIGNAL (layoutChanged()), this, SLOT (runAllTests()) );
+ connect ( model, SIGNAL (modelReset()), this, SLOT (runAllTests()) );
+ connect ( model, SIGNAL (modelAboutToBeReset()), this, SLOT (modelAboutToBeReset()) );
+ connect ( model, SIGNAL (modelReset()), this, SLOT (modelReset()) );
+ connect ( model, SIGNAL (rowsAboutToBeInserted(QModelIndex,int,int)),
+ this, SLOT (runAllTests()) );
+ connect ( model, SIGNAL (rowsAboutToBeRemoved(QModelIndex,int,int)),
+ this, SLOT (runAllTests()) );
+ connect ( model, SIGNAL (rowsInserted(QModelIndex,int,int)),
+ this, SLOT (runAllTests()) );
+ connect ( model, SIGNAL (rowsRemoved(QModelIndex,int,int)),
+ this, SLOT (runAllTests()) );
+
+ // Special checks for inserting/removing
+ connect ( model, SIGNAL (layoutAboutToBeChanged()),
+ this, SLOT (layoutAboutToBeChanged()) );
+ connect ( model, SIGNAL (layoutChanged()),
+ this, SLOT (layoutChanged()) );
+
+ connect ( model, SIGNAL (rowsAboutToBeInserted(QModelIndex,int,int)),
+ this, SLOT (rowsAboutToBeInserted(QModelIndex,int,int)) );
+ connect ( model, SIGNAL (rowsAboutToBeRemoved(QModelIndex,int,int)),
+ this, SLOT (rowsAboutToBeRemoved(QModelIndex,int,int)) );
+ connect ( model, SIGNAL (rowsInserted(QModelIndex,int,int)),
+ this, SLOT (rowsInserted(QModelIndex,int,int)) );
+ connect ( model, SIGNAL (rowsRemoved(QModelIndex,int,int)),
+ this, SLOT (rowsRemoved(QModelIndex,int,int)) );
+
+ connect ( model, SIGNAL (rowsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int)),
+ this, SLOT (rowsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int)) );
+ connect ( model, SIGNAL (rowsMoved(QModelIndex,int,int,QModelIndex,int)),
+ this, SLOT (rowsMoved(QModelIndex,int,int,QModelIndex,int)) );
+
+ runAllTests();
+}
+
+void ModelTest::runAllTests()
+{
+
+ qDebug() << "runAllTests";
+ if ( fetchingMore )
+ return;
+ nonDestructiveBasicTest();
+ rowCount();
+ columnCount();
+ hasIndex();
+ index();
+ parent();
+ data();
+}
+
+/*!
+ nonDestructiveBasicTest tries to call a number of the basic functions (not all)
+ to make sure the model doesn't outright segfault, testing the functions that makes sense.
+*/
+void ModelTest::nonDestructiveBasicTest()
+{
+ Q_ASSERT ( model->buddy ( QModelIndex() ) == QModelIndex() );
+// model->canFetchMore ( QModelIndex() );
+ Q_ASSERT ( model->columnCount ( QModelIndex() ) >= 0 );
+ Q_ASSERT ( model->data ( QModelIndex() ) == QVariant() );
+// fetchingMore = true;
+// model->fetchMore ( QModelIndex() );
+// fetchingMore = false;
+ Qt::ItemFlags flags = model->flags ( QModelIndex() );
+ Q_ASSERT ( flags == Qt::ItemIsDropEnabled || flags == 0 );
+ Q_UNUSED( flags );
+ model->hasChildren ( QModelIndex() );
+ model->hasIndex ( 0, 0 );
+ model->headerData ( 0, Qt::Horizontal );
+ model->index ( 0, 0 );
+ model->itemData ( QModelIndex() );
+ QVariant cache;
+ model->match ( QModelIndex(), -1, cache );
+ model->mimeTypes();
+ Q_ASSERT ( model->parent ( QModelIndex() ) == QModelIndex() );
+ Q_ASSERT ( model->rowCount() >= 0 );
+ QVariant variant;
+ model->setData ( QModelIndex(), variant, -1 );
+ model->setHeaderData ( -1, Qt::Horizontal, QVariant() );
+ model->setHeaderData ( 999999, Qt::Horizontal, QVariant() );
+ QMap<int, QVariant> roles;
+ model->sibling ( 0, 0, QModelIndex() );
+ model->span ( QModelIndex() );
+ model->supportedDropActions();
+}
+
+/*!
+ Tests model's implementation of QAbstractItemModel::rowCount() and hasChildren()
+
+ Models that are dynamically populated are not as fully tested here.
+ */
+void ModelTest::rowCount()
+{
+// qDebug() << "rc";
+ // check top row
+ QModelIndex topIndex = model->index ( 0, 0, QModelIndex() );
+ int rows = model->rowCount ( topIndex );
+ Q_ASSERT ( rows >= 0 );
+ if ( rows > 0 )
+ Q_ASSERT ( model->hasChildren ( topIndex ) == true );
+
+ QModelIndex secondLevelIndex = model->index ( 0, 0, topIndex );
+ if ( secondLevelIndex.isValid() ) { // not the top level
+ // check a row count where parent is valid
+ rows = model->rowCount ( secondLevelIndex );
+ Q_ASSERT ( rows >= 0 );
+ if ( rows > 0 )
+ Q_ASSERT ( model->hasChildren ( secondLevelIndex ) == true );
+ }
+
+ // The models rowCount() is tested more extensively in checkChildren(),
+ // but this catches the big mistakes
+}
+
+/*!
+ Tests model's implementation of QAbstractItemModel::columnCount() and hasChildren()
+ */
+void ModelTest::columnCount()
+{
+ // check top row
+ QModelIndex topIndex = model->index ( 0, 0, QModelIndex() );
+ Q_ASSERT ( model->columnCount ( topIndex ) >= 0 );
+
+ // check a column count where parent is valid
+ QModelIndex childIndex = model->index ( 0, 0, topIndex );
+ if ( childIndex.isValid() )
+ Q_ASSERT ( model->columnCount ( childIndex ) >= 0 );
+
+ // columnCount() is tested more extensively in checkChildren(),
+ // but this catches the big mistakes
+}
+
+/*!
+ Tests model's implementation of QAbstractItemModel::hasIndex()
+ */
+void ModelTest::hasIndex()
+{
+// qDebug() << "hi";
+ // Make sure that invalid values returns an invalid index
+ Q_ASSERT ( model->hasIndex ( -2, -2 ) == false );
+ Q_ASSERT ( model->hasIndex ( -2, 0 ) == false );
+ Q_ASSERT ( model->hasIndex ( 0, -2 ) == false );
+
+ int rows = model->rowCount();
+ int columns = model->columnCount();
+
+ // check out of bounds
+ Q_ASSERT ( model->hasIndex ( rows, columns ) == false );
+ Q_ASSERT ( model->hasIndex ( rows + 1, columns + 1 ) == false );
+
+ Q_UNUSED( columns );
+
+ if ( rows > 0 )
+ Q_ASSERT ( model->hasIndex ( 0, 0 ) == true );
+
+ // hasIndex() is tested more extensively in checkChildren(),
+ // but this catches the big mistakes
+}
+
+/*!
+ Tests model's implementation of QAbstractItemModel::index()
+ */
+void ModelTest::index()
+{
+// qDebug() << "i";
+ // Make sure that invalid values returns an invalid index
+ Q_ASSERT ( model->index ( -2, -2 ) == QModelIndex() );
+ Q_ASSERT ( model->index ( -2, 0 ) == QModelIndex() );
+ Q_ASSERT ( model->index ( 0, -2 ) == QModelIndex() );
+
+ int rows = model->rowCount();
+ int columns = model->columnCount();
+
+ if ( rows == 0 )
+ return;
+
+ // Catch off by one errors
+ Q_ASSERT ( model->index ( rows, columns ) == QModelIndex() );
+ Q_ASSERT ( model->index ( 0, 0 ).isValid() == true );
+
+ Q_UNUSED( columns );
+
+ // Make sure that the same index is *always* returned
+ QModelIndex a = model->index ( 0, 0 );
+ QModelIndex b = model->index ( 0, 0 );
+ Q_ASSERT ( a == b );
+
+ // index() is tested more extensively in checkChildren(),
+ // but this catches the big mistakes
+}
+
+/*!
+ Tests model's implementation of QAbstractItemModel::parent()
+ */
+void ModelTest::parent()
+{
+// qDebug() << "p";
+ // Make sure the model wont crash and will return an invalid QModelIndex
+ // when asked for the parent of an invalid index.
+ Q_ASSERT ( model->parent ( QModelIndex() ) == QModelIndex() );
+
+ if ( model->rowCount() == 0 )
+ return;
+
+ // Column 0 | Column 1 |
+ // QModelIndex() | |
+ // \- topIndex | topIndex1 |
+ // \- childIndex | childIndex1 |
+
+ // Common error test #1, make sure that a top level index has a parent
+ // that is a invalid QModelIndex.
+ QModelIndex topIndex = model->index ( 0, 0, QModelIndex() );
+ Q_ASSERT ( model->parent ( topIndex ) == QModelIndex() );
+
+ // Common error test #2, make sure that a second level index has a parent
+ // that is the first level index.
+ if ( model->rowCount ( topIndex ) > 0 ) {
+ QModelIndex childIndex = model->index ( 0, 0, topIndex );
+ Q_ASSERT ( model->parent ( childIndex ) == topIndex );
+ }
+
+ // Common error test #3, the second column should NOT have the same children
+ // as the first column in a row.
+ // Usually the second column shouldn't have children.
+ QModelIndex topIndex1 = model->index ( 0, 1, QModelIndex() );
+ if ( model->rowCount ( topIndex1 ) > 0 ) {
+ QModelIndex childIndex = model->index ( 0, 0, topIndex );
+ QModelIndex childIndex1 = model->index ( 0, 0, topIndex1 );
+ Q_ASSERT ( childIndex != childIndex1 );
+ }
+
+ // Full test, walk n levels deep through the model making sure that all
+ // parent's children correctly specify their parent.
+ checkChildren ( QModelIndex() );
+}
+
+/*!
+ Called from the parent() test.
+
+ A model that returns an index of parent X should also return X when asking
+ for the parent of the index.
+
+ This recursive function does pretty extensive testing on the whole model in an
+ effort to catch edge cases.
+
+ This function assumes that rowCount(), columnCount() and index() already work.
+ If they have a bug it will point it out, but the above tests should have already
+ found the basic bugs because it is easier to figure out the problem in
+ those tests then this one.
+ */
+void ModelTest::checkChildren ( const QModelIndex &parent, int currentDepth )
+{
+ // First just try walking back up the tree.
+ QModelIndex p = parent;
+ while ( p.isValid() )
+ p = p.parent();
+
+ // For models that are dynamically populated
+// if ( model->canFetchMore ( parent ) ) {
+// fetchingMore = true;
+// model->fetchMore ( parent );
+// fetchingMore = false;
+// }
+
+ int rows = model->rowCount ( parent );
+ int columns = model->columnCount ( parent );
+
+ //qDebug() << "checkChildren" << "parent=" << model->data ( parent ).value<QString>()
+ //<< "current count of parent=" << rows << "columns=" << columns;
+ QString indent= QLatin1String("");
+ for (int i=0; i<currentDepth; i++) {
+ indent += QLatin1String(" ");
+ }
+ qDebug() << QString(indent+QLatin1String("+")).toUtf8() << model->data ( parent ).value<QString>();
+
+ if ( rows > 0 )
+ {
+
+ Q_ASSERT(parent.column() <= 0);
+ Q_ASSERT ( model->hasChildren ( parent ) );
+ }
+
+ // Some further testing against rows(), columns(), and hasChildren()
+ Q_ASSERT ( rows >= 0 );
+ Q_ASSERT ( columns >= 0 );
+ if ( rows > 0 )
+ Q_ASSERT ( model->hasChildren ( parent ) == true );
+
+ //qDebug() << "parent:" << model->data(parent).toString() << "rows:" << rows
+ // << "columns:" << columns << "parent column:" << parent.column();
+
+ Q_ASSERT ( model->hasIndex ( rows, 0, parent ) == false );
+ Q_ASSERT ( model->index(rows, 0, parent).isValid() == false );
+ for ( int r = 0; r < rows; ++r ) {
+// if ( model->canFetchMore ( parent ) ) {
+// fetchingMore = true;
+// model->fetchMore ( parent );
+// fetchingMore = false;
+// }
+ Q_ASSERT ( model->hasIndex ( r, columns, parent ) == false );
+ Q_ASSERT ( model->index(r, columns, parent).isValid() == false );
+ for ( int c = 0; c < columns; ++c ) {
+ Q_ASSERT ( model->hasIndex ( r, c, parent ) == true );
+ QModelIndex index = model->index ( r, c, parent );
+ // rowCount() and columnCount() said that it existed...
+ Q_ASSERT ( index.isValid() == true );
+
+ // index() should always return the same index when called twice in a row
+ QModelIndex modifiedIndex = model->index ( r, c, parent );
+ Q_ASSERT ( index == modifiedIndex );
+
+ // Make sure we get the same index if we request it twice in a row
+ QModelIndex a = model->index ( r, c, parent );
+ QModelIndex b = model->index ( r, c, parent );
+ Q_ASSERT ( a == b );
+
+ // Some basic checking on the index that is returned
+ Q_ASSERT ( index.model() == model );
+ Q_ASSERT ( index.row() == r );
+ Q_ASSERT ( index.column() == c );
+ // While you can technically return a QVariant usually this is a sign
+ // of an bug in data() Disable if this really is ok in your model.
+ //qDebug() << index << index.data() << index.parent();
+ Q_ASSERT ( model->data ( index, Qt::DisplayRole ).isValid() == true );
+
+ // If the next test fails here is some somewhat useful debug you play with.
+ /*
+ if (model->parent(index) != parent) {
+ qDebug() << r << c << currentDepth << model->data(index).toString()
+ << model->data(parent).toString();
+ qDebug() << index << parent << model->parent(index);
+ // And a view that you can even use to show the model.
+ //QTreeView view;
+ //view.setModel(model);
+ //view.show();
+ }*/
+
+ // Check that we can get back our real parent.
+ Q_ASSERT ( model->parent ( index ) == parent );
+
+ // recursively go down the children
+ if ( model->hasChildren ( index ) && currentDepth < 10 ) {
+ //qDebug() << r << c << "has children" << model->rowCount(index);
+ checkChildren ( index, currentDepth+1 );
+ //qDebug() << "----";
+ }/* else { if (currentDepth >= 10) qDebug() << "checked 10 deep"; };*/
+ else { qDebug() << QString(indent+QLatin1String(" |---")).toUtf8() << model->data ( index ).value<QString>(); }
+
+ // make sure that after testing the children that the index doesn't change.
+ QModelIndex newerIndex = model->index ( r, c, parent );
+ Q_ASSERT ( index == newerIndex );
+ }
+ }
+}
+
+/*!
+ Tests model's implementation of QAbstractItemModel::data()
+ */
+void ModelTest::data()
+{
+ // Invalid index should return an invalid qvariant
+ Q_ASSERT ( !model->data ( QModelIndex() ).isValid() );
+
+ if ( model->rowCount() == 0 )
+ return;
+
+ // A valid index should have a valid QVariant data
+ Q_ASSERT ( model->index ( 0, 0 ).isValid() );
+
+ // shouldn't be able to set data on an invalid index
+ Q_ASSERT ( model->setData ( QModelIndex(), QLatin1String ( "foo" ), Qt::DisplayRole ) == false );
+
+ // General Purpose roles that should return a QString
+ QVariant variant = model->data ( model->index ( 0, 0 ), Qt::ToolTipRole );
+ if ( variant.isValid() ) {
+ Q_ASSERT ( qVariantCanConvert<QString> ( variant ) );
+ }
+ variant = model->data ( model->index ( 0, 0 ), Qt::StatusTipRole );
+ if ( variant.isValid() ) {
+ Q_ASSERT ( qVariantCanConvert<QString> ( variant ) );
+ }
+ variant = model->data ( model->index ( 0, 0 ), Qt::WhatsThisRole );
+ if ( variant.isValid() ) {
+ Q_ASSERT ( qVariantCanConvert<QString> ( variant ) );
+ }
+
+ // General Purpose roles that should return a QSize
+ variant = model->data ( model->index ( 0, 0 ), Qt::SizeHintRole );
+ if ( variant.isValid() ) {
+ Q_ASSERT ( qVariantCanConvert<QSize> ( variant ) );
+ }
+
+ // General Purpose roles that should return a QFont
+ QVariant fontVariant = model->data ( model->index ( 0, 0 ), Qt::FontRole );
+ if ( fontVariant.isValid() ) {
+ Q_ASSERT ( qVariantCanConvert<QFont> ( fontVariant ) );
+ }
+
+ // Check that the alignment is one we know about
+ QVariant textAlignmentVariant = model->data ( model->index ( 0, 0 ), Qt::TextAlignmentRole );
+ if ( textAlignmentVariant.isValid() ) {
+ int alignment = textAlignmentVariant.toInt();
+ Q_ASSERT ( alignment == ( alignment & ( Qt::AlignHorizontal_Mask | Qt::AlignVertical_Mask ) ) );
+ Q_UNUSED( alignment );
+ }
+
+ // General Purpose roles that should return a QColor
+ QVariant colorVariant = model->data ( model->index ( 0, 0 ), Qt::BackgroundColorRole );
+ if ( colorVariant.isValid() ) {
+ Q_ASSERT ( qVariantCanConvert<QColor> ( colorVariant ) );
+ }
+
+ colorVariant = model->data ( model->index ( 0, 0 ), Qt::TextColorRole );
+ if ( colorVariant.isValid() ) {
+ Q_ASSERT ( qVariantCanConvert<QColor> ( colorVariant ) );
+ }
+
+ // Check that the "check state" is one we know about.
+ QVariant checkStateVariant = model->data ( model->index ( 0, 0 ), Qt::CheckStateRole );
+ if ( checkStateVariant.isValid() ) {
+ int state = checkStateVariant.toInt();
+ Q_ASSERT ( state == Qt::Unchecked ||
+ state == Qt::PartiallyChecked ||
+ state == Qt::Checked );
+ Q_UNUSED( state );
+ }
+}
+
+/*!
+ Store what is about to be inserted to make sure it actually happens
+
+ \sa rowsInserted()
+ */
+void ModelTest::rowsAboutToBeInserted ( const QModelIndex &parent, int start, int end )
+{
+// Q_UNUSED(end);
+ qDebug() << "rowsAboutToBeInserted" << "start=" << start << "end=" << end << "parent=" << model->data ( parent ).value<QString>()
+ << "current count of parent=" << model->rowCount ( parent ); // << "display of last=" << model->data( model->index(start-1, 0, parent) );
+// qDebug() << model->index(start-1, 0, parent) << model->data( model->index(start-1, 0, parent) );
+ Changing c;
+ c.parent = parent;
+ c.oldSize = model->rowCount ( parent );
+ c.last = model->data ( model->index ( start - 1, 0, parent ) );
+ c.next = model->data ( model->index ( start, 0, parent ) );
+ insert.push ( c );
+}
+
+/*!
+ Confirm that what was said was going to happen actually did
+
+ \sa rowsAboutToBeInserted()
+ */
+void ModelTest::rowsInserted ( const QModelIndex & parent, int start, int end )
+{
+ Changing c = insert.pop();
+ Q_ASSERT ( c.parent == parent );
+ qDebug() << "rowsInserted" << "start=" << start << "end=" << end << "oldsize=" << c.oldSize
+ << "parent=" << model->data ( parent ).value<QString>() << "current rowcount=" << model->rowCount ( parent );
+
+ for (int ii=start; ii <= end; ii++)
+ {
+ qDebug() << "itemWasInserted:" << model->data ( model->index ( ii, 0, parent ));
+ }
+
+ Q_ASSERT ( c.oldSize + ( end - start + 1 ) == model->rowCount ( parent ) );
+ Q_ASSERT ( c.last == model->data ( model->index ( start - 1, 0, c.parent ) ) );
+ /*
+ if (c.next != model->data(model->index(end + 1, 0, c.parent))) {
+ qDebug() << start << end;
+ for (int i=0; i < model->rowCount(); ++i)
+ qDebug() << model->index(i, 0).data().toString();
+ qDebug() << c.next << model->data(model->index(end + 1, 0, c.parent));
+ }
+ */
+ Q_ASSERT ( c.next == model->data ( model->index ( end + 1, 0, c.parent ) ) );
+}
+
+
+void ModelTest::modelAboutToBeReset()
+{
+ qDebug() << "@@@@@@@@@@@" << "modelAboutToBeReset";
+}
+
+void ModelTest::modelReset()
+{
+ qDebug() << "@@@@@@@@@@@" << "modelReset";
+}
+
+void ModelTest::layoutAboutToBeChanged()
+{
+ for ( int i = 0; i < qBound ( 0, model->rowCount(), 100 ); ++i )
+ changing.append ( QPersistentModelIndex ( model->index ( i, 0 ) ) );
+}
+
+void ModelTest::layoutChanged()
+{
+ for ( int i = 0; i < changing.count(); ++i ) {
+ QPersistentModelIndex p = changing[i];
+ Q_ASSERT ( p == model->index ( p.row(), p.column(), p.parent() ) );
+ }
+ changing.clear();
+}
+
+void ModelTest::rowsAboutToBeMoved(const QModelIndex &srcParent, int start, int end, const QModelIndex &destParent, int destinationRow )
+{
+ qDebug() << "rowsAboutToBeMoved" << srcParent << start << end << destParent << destinationRow;
+ Changing cs;
+ cs.parent = srcParent;
+ cs.oldSize = model->rowCount ( srcParent );
+ cs.last = model->data ( model->index ( start - 1, 0, srcParent ) );
+ qDebug() << start << model->data ( model->index (start , 0, srcParent)).toString();
+ cs.next = model->data ( model->index ( end + 1, 0, srcParent ) );
+ remove.push ( cs );
+ Changing cd;
+ cd.parent = destParent;
+ cd.oldSize = model->rowCount ( destParent );
+ cd.last = model->data ( model->index ( destinationRow - 1, 0, destParent ) );
+ cd.next = model->data ( model->index ( destinationRow, 0, destParent ) );
+ insert.push ( cd );
+}
+
+void ModelTest::rowsMoved(const QModelIndex &srcParent, int start, int end, const QModelIndex &destParent, int destinationRow )
+{
+ qDebug() << "rowsMoved" << srcParent << start << end << destParent << destinationRow;
+
+ Changing cd = insert.pop();
+ Q_ASSERT ( cd.parent == destParent );
+ if (srcParent == destParent)
+ {
+ Q_ASSERT ( cd.oldSize == model->rowCount ( destParent ) );
+
+ // TODO: Find out what I can assert here about last and next.
+// Q_ASSERT ( cd.last == model->data ( model->index ( destinationRow - 1, 0, cd.parent ) ) );
+// Q_ASSERT ( cd.next == model->data ( model->index ( destinationRow + (end - start + 1), 0, cd.parent ) ) );
+
+
+ }
+ else
+ {
+ Q_ASSERT ( cd.oldSize + ( end - start + 1 ) == model->rowCount ( destParent ) );
+
+ Q_ASSERT ( cd.last == model->data ( model->index ( destinationRow - 1, 0, cd.parent ) ) );
+ Q_ASSERT ( cd.next == model->data ( model->index ( destinationRow + (end - start + 1), 0, cd.parent ) ) );
+ }
+ Changing cs = remove.pop();
+
+ Q_ASSERT ( cs.parent == srcParent );
+ if (srcParent == destParent)
+ {
+ Q_ASSERT ( cs.oldSize == model->rowCount ( srcParent ) );
+ }
+ else
+ {
+ Q_ASSERT ( cs.oldSize - ( end - start + 1 ) == model->rowCount ( srcParent ) );
+ qDebug() << start - 1 << model->data ( model->index ( start - 1, 0, srcParent ) ).toString() << cs.last.toString();
+ qDebug() << start << model->data ( model->index ( start , 0, srcParent ) ).toString() << cs.next.toString();
+ Q_ASSERT ( cs.last == model->data ( model->index ( start - 1, 0, srcParent ) ) );
+ qDebug() << cs.next << model->data ( model->index ( start, 0, srcParent ) );
+ Q_ASSERT ( cs.next == model->data ( model->index ( start, 0, srcParent ) ) );
+ }
+
+}
+
+/*!
+ Store what is about to be inserted to make sure it actually happens
+
+ \sa rowsRemoved()
+ */
+void ModelTest::rowsAboutToBeRemoved ( const QModelIndex &parent, int start, int end )
+{
+qDebug() << "ratbr" << parent << start << end;
+ for (int ii=start; ii <= end; ii++)
+ {
+ qDebug() << "itemwillbe removed:" << model->data ( model->index ( ii, 0, parent ));
+ }
+
+
+ Changing c;
+ c.parent = parent;
+ c.oldSize = model->rowCount ( parent );
+ c.last = model->data ( model->index ( start - 1, 0, parent ) );
+ c.next = model->data ( model->index ( end + 1, 0, parent ) );
+ remove.push ( c );
+}
+
+/*!
+ Confirm that what was said was going to happen actually did
+
+ \sa rowsAboutToBeRemoved()
+ */
+void ModelTest::rowsRemoved ( const QModelIndex & parent, int start, int end )
+{
+ qDebug() << "rr" << parent << start << end;
+ Changing c = remove.pop();
+ Q_ASSERT ( c.parent == parent );
+ Q_ASSERT ( c.oldSize - ( end - start + 1 ) == model->rowCount ( parent ) );
+ Q_ASSERT ( c.last == model->data ( model->index ( start - 1, 0, c.parent ) ) );
+ Q_ASSERT ( c.next == model->data ( model->index ( start, 0, c.parent ) ) );
+}
+
diff --git a/korganizer/views/collectionview/tests/modeltest.h b/korganizer/views/collectionview/tests/modeltest.h
new file mode 100644
index 0000000..0ed70be
--- /dev/null
+++ b/korganizer/views/collectionview/tests/modeltest.h
@@ -0,0 +1,80 @@
+/****************************************************************************
+**
+** Copyright (C) 2007 Trolltech ASA. All rights reserved.
+**
+** This file is part of the Qt Concurrent project on Trolltech Labs.
+**
+** This file may be used under the terms of the GNU General Public
+** License version 2.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of
+** this file. Please review the following information to ensure GNU
+** General Public Licensing requirements will be met:
+** http://www.trolltech.com/products/qt/opensource.html
+**
+** If you are unsure which license is appropriate for your use, please
+** review the following information:
+** http://www.trolltech.com/products/qt/licensing.html or contact the
+** sales department at sales at trolltech.com.
+**
+** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
+** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+**
+****************************************************************************/
+//krazy:excludeall=style
+
+#ifndef MODELTEST_H
+#define MODELTEST_H
+
+#include <QtCore/QObject>
+#include <QtCore/QAbstractItemModel>
+#include <QtCore/QStack>
+
+class ModelTest : public QObject
+{
+ Q_OBJECT
+
+public:
+ explicit ModelTest( QAbstractItemModel *model, QObject *parent = 0 );
+
+private Q_SLOTS:
+ void nonDestructiveBasicTest();
+ void rowCount();
+ void columnCount();
+ void hasIndex();
+ void index();
+ void parent();
+ void data();
+
+protected Q_SLOTS:
+ void runAllTests();
+ void layoutAboutToBeChanged();
+ void layoutChanged();
+ void modelAboutToBeReset();
+ void modelReset();
+ void rowsAboutToBeInserted( const QModelIndex &parent, int start, int end );
+ void rowsInserted( const QModelIndex & parent, int start, int end );
+ void rowsAboutToBeRemoved( const QModelIndex &parent, int start, int end );
+ void rowsRemoved( const QModelIndex & parent, int start, int end );
+ void rowsAboutToBeMoved ( const QModelIndex &, int, int, const QModelIndex &, int);
+ void rowsMoved ( const QModelIndex &, int, int, const QModelIndex &, int );
+
+private:
+ void checkChildren( const QModelIndex &parent, int currentDepth = 0 );
+
+ QAbstractItemModel *model;
+
+ struct Changing {
+ QModelIndex parent;
+ int oldSize;
+ QVariant last;
+ QVariant next;
+ };
+ QStack<Changing> insert;
+ QStack<Changing> remove;
+
+ bool fetchingMore;
+
+ QList<QPersistentModelIndex> changing;
+};
+
+#endif
diff --git a/korganizer/views/collectionview/tests/reparentingmodeltest.cpp b/korganizer/views/collectionview/tests/reparentingmodeltest.cpp
index f7ddd70..6fb5526 100644
--- a/korganizer/views/collectionview/tests/reparentingmodeltest.cpp
+++ b/korganizer/views/collectionview/tests/reparentingmodeltest.cpp
@@ -33,7 +33,9 @@ public:
DummyNode(ReparentingModel &personModel, const QString &name, const QString &data=QString())
: ReparentingModel::Node(personModel),
mName(name),
- mData(data)
+ mData(data),
+ mUid(name),
+ mParent(QLatin1String("orphan"))
{}
virtual ~DummyNode(){};
@@ -41,15 +43,21 @@ public:
virtual bool operator==(const Node &node) const {
const DummyNode *dummyNode = dynamic_cast<const DummyNode*>(&node);
if (dummyNode) {
- return (dummyNode->mName == mName);
+ return (dummyNode->mUid == mUid);
}
return false;
}
+ QString mUid;
+ QString mParent;
private:
virtual QVariant data(int role) const {
if (role == Qt::DisplayRole) {
- return mName;
+ if (mName != mUid) {
+ return QString(mUid+QLatin1Char('-')+mName);
+ } else {
+ return mName;
+ }
} else if (role == Qt::UserRole) {
return mData;
}
@@ -61,11 +69,11 @@ private:
return false;
}
virtual bool isDuplicateOf(const QModelIndex& sourceIndex) {
- return (sourceIndex.data().toString() == mName);
+ return (sourceIndex.data().toString() == mUid);
}
virtual bool adopts(const QModelIndex& sourceIndex) {
- return sourceIndex.data().toString().contains(QLatin1String("orphan"));
+ return sourceIndex.data().toString().contains(mParent);
}
QString mName;
@@ -148,6 +156,7 @@ private Q_SLOTS:
void testNestedDeduplicateProxyNodeFirst();
void testUpdateNode();
void testReparent();
+ void testReparentSubcollections();
void testReparentResetWithoutCrash();
void testAddReparentedSourceItem();
void testRemoveReparentedSourceItem();
@@ -413,6 +422,63 @@ void ReparentingModelTest::testReparent()
QCOMPARE(reparentingModel.rowCount(getIndex("proxy1", reparentingModel)), 1);
}
+void ReparentingModelTest::testReparentSubcollections()
+{
+ QStandardItemModel sourceModel;
+ ReparentingModel reparentingModel;
+ reparentingModel.setSourceModel(&sourceModel);
+
+ /* Source structure
+ -- +
+ -- + orphan
+ -- + col1
+ -- sub1
+ -- sub2
+ -- col2
+ */
+ sourceModel.appendRow(new QStandardItem(QLatin1String("orphan")));
+ sourceModel.item(0,0)->appendRow(new QStandardItem(QLatin1String("col1")));
+ sourceModel.item(0,0)->child(0,0)->appendRow(new QStandardItem(QLatin1String("sub1")));
+ sourceModel.item(0,0)->child(0,0)->appendRow(new QStandardItem(QLatin1String("sub2")));
+ sourceModel.item(0,0)->appendRow(new QStandardItem(QLatin1String("col2")));
+
+ DummyNode *node = new DummyNode(reparentingModel, QLatin1String("col1"));
+ node->mUid = QLatin1String("uid");
+ node->mParent = QLatin1String("col");
+
+ /* new srutcure:
+ -- +
+ -- orphan
+ -- + uid-col1
+ -- + col1
+ -- sub1
+ -- sub2
+ -- col2
+ */
+ reparentingModel.addNode(ReparentingModel::Node::Ptr(node));
+
+ QTest::qWait(0);
+
+ QCOMPARE(reparentingModel.rowCount(QModelIndex()), 2);
+ QVERIFY(getIndex("col1", reparentingModel).isValid());
+ QCOMPARE(getIndex("col1", reparentingModel).parent(), getIndex("uid-col1", reparentingModel));
+ QCOMPARE(reparentingModel.rowCount(getIndex("col1", reparentingModel)), 2);
+ QCOMPARE(reparentingModel.rowCount(getIndex("uid-col1", reparentingModel)), 2);
+
+ node = new DummyNode(reparentingModel, QLatin1String("xxx"));
+ node->mUid = QLatin1String("uid");
+ node->mParent = QLatin1String("col");
+
+ // same structure but new data
+ reparentingModel.updateNode(ReparentingModel::Node::Ptr(node));
+
+ QTest::qWait(0);
+
+ QCOMPARE(getIndex("col1", reparentingModel).parent(), getIndex("uid-xxx", reparentingModel));
+ QCOMPARE(reparentingModel.rowCount(getIndex("col1", reparentingModel)), 2);
+ QCOMPARE(reparentingModel.rowCount(getIndex("uid-xxx", reparentingModel)), 2);
+}
+
/*
* This test ensures we properly deal with reparented source nodes if the model is reset.
* This is important since source nodes are removed during the model reset while the proxy nodes (to which the source nodes have been reparented) remain.
@@ -677,3 +743,4 @@ void ReparentingModelTest::testRemoveNodeByNodeManagerWithDataChanged()
QTEST_MAIN(ReparentingModelTest)
#include "reparentingmodeltest.moc"
+
\ No newline at end of file
More information about the commits
mailing list