diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 13cf4f2f1..a27554bb8 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -13,7 +13,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-include_directories( ${CMAKE_CURRENT_BINARY_DIR} )
+include_directories( ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} )
configure_file( config-keepassx.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-keepassx.h )
@@ -26,6 +26,7 @@ set(keepassx_SOURCES
core/Parser.cpp
core/TimeInfo.cpp
core/Uuid.cpp
+ gui/GroupModel.cpp
)
automoc4_add_library( keepassx_core STATIC ${keepassx_SOURCES} )
diff --git a/src/core/Database.cpp b/src/core/Database.cpp
index 368c96683..bc36693fa 100644
--- a/src/core/Database.cpp
+++ b/src/core/Database.cpp
@@ -35,7 +35,7 @@ Group* Database::rootGroup()
void Database::setRootGroup(Group* group)
{
- Q_ASSERT(group == 0 || group->parent() == this);
+ Q_ASSERT(group == 0 || group->database() == this);
m_rootGroup = group;
}
diff --git a/src/core/Group.cpp b/src/core/Group.cpp
index dc4359484..3a9561c82 100644
--- a/src/core/Group.cpp
+++ b/src/core/Group.cpp
@@ -122,6 +122,11 @@ void Group::setLastTopVisibleEntry(Entry* entry)
m_lastTopVisibleEntry = entry;
}
+const Group* Group::parentGroup() const
+{
+ return m_parent;
+}
+
void Group::setParent(Group* parent)
{
Q_ASSERT(parent != 0);
@@ -168,12 +173,32 @@ const Database* Group::database() const
return m_db;
}
-QList Group::children() const
+QList Group::children() const
+{
+ QList constChildren;
+ Q_FOREACH (Group* group, m_children) {
+ constChildren << group;
+ }
+
+ return constChildren;
+}
+
+QList Group::children()
{
return m_children;
}
-QList Group::entries() const
+QList Group::entries() const
+{
+ QList constEntries;
+ Q_FOREACH (Entry* entry, m_entries) {
+ constEntries << entry;
+ }
+
+ return constEntries;
+}
+
+QList Group::entries()
{
return m_entries;
}
diff --git a/src/core/Group.h b/src/core/Group.h
index 7f851443d..7140df4c5 100644
--- a/src/core/Group.h
+++ b/src/core/Group.h
@@ -51,12 +51,15 @@ public:
void setDefaultAutoTypeSequence(const QString& sequence);
void setLastTopVisibleEntry(Entry* entry);
+ const Group* parentGroup() const;
void setParent(Group* parent);
void setParent(Database* db);
const Database* database() const;
- QList children() const;
- QList entries() const;
+ QList children() const;
+ QList children();
+ QList entries() const;
+ QList entries();
void addEntry(Entry* entry);
void removeEntry(Entry* entry);
diff --git a/src/gui/GroupModel.cpp b/src/gui/GroupModel.cpp
new file mode 100644
index 000000000..2092438bf
--- /dev/null
+++ b/src/gui/GroupModel.cpp
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2010 Felix Geyer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 or (at your option)
+ * version 3 of the License.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include "GroupModel.h"
+
+#include "core/Group.h"
+
+GroupModel::GroupModel(const Group* rootGroup, QObject* parent) : QAbstractItemModel(parent)
+{
+ m_root = rootGroup;
+}
+
+void GroupModel::setRootGroup(const Group* group)
+{
+ m_root = group;
+}
+
+int GroupModel::rowCount(const QModelIndex& parent) const
+{
+ if (!parent.isValid()) {
+ return 1;
+ }
+ else {
+ const Group* group = groupFromIndex(parent);
+ return group->children().size();
+ }
+}
+
+int GroupModel::columnCount(const QModelIndex& parent) const
+{
+ Q_UNUSED(parent);
+
+ return 1;
+}
+
+QModelIndex GroupModel::index(int row, int column, const QModelIndex& parent) const
+{
+ if (!hasIndex(row, column, parent)) {
+ return QModelIndex();
+ }
+
+ const Group* group;
+
+ if (!parent.isValid()) {
+ group = m_root;
+ }
+ else {
+ group = groupFromIndex(parent)->children().at(row);
+ }
+
+ return createIndex(row, column, group);
+}
+
+QModelIndex GroupModel::parent(const QModelIndex& index) const
+{
+ if (!index.isValid()) {
+ return QModelIndex();
+ }
+
+ const Group* childGroup = groupFromIndex(index);
+ const Group* parentGroup = childGroup->parentGroup();
+
+ if (!parentGroup) {
+ return QModelIndex();
+ }
+ else {
+ const Group* grandParentGroup = parentGroup->parentGroup();
+ if (!grandParentGroup) {
+ return createIndex(0, 0, parentGroup);
+ }
+ else {
+ return createIndex(grandParentGroup->children().indexOf(parentGroup), 0, parentGroup);
+ }
+ }
+}
+
+QVariant GroupModel::data(const QModelIndex& index, int role) const
+{
+ if (!index.isValid() || role != Qt::DisplayRole) {
+ return QVariant();
+ }
+
+ const Group* group = groupFromIndex(index);
+ return group->name();
+}
+
+QVariant GroupModel::headerData(int section, Qt::Orientation orientation, int role) const
+{
+ Q_UNUSED(section);
+ Q_UNUSED(orientation);
+ Q_UNUSED(role);
+
+ return QVariant();
+}
+
+QModelIndex GroupModel::createIndex(int row, int column, const Group* group) const
+{
+ return QAbstractItemModel::createIndex(row, column, const_cast(group));
+}
+
+const Group* GroupModel::groupFromIndex(const QModelIndex& index) const
+{
+ Q_ASSERT(index.internalPointer());
+
+ return static_cast(index.internalPointer());
+}
diff --git a/src/gui/GroupModel.h b/src/gui/GroupModel.h
new file mode 100644
index 000000000..36be8aa0a
--- /dev/null
+++ b/src/gui/GroupModel.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2010 Felix Geyer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 or (at your option)
+ * version 3 of the License.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef KEEPASSX_GROUPMODEL_H
+#define KEEPASSX_GROUPMODEL_H
+
+#include
+
+class Group;
+
+class GroupModel : public QAbstractItemModel
+{
+ Q_OBJECT
+
+public:
+ explicit GroupModel(const Group* rootGroup, QObject* parent = 0);
+ void setRootGroup(const Group* group);
+
+ int rowCount(const QModelIndex& parent = QModelIndex()) const;
+ int columnCount(const QModelIndex& parent = QModelIndex()) const;
+ QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const;
+ QModelIndex parent(const QModelIndex& index) const;
+ QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const;
+ QVariant headerData(int section, Qt::Orientation orientation, int role) const;
+
+private:
+ QModelIndex createIndex(int row, int column, const Group* group) const;
+ const Group* groupFromIndex(const QModelIndex& index) const;
+
+ const Group* m_root;
+};
+
+#endif // KEEPASSX_GROUPMODEL_H
diff --git a/src/main.cpp b/src/main.cpp
index 851f68f18..8fe9ce69c 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -15,9 +15,29 @@
* along with this program. If not, see .
*/
+#include
+#include
+
#include "core/Database.h"
+#include "core/Parser.h"
+#include "gui/GroupModel.h"
+
+#include "../tests/config-keepassx-tests.h"
int main(int argc, char **argv)
{
+ QApplication app(argc, argv);
+ Database* db= new Database();
+ Parser* parser = new Parser(db);
+ parser->parse(QString(KEEPASSX_TEST_DIR).append("/NewDatabase.xml"));
+
+ GroupModel groupModel(db->rootGroup());
+
+ QTreeView view;
+ view.setModel(&groupModel);
+ view.setHeaderHidden(true);
+ view.expandAll();
+ view.show();
+ return app.exec();
}
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index bbb9a0566..63fabf3c7 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -68,3 +68,6 @@ target_link_libraries( testgroup keepassx_core ${QT_QTCORE_LIBRARY} ${QT_QTGUI_L
add_unit_test( testparser TestParser.cpp )
target_link_libraries( testparser keepassx_core ${QT_QTCORE_LIBRARY} ${QT_QTGUI_LIBRARY} ${QT_QTTEST_LIBRARY} )
+
+add_unit_test( testgroupmodel TestGroupModel.cpp modeltest.cpp )
+target_link_libraries( testgroupmodel keepassx_core ${QT_QTCORE_LIBRARY} ${QT_QTGUI_LIBRARY} ${QT_QTTEST_LIBRARY} )
diff --git a/tests/TestGroup.cpp b/tests/TestGroup.cpp
index f1ee2d71c..5a8800653 100644
--- a/tests/TestGroup.cpp
+++ b/tests/TestGroup.cpp
@@ -62,8 +62,8 @@ void TestGroup::testParenting()
QVERIFY(g3->children().size() == 1);
QVERIFY(g4->children().size() == 0);
- QVERIFY(g1->children().contains(g2));
- QVERIFY(g1->children().contains(g3));
+ QVERIFY(g1->children().at(0) == g2);
+ QVERIFY(g1->children().at(1) == g3);
QVERIFY(g3->children().contains(g4));
}
diff --git a/tests/TestGroupModel.cpp b/tests/TestGroupModel.cpp
new file mode 100644
index 000000000..41342d152
--- /dev/null
+++ b/tests/TestGroupModel.cpp
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2010 Felix Geyer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 or (at your option)
+ * version 3 of the License.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include
+
+#include "modeltest.h"
+#include "core/Group.h"
+#include "gui/GroupModel.h"
+
+class TestGroupModel : public QObject
+{
+ Q_OBJECT
+
+private Q_SLOTS:
+ void test();
+};
+
+void TestGroupModel::test()
+{
+ Group* groupRoot = new Group();
+ groupRoot->setName("groupRoot");
+
+ Group* group1 = new Group();
+ group1->setName("group1");
+ group1->setParent(groupRoot);
+
+ Group* group11 = new Group();
+ group11->setName("group11");
+ group11->setParent(group1);
+
+ Group* group12 = new Group();
+ group12->setName("group12");
+ group12->setParent(group1);
+
+ Group* group121 = new Group();
+ group121->setName("group121");
+ group121->setParent(group12);
+
+ Group* group2 = new Group();
+ group2->setName("group2");
+ group2->setParent(groupRoot);
+
+ GroupModel* model = new GroupModel(groupRoot, this);
+
+ new ModelTest(model, this);
+
+ QModelIndex indexRoot = model->index(0, 0);
+ QModelIndex index1 = model->index(0, 0, indexRoot);
+ QModelIndex index11 = model->index(0, 0, index1);
+ QModelIndex index12 = model->index(1, 0, index1);
+ QModelIndex index121 = model->index(0, 0, index12);
+ QModelIndex index2 = model->index(1, 0, indexRoot);
+
+ QVERIFY(model->data(indexRoot) == "groupRoot");
+ QVERIFY(model->data(index1) == "group1");
+ QVERIFY(model->data(index11) == "group11");
+ QVERIFY(model->data(index12) == "group12");
+ QVERIFY(model->data(index121) == "group121");
+ QVERIFY(model->data(index2) == "group2");
+
+ delete groupRoot;
+}
+
+QTEST_MAIN(TestGroupModel);
+
+#include "TestGroupModel.moc"
diff --git a/tests/modeltest.cpp b/tests/modeltest.cpp
new file mode 100644
index 000000000..2a927985f
--- /dev/null
+++ b/tests/modeltest.cpp
@@ -0,0 +1,539 @@
+/****************************************************************************
+**
+** 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-2 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@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.
+**
+****************************************************************************/
+
+#include
+
+#include "modeltest.h"
+
+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(const QModelIndex &, int, int)),
+ this, SLOT(runAllTests()));
+ connect(model, SIGNAL(columnsAboutToBeRemoved(const QModelIndex &, int, int)),
+ this, SLOT(runAllTests()));
+ connect(model, SIGNAL(columnsInserted(const QModelIndex &, int, int)),
+ this, SLOT(runAllTests()));
+ connect(model, SIGNAL(columnsRemoved(const QModelIndex &, int, int)),
+ this, SLOT(runAllTests()));
+ connect(model, SIGNAL(dataChanged(const QModelIndex &, const 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(rowsAboutToBeInserted(const QModelIndex &, int, int)),
+ this, SLOT(runAllTests()));
+ connect(model, SIGNAL(rowsAboutToBeRemoved(const QModelIndex &, int, int)),
+ this, SLOT(runAllTests()));
+ connect(model, SIGNAL(rowsInserted(const QModelIndex &, int, int)),
+ this, SLOT(runAllTests()));
+ connect(model, SIGNAL(rowsRemoved(const 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(const QModelIndex &, int, int)),
+ this, SLOT(rowsAboutToBeInserted(const QModelIndex &, int, int)));
+ connect(model, SIGNAL(rowsAboutToBeRemoved(const QModelIndex &, int, int)),
+ this, SLOT(rowsAboutToBeRemoved(const QModelIndex &, int, int)));
+ connect(model, SIGNAL(rowsInserted(const QModelIndex &, int, int)),
+ this, SLOT(rowsInserted(const QModelIndex &, int, int)));
+ connect(model, SIGNAL(rowsRemoved(const QModelIndex &, int, int)),
+ this, SLOT(rowsRemoved(const QModelIndex &, int, int)));
+
+ runAllTests();
+}
+
+void ModelTest::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);
+ model->hasChildren(QModelIndex());
+ model->hasIndex(0, 0);
+ model->headerData(0, Qt::Horizontal);
+ model->index(0, 0);
+ Q_ASSERT(model->index(-1, -1) == QModelIndex());
+ 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(0, Qt::Horizontal, QVariant());
+ model->setHeaderData(999999, Qt::Horizontal, QVariant());
+ QMap 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()
+{
+ // 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()
+{
+ // 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);
+
+ 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()
+{
+ // 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);
+
+ // 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()
+{
+ // 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);
+
+ if (rows > 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 + 1, 0, parent) == 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 + 1, parent) == 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.
+ //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.
+ QModelIndex p = model->parent(index);
+ //qDebug() << "child:" << index;
+ //qDebug() << p;
+ //qDebug() << 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);
+ }/* else { if (currentDepth >= 10) qDebug() << "checked 10 deep"; };*/
+
+ // 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(variant));
+ }
+ variant = model->data(model->index(0, 0), Qt::StatusTipRole);
+ if (variant.isValid()) {
+ Q_ASSERT(qVariantCanConvert(variant));
+ }
+ variant = model->data(model->index(0, 0), Qt::WhatsThisRole);
+ if (variant.isValid()) {
+ Q_ASSERT(qVariantCanConvert(variant));
+ }
+
+ // General Purpose roles that should return a QSize
+ variant = model->data(model->index(0, 0), Qt::SizeHintRole);
+ if (variant.isValid()) {
+ Q_ASSERT(qVariantCanConvert(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(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 == Qt::AlignLeft ||
+ alignment == Qt::AlignRight ||
+ alignment == Qt::AlignHCenter ||
+ alignment == Qt::AlignJustify ||
+ alignment == Qt::AlignTop ||
+ alignment == Qt::AlignBottom ||
+ alignment == Qt::AlignVCenter ||
+ alignment == Qt::AlignCenter ||
+ alignment == Qt::AlignAbsolute ||
+ alignment == Qt::AlignLeading ||
+ alignment == Qt::AlignTrailing);
+ }
+
+ // General Purpose roles that should return a QColor
+ QVariant colorVariant = model->data(model->index(0, 0), Qt::BackgroundColorRole);
+ if (colorVariant.isValid()) {
+ Q_ASSERT(qVariantCanConvert(colorVariant));
+ }
+
+ colorVariant = model->data(model->index(0, 0), Qt::TextColorRole);
+ if (colorVariant.isValid()) {
+ Q_ASSERT(qVariantCanConvert(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);
+ }
+}
+
+/*!
+ 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);
+ 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);
+ 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::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();
+}
+
+/*!
+ 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)
+{
+ 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)
+{
+ 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/tests/modeltest.h b/tests/modeltest.h
new file mode 100644
index 000000000..783a5214d
--- /dev/null
+++ b/tests/modeltest.h
@@ -0,0 +1,76 @@
+/****************************************************************************
+**
+** 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-2 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@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.
+**
+****************************************************************************/
+
+#ifndef MODELTEST_H
+#define MODELTEST_H
+
+#include
+#include
+#include
+
+class ModelTest : public QObject
+{
+ Q_OBJECT
+
+public:
+ 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 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);
+
+private:
+ void checkChildren(const QModelIndex &parent, int currentDepth = 0);
+
+ QAbstractItemModel *model;
+
+ struct Changing
+ {
+ QModelIndex parent;
+ int oldSize;
+ QVariant last;
+ QVariant next;
+ };
+ QStack insert;
+ QStack remove;
+
+ bool fetchingMore;
+
+ QList changing;
+};
+
+#endif