| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517 | /*=========================================================================  Library:   ctk  Copyright (c) Kitware Inc.   All rights reserved.  Distributed under a BSD License. See LICENSE.txt file.  This software is distributed "AS IS" WITHOUT ANY WARRANTY; without even  the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the above copyright notice for more information.=========================================================================*/#include "ctkModelTester.h"#include <QDebug>#include <QStack>//-----------------------------------------------------------------------------class ctkModelTesterPrivate: public qCTKPrivate<ctkModelTester>{public:  ctkModelTesterPrivate();  QAbstractItemModel *Model;  bool ThrowOnError;  bool NestedInserts;  struct Change  {    QModelIndex Parent;    Qt::Orientation Orientation;    int Start;    int End;        int Count;    QList<QPersistentModelIndex> Items;  };  QStack<Change> AboutToBeInserted;  QStack<Change> AboutToBeRemoved;  QList<QPersistentModelIndex> LayoutAboutToBeChanged;};//-----------------------------------------------------------------------------ctkModelTesterPrivate::ctkModelTesterPrivate(){  this->Model = 0;  this->ThrowOnError = true;  this->NestedInserts = false;}//-----------------------------------------------------------------------------ctkModelTester::ctkModelTester(QAbstractItemModel *_model, QObject *_parent)  :QObject(_parent){  QCTK_INIT_PRIVATE(ctkModelTester);  this->setModel(_model);}//-----------------------------------------------------------------------------void ctkModelTester::setModel(QAbstractItemModel *_model){  QCTK_D(ctkModelTester);  if (d->Model)    {    // disconnect    d->Model->disconnect(this);    d->AboutToBeInserted.clear();    d->AboutToBeRemoved.clear();    d->LayoutAboutToBeChanged.clear();    }  if (_model)    {    connect(_model, SIGNAL(columnsAboutToBeInserted(const QModelIndex &, int, int)),            this, SLOT(onColumnsAboutToBeInserted(const QModelIndex& , int, int)));    connect(_model, SIGNAL(columnsAboutToBeRemoved(const QModelIndex &, int, int)),            this, SLOT(onColumnsAboutToBeRemoved(const QModelIndex& , int, int)));    connect(_model, SIGNAL(columnsInserted(const QModelIndex &, int, int)),            this, SLOT(onColumnsInserted(const QModelIndex& , int, int)));    connect(_model, SIGNAL(columnsRemoved(const QModelIndex &, int, int)),            this, SLOT(onColumnsRemoved(const QModelIndex& , int, int)));    connect(_model, SIGNAL(dataChanged(const QModelIndex &, const QModelIndex &)),            this, SLOT(onDataChanged(const QModelIndex& , const QModelIndex &)));    connect(_model, SIGNAL(layoutAboutToBeChanged()), this, SLOT(onLayoutAboutToBeChanged()));    connect(_model, SIGNAL(layoutChanged()), this, SLOT(onLayoutChanged()));    connect(_model, SIGNAL(modelAboutToBeReset()), this, SLOT(onModelAboutToBeReset()));    connect(_model, SIGNAL(modelReset()), this, SLOT(onModelReset()));    connect(_model, SIGNAL(rowsAboutToBeInserted(const QModelIndex &, int, int)),            this, SLOT(onRowsAboutToBeInserted(const QModelIndex& , int, int)));    connect(_model, SIGNAL(rowsAboutToBeRemoved(const QModelIndex &, int, int)),            this, SLOT(onRowsAboutToBeRemoved(const QModelIndex& , int, int)));    connect(_model, SIGNAL(rowsInserted(const QModelIndex &, int, int)),            this, SLOT(onRowsInserted(const QModelIndex& , int, int)));    connect(_model, SIGNAL(rowsRemoved(const QModelIndex &, int, int)),            this, SLOT(onRowsRemoved(const QModelIndex& , int, int)));    }  d->Model = _model;  this->testModel();}//-----------------------------------------------------------------------------QAbstractItemModel* ctkModelTester::model()const{  return qctk_d()->Model;}//-----------------------------------------------------------------------------void ctkModelTester::setThrowOnError(bool throwException){  qctk_d()->ThrowOnError = throwException;}//-----------------------------------------------------------------------------bool ctkModelTester::throwOnError()const{  return qctk_d()->ThrowOnError;}//-----------------------------------------------------------------------------void ctkModelTester::setNestedInserts( bool nestedInserts ){  qctk_d()->NestedInserts = nestedInserts;}//-----------------------------------------------------------------------------bool ctkModelTester::nestedInserts()const{  return qctk_d()->NestedInserts;}//-----------------------------------------------------------------------------void  ctkModelTester::test(bool result, const QString& errorString)const{  if (result)    {    return;    }  qDebug() << errorString;  if (this->throwOnError())    {    throw errorString;    }}//-----------------------------------------------------------------------------void ctkModelTester::testModelIndex(const QModelIndex& index)const{  QCTK_D(const ctkModelTester);  if (!index.isValid())    {// invalid index    this->test(index.model() == 0, "An invalid index can't have a valid model.");    this->test(index.model() != d->Model, "An invalid index can't have a valid model.");    this->test(index.column() == -1, "An invalid index can't have a valid column.");    this->test(index.row() == -1, "An invalid index can't have a valid row.");    this->test(index.parent().isValid() == false, "An invalid index can't have a valid row.");    this->test(index.row() == -1, "An invalid index can't have a valid row.");    for(int i = 0; i < 100; ++i)      {      this->test(index.sibling(i % 10, i / 10).isValid() == false, "An invalid index can't have valid sibling.");      }    }  else    {// valid index    this->test(index.model() == d->Model, "A valid index must have a valid model.");    this->test(index.column() >= 0, "An valid index can't have an invalid column.");    this->test(index.row() >= 0, "An valid index can't have an invalid row.");    this->test(index == index.sibling(index.row(), index.column()), "Index's row and/or column is wrong.");    }  this->testData(index);  this->testParent(index);}//-----------------------------------------------------------------------------void ctkModelTester::testData(const QModelIndex& index)const{  if (!index.isValid())    {    this->test(!index.data(Qt::DisplayRole).isValid(),                QString("An invalid index can't have valid data: %1")               .arg(index.data(Qt::DisplayRole).toString()));    }  else    {    this->test(index.data(Qt::DisplayRole).isValid(),                QString("A valid index can't have invalid data: %1, %2, %3")               .arg(index.row()).arg(index.column()).arg(long(index.internalPointer())));    }}//-----------------------------------------------------------------------------void ctkModelTester::testParent(const QModelIndex& vparent)const{  QCTK_D(const ctkModelTester);  if (!d->Model->hasChildren(vparent))    {    // it's asking a lot :-)    //this->test(d->Model->columnCount(vparent) <= 0, "A parent with no children can't have a columnCount > 0.");    this->test(d->Model->rowCount(vparent) <= 0, "A parent with no children can't have a rowCount > 0.");    }  else    {    this->test(d->Model->columnCount(vparent) > 0, "A parent with children can't have a columnCount <= 0.");    this->test(d->Model->rowCount(vparent) > 0 || d->Model->canFetchMore(vparent), "A parent with children can't have a rowCount <= 0. or if it does, canFetchMore should return true");    }  if (!vparent.isValid())    {// otherwise there will be an infinite loop    return;    }    for (int i = 0 ; i < d->Model->rowCount(vparent); ++i)    {    for (int j = 0; j < d->Model->columnCount(vparent); ++j)      {      this->test(d->Model->hasIndex(i, j, vparent), "hasIndex should return true for int range {0->rowCount(), 0->columnCount()}");      QModelIndex child = vparent.child(i, j);      this->test(child.parent() == vparent, "A child's parent can't be different from its parent");      this->testModelIndex(child);      }    }}//-----------------------------------------------------------------------------void ctkModelTester::testPersistentModelIndex(const QPersistentModelIndex& index)const{  QCTK_D(const ctkModelTester);  //qDebug() << "Test persistent Index: " << index ;  this->test(index.isValid(), "Persistent model index can't be invalid");  // did you forget to call QAbstractItemModel::changePersistentIndex() between   // beginLayoutChanged() and changePersistentIndex() ?  QModelIndex modelIndex = d->Model->index(index.row(), index.column(), index.parent());  this->test(modelIndex == index,              QString("Persistent index (%1, %2) can't be invalid").arg(index.row()).arg(index.column()));}//-----------------------------------------------------------------------------void ctkModelTester::testModel()const{  QCTK_D(const ctkModelTester);  if (d->Model == 0)    {    return;    }  for (int i = 0 ; i < d->Model->rowCount(); ++i)    {    for (int j = 0; j < d->Model->columnCount(); ++j)      {      this->test(d->Model->hasIndex(i, j), "hasIndex should return true for int range {0->rowCount(), 0->columnCount()}");      QModelIndex child = d->Model->index(i, j);      this->test(!child.parent().isValid(), "A child's parent can't be different from its parent");      this->testModelIndex(child);      }    }}//-----------------------------------------------------------------------------void ctkModelTester::onColumnsAboutToBeInserted(const QModelIndex & vparent, int start, int end){  //qDebug() << "columnsAboutToBeInserted: " << vparent << start << end;  this->onItemsAboutToBeInserted(vparent, Qt::Horizontal, start, end);}//-----------------------------------------------------------------------------void ctkModelTester::onColumnsAboutToBeRemoved(const QModelIndex & vparent, int start, int end){  //qDebug() << "columnsAboutToBeRemoved: " << vparent << start << end;  this->onItemsAboutToBeRemoved(vparent, Qt::Horizontal, start, end);}//-----------------------------------------------------------------------------void ctkModelTester::onColumnsInserted(const QModelIndex & vparent, int start, int end){  //qDebug() << "columnsInserted: " << vparent << start << end;  this->onItemsInserted(vparent, Qt::Horizontal, start, end);}//-----------------------------------------------------------------------------void ctkModelTester::onColumnsRemoved(const QModelIndex & vparent, int start, int end){  //qDebug() << "columnsRemoved: " << vparent << start << end;  this->onItemsRemoved(vparent, Qt::Horizontal, start, end);}//-----------------------------------------------------------------------------void ctkModelTester::onDataChanged(const QModelIndex & topLeft, const QModelIndex & bottomRight){  this->test(topLeft.parent() == bottomRight.parent(), "DataChanged support items with the same parent only");  this->test(topLeft.row() >= bottomRight.row(), "topLeft can't have a row lower than bottomRight");  this->test(bottomRight.column() >= topLeft.column(), "topLeft can't have a column lower than bottomRight");  for (int i = topLeft.row(); i <= bottomRight.row(); ++i)    {    for (int j = topLeft.column(); j < bottomRight.column(); ++j)      {      this->test(topLeft.sibling(i,j).isValid(), "Changed data must be valid");      // do the test on the indexes here, it's easier to debug than in testModel();      this->testModelIndex(topLeft.sibling(i,j));      }    }  this->testModel();}//-----------------------------------------------------------------------------void ctkModelTester::onHeaderDataChanged(Qt::Orientation orientation, int first, int last){  QCTK_D(ctkModelTester);  this->test(first <= last, "Changed headers have wrong indexes");  switch (orientation)    {    case Qt::Horizontal:      this->test(d->Model->columnCount() > last, "There is no more horizontal headers than columns.");      break;    case Qt::Vertical:      this->test(d->Model->rowCount() > last, "There is no more vertical headers than rows.");      break;    default:      this->test(orientation == Qt::Horizontal || orientation == Qt::Vertical, "Wrong orientation.");      break;    }  this->testModel();}//-----------------------------------------------------------------------------QList<QPersistentModelIndex> ctkModelTester::persistentModelIndexes(const QModelIndex& index)const{  QCTK_D(const ctkModelTester);  QList<QPersistentModelIndex> list;  for (int i = 0; i < d->Model->rowCount(index); ++i)    {    for (int j = 0; j < d->Model->columnCount(index); ++j)      {      QPersistentModelIndex child = d->Model->index(i, j, index);      list.append(child);      list += this->ctkModelTester::persistentModelIndexes(child);      }    }  return list;}//-----------------------------------------------------------------------------void ctkModelTester::onLayoutAboutToBeChanged(){  QCTK_D(ctkModelTester);  d->LayoutAboutToBeChanged = this->persistentModelIndexes(QModelIndex());  this->testModel();}//-----------------------------------------------------------------------------void ctkModelTester::onLayoutChanged(){  QCTK_D(ctkModelTester);  foreach (const QPersistentModelIndex& index, d->LayoutAboutToBeChanged)    {    this->testPersistentModelIndex(index);    }  d->LayoutAboutToBeChanged.clear();  this->testModel();}//-----------------------------------------------------------------------------void ctkModelTester::onModelAboutToBeReset(){  this->testModel();}//-----------------------------------------------------------------------------void ctkModelTester::onModelReset(){  this->testModel();}//-----------------------------------------------------------------------------void ctkModelTester::onRowsAboutToBeInserted(const QModelIndex &vparent, int start, int end){  //qDebug() << "rowsAboutToBeInserted: " << vparent << start << end;  this->onItemsAboutToBeInserted(vparent, Qt::Vertical, start, end);}//-----------------------------------------------------------------------------void ctkModelTester::onRowsAboutToBeRemoved(const QModelIndex &vparent, int start, int end){  //qDebug() << "rowsAboutToBeRemoved: " << vparent << start << end;  this->onItemsAboutToBeRemoved(vparent, Qt::Vertical, start, end);}//-----------------------------------------------------------------------------void ctkModelTester::onRowsInserted(const QModelIndex & vparent, int start, int end){  //qDebug() << "rowsInserted: " << vparent << start << end;  this->onItemsInserted(vparent, Qt::Vertical, start, end);}//-----------------------------------------------------------------------------void ctkModelTester::onRowsRemoved(const QModelIndex & vparent, int start, int end){  //qDebug() << "rowsRemoved: " << vparent << start << end;  this->onItemsRemoved(vparent, Qt::Vertical, start, end);}//-----------------------------------------------------------------------------void ctkModelTester::onItemsAboutToBeInserted(const QModelIndex &vparent, Qt::Orientation orientation, int start, int end){  QCTK_D(ctkModelTester);  this->test(start <= end, "Start can't be higher than end");  //Not sure about that  if (!d->NestedInserts)    {    this->test(d->AboutToBeInserted.size() == 0, "While inserting items, you can't insert other items.");    }  //Not sure about that  this->test(d->AboutToBeRemoved.size() == 0, "While removing items, you can't insert other items.");  ctkModelTesterPrivate::Change change;  change.Parent = vparent;  change.Orientation = orientation;  change.Start = start;  change.End = end;  change.Count = (orientation == Qt::Vertical ? d->Model->rowCount(vparent) :d->Model->columnCount(vparent) );  change.Items = this->persistentModelIndexes(vparent);  d->AboutToBeInserted.push(change);    this->testModel();}//-----------------------------------------------------------------------------void ctkModelTester::onItemsAboutToBeRemoved(const QModelIndex &vparent, Qt::Orientation orientation, int start, int end){  QCTK_D(ctkModelTester);  this->test(start <= end, "Start can't be higher than end");  //Not sure about that  this->test(d->AboutToBeInserted.size() == 0, "While inserting items, you can't remove other items.");  //Not sure about that  this->test(d->AboutToBeRemoved.size() == 0, "While removing items, you can't remove other items.");    int count = (orientation == Qt::Vertical ? d->Model->rowCount(vparent) :d->Model->columnCount(vparent) );  this->test(start < count, "Item to remove can't be invalid");  this->test(end < count, "Item to remove can't be invalid");    ctkModelTesterPrivate::Change change;  change.Parent = vparent;  change.Orientation = orientation;  change.Start = start;  change.End = end;  change.Count = count;  for (int i = 0 ; i < count; ++i)    {    QPersistentModelIndex index;    index = (orientation == Qt::Vertical ? d->Model->index(i, 0, vparent) : d->Model->index(0, i, vparent));    this->test(index.isValid(), "Index invalid");    if (orientation == Qt::Vertical && (index.row() < start || index.row() > end))      {      change.Items.append(index);      }    if (orientation == Qt::Horizontal && (index.column() < start || index.column() > end))      {      change.Items.append(index);      }    }  d->AboutToBeRemoved.push(change);  this->testModel();  //qDebug() << "About to be removed: " << start << " " << end <<vparent << count << change.Items.count();}//-----------------------------------------------------------------------------void ctkModelTester::onItemsInserted(const QModelIndex & vparent, Qt::Orientation orientation, int start, int end){  QCTK_D(ctkModelTester);  this->test(start <= end, "Start can't be higher end");  this->test(d->AboutToBeInserted.size() != 0, "rowsInserted() has been emitted, but not rowsAboutToBeInserted.");  //Not sure about that  this->test(d->AboutToBeRemoved.size() == 0, "While removing items, you can't insert other items.");  ctkModelTesterPrivate::Change change = d->AboutToBeInserted.pop();  this->test(change.Parent == vparent, "Parent can't be different");  this->test(change.Orientation == Qt::Vertical, "Orientation can't be different");  this->test(change.Start == start, "Start can't be different");  this->test(change.End == end, "End can't be different");  int count =  (orientation == Qt::Vertical ? d->Model->rowCount(vparent) :d->Model->columnCount(vparent) );  this->test(change.Count < count, "The new count number can't be lower");  this->test(count - change.Count == (end - start + 1) , "The new count number can't be lower");  foreach(const QPersistentModelIndex& index, change.Items)    {    this->testPersistentModelIndex(index);    }  change.Items.clear();    this->testModel();}//-----------------------------------------------------------------------------void ctkModelTester::onItemsRemoved(const QModelIndex & vparent, Qt::Orientation orientation, int start, int end){   QCTK_D(ctkModelTester);  this->test(start <= end, "Start can't be higher end");  this->test(d->AboutToBeRemoved.size() != 0, "rowsRemoved() has been emitted, but not rowsAboutToBeRemoved.");  //Not sure about that  this->test(d->AboutToBeInserted.size() == 0, "While inserted items, you can't remove other items.");  ctkModelTesterPrivate::Change change = d->AboutToBeRemoved.pop();  this->test(change.Parent == vparent, "Parent can't be different");  this->test(change.Orientation == Qt::Vertical, "Orientation can't be different");  this->test(change.Start == start, "Start can't be different");  this->test(change.End == end, "End can't be different");  int count = (orientation == Qt::Vertical ? d->Model->rowCount(vparent) :d->Model->columnCount(vparent) );  this->test(change.Count > count, "The new count number can't be higher");  this->test(change.Count - count == (end - start + 1) , "The new count number can't be higher");  foreach(const QPersistentModelIndex& index, change.Items)    {    this->testPersistentModelIndex(index);    }  change.Items.clear();    this->testModel();}
 |