ctkModelTester.cpp 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519
  1. /*=========================================================================
  2. Library: ctk
  3. Copyright (c) Kitware Inc.
  4. All rights reserved.
  5. Distributed under a BSD License. See LICENSE.txt file.
  6. This software is distributed "AS IS" WITHOUT ANY WARRANTY; without even
  7. the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  8. See the above copyright notice for more information.
  9. =========================================================================*/
  10. // QT includes
  11. #include <QDebug>
  12. #include <QStack>
  13. // CTK includes
  14. #include "ctkModelTester.h"
  15. //-----------------------------------------------------------------------------
  16. class ctkModelTesterPrivate: public ctkPrivate<ctkModelTester>
  17. {
  18. public:
  19. ctkModelTesterPrivate();
  20. QAbstractItemModel *Model;
  21. bool ThrowOnError;
  22. bool NestedInserts;
  23. struct Change
  24. {
  25. QModelIndex Parent;
  26. Qt::Orientation Orientation;
  27. int Start;
  28. int End;
  29. int Count;
  30. QList<QPersistentModelIndex> Items;
  31. };
  32. QStack<Change> AboutToBeInserted;
  33. QStack<Change> AboutToBeRemoved;
  34. QList<QPersistentModelIndex> LayoutAboutToBeChanged;
  35. };
  36. //-----------------------------------------------------------------------------
  37. ctkModelTesterPrivate::ctkModelTesterPrivate()
  38. {
  39. this->Model = 0;
  40. this->ThrowOnError = true;
  41. this->NestedInserts = false;
  42. }
  43. //-----------------------------------------------------------------------------
  44. ctkModelTester::ctkModelTester(QAbstractItemModel *_model, QObject *_parent)
  45. :QObject(_parent)
  46. {
  47. CTK_INIT_PRIVATE(ctkModelTester);
  48. this->setModel(_model);
  49. }
  50. //-----------------------------------------------------------------------------
  51. void ctkModelTester::setModel(QAbstractItemModel *_model)
  52. {
  53. CTK_D(ctkModelTester);
  54. if (d->Model)
  55. {
  56. // disconnect
  57. d->Model->disconnect(this);
  58. d->AboutToBeInserted.clear();
  59. d->AboutToBeRemoved.clear();
  60. d->LayoutAboutToBeChanged.clear();
  61. }
  62. if (_model)
  63. {
  64. connect(_model, SIGNAL(columnsAboutToBeInserted(const QModelIndex &, int, int)),
  65. this, SLOT(onColumnsAboutToBeInserted(const QModelIndex& , int, int)));
  66. connect(_model, SIGNAL(columnsAboutToBeRemoved(const QModelIndex &, int, int)),
  67. this, SLOT(onColumnsAboutToBeRemoved(const QModelIndex& , int, int)));
  68. connect(_model, SIGNAL(columnsInserted(const QModelIndex &, int, int)),
  69. this, SLOT(onColumnsInserted(const QModelIndex& , int, int)));
  70. connect(_model, SIGNAL(columnsRemoved(const QModelIndex &, int, int)),
  71. this, SLOT(onColumnsRemoved(const QModelIndex& , int, int)));
  72. connect(_model, SIGNAL(dataChanged(const QModelIndex &, const QModelIndex &)),
  73. this, SLOT(onDataChanged(const QModelIndex& , const QModelIndex &)));
  74. connect(_model, SIGNAL(layoutAboutToBeChanged()), this, SLOT(onLayoutAboutToBeChanged()));
  75. connect(_model, SIGNAL(layoutChanged()), this, SLOT(onLayoutChanged()));
  76. connect(_model, SIGNAL(modelAboutToBeReset()), this, SLOT(onModelAboutToBeReset()));
  77. connect(_model, SIGNAL(modelReset()), this, SLOT(onModelReset()));
  78. connect(_model, SIGNAL(rowsAboutToBeInserted(const QModelIndex &, int, int)),
  79. this, SLOT(onRowsAboutToBeInserted(const QModelIndex& , int, int)));
  80. connect(_model, SIGNAL(rowsAboutToBeRemoved(const QModelIndex &, int, int)),
  81. this, SLOT(onRowsAboutToBeRemoved(const QModelIndex& , int, int)));
  82. connect(_model, SIGNAL(rowsInserted(const QModelIndex &, int, int)),
  83. this, SLOT(onRowsInserted(const QModelIndex& , int, int)));
  84. connect(_model, SIGNAL(rowsRemoved(const QModelIndex &, int, int)),
  85. this, SLOT(onRowsRemoved(const QModelIndex& , int, int)));
  86. }
  87. d->Model = _model;
  88. this->testModel();
  89. }
  90. //-----------------------------------------------------------------------------
  91. QAbstractItemModel* ctkModelTester::model()const
  92. {
  93. return ctk_d()->Model;
  94. }
  95. //-----------------------------------------------------------------------------
  96. void ctkModelTester::setThrowOnError(bool throwException)
  97. {
  98. ctk_d()->ThrowOnError = throwException;
  99. }
  100. //-----------------------------------------------------------------------------
  101. bool ctkModelTester::throwOnError()const
  102. {
  103. return ctk_d()->ThrowOnError;
  104. }
  105. //-----------------------------------------------------------------------------
  106. void ctkModelTester::setNestedInserts( bool nestedInsertsValue )
  107. {
  108. ctk_d()->NestedInserts = nestedInsertsValue;
  109. }
  110. //-----------------------------------------------------------------------------
  111. bool ctkModelTester::nestedInserts()const
  112. {
  113. return ctk_d()->NestedInserts;
  114. }
  115. //-----------------------------------------------------------------------------
  116. void ctkModelTester::test(bool result, const QString& errorString)const
  117. {
  118. if (result)
  119. {
  120. return;
  121. }
  122. qDebug() << errorString;
  123. if (this->throwOnError())
  124. {
  125. throw errorString;
  126. }
  127. }
  128. //-----------------------------------------------------------------------------
  129. void ctkModelTester::testModelIndex(const QModelIndex& index)const
  130. {
  131. CTK_D(const ctkModelTester);
  132. if (!index.isValid())
  133. {// invalid index
  134. this->test(index.model() == 0, "An invalid index can't have a valid model.");
  135. this->test(index.model() != d->Model, "An invalid index can't have a valid model.");
  136. this->test(index.column() == -1, "An invalid index can't have a valid column.");
  137. this->test(index.row() == -1, "An invalid index can't have a valid row.");
  138. this->test(index.parent().isValid() == false, "An invalid index can't have a valid row.");
  139. this->test(index.row() == -1, "An invalid index can't have a valid row.");
  140. for(int i = 0; i < 100; ++i)
  141. {
  142. this->test(index.sibling(i % 10, i / 10).isValid() == false, "An invalid index can't have valid sibling.");
  143. }
  144. }
  145. else
  146. {// valid index
  147. this->test(index.model() == d->Model, "A valid index must have a valid model.");
  148. this->test(index.column() >= 0, "An valid index can't have an invalid column.");
  149. this->test(index.row() >= 0, "An valid index can't have an invalid row.");
  150. this->test(index == index.sibling(index.row(), index.column()), "Index's row and/or column is wrong.");
  151. }
  152. this->testData(index);
  153. this->testParent(index);
  154. }
  155. //-----------------------------------------------------------------------------
  156. void ctkModelTester::testData(const QModelIndex& index)const
  157. {
  158. if (!index.isValid())
  159. {
  160. this->test(!index.data(Qt::DisplayRole).isValid(),
  161. QString("An invalid index can't have valid data: %1")
  162. .arg(index.data(Qt::DisplayRole).toString()));
  163. }
  164. else
  165. {
  166. this->test(index.data(Qt::DisplayRole).isValid(),
  167. QString("A valid index can't have invalid data: %1, %2, %3")
  168. .arg(index.row()).arg(index.column()).arg(long(index.internalPointer())));
  169. }
  170. }
  171. //-----------------------------------------------------------------------------
  172. void ctkModelTester::testParent(const QModelIndex& vparent)const
  173. {
  174. CTK_D(const ctkModelTester);
  175. if (!d->Model->hasChildren(vparent))
  176. {
  177. // it's asking a lot :-)
  178. //this->test(d->Model->columnCount(vparent) <= 0, "A parent with no children can't have a columnCount > 0.");
  179. this->test(d->Model->rowCount(vparent) <= 0, "A parent with no children can't have a rowCount > 0.");
  180. }
  181. else
  182. {
  183. this->test(d->Model->columnCount(vparent) > 0, "A parent with children can't have a columnCount <= 0.");
  184. 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");
  185. }
  186. if (!vparent.isValid())
  187. {// otherwise there will be an infinite loop
  188. return;
  189. }
  190. for (int i = 0 ; i < d->Model->rowCount(vparent); ++i)
  191. {
  192. for (int j = 0; j < d->Model->columnCount(vparent); ++j)
  193. {
  194. this->test(d->Model->hasIndex(i, j, vparent), "hasIndex should return true for int range {0->rowCount(), 0->columnCount()}");
  195. QModelIndex child = vparent.child(i, j);
  196. this->test(child.parent() == vparent, "A child's parent can't be different from its parent");
  197. this->testModelIndex(child);
  198. }
  199. }
  200. }
  201. //-----------------------------------------------------------------------------
  202. void ctkModelTester::testPersistentModelIndex(const QPersistentModelIndex& index)const
  203. {
  204. CTK_D(const ctkModelTester);
  205. //qDebug() << "Test persistent Index: " << index ;
  206. this->test(index.isValid(), "Persistent model index can't be invalid");
  207. // did you forget to call QAbstractItemModel::changePersistentIndex() between
  208. // beginLayoutChanged() and changePersistentIndex() ?
  209. QModelIndex modelIndex = d->Model->index(index.row(), index.column(), index.parent());
  210. this->test(modelIndex == index,
  211. QString("Persistent index (%1, %2) can't be invalid").arg(index.row()).arg(index.column()));
  212. }
  213. //-----------------------------------------------------------------------------
  214. void ctkModelTester::testModel()const
  215. {
  216. CTK_D(const ctkModelTester);
  217. if (d->Model == 0)
  218. {
  219. return;
  220. }
  221. for (int i = 0 ; i < d->Model->rowCount(); ++i)
  222. {
  223. for (int j = 0; j < d->Model->columnCount(); ++j)
  224. {
  225. this->test(d->Model->hasIndex(i, j), "hasIndex should return true for int range {0->rowCount(), 0->columnCount()}");
  226. QModelIndex child = d->Model->index(i, j);
  227. this->test(!child.parent().isValid(), "A child's parent can't be different from its parent");
  228. this->testModelIndex(child);
  229. }
  230. }
  231. }
  232. //-----------------------------------------------------------------------------
  233. void ctkModelTester::onColumnsAboutToBeInserted(const QModelIndex & vparent, int start, int end)
  234. {
  235. //qDebug() << "columnsAboutToBeInserted: " << vparent << start << end;
  236. this->onItemsAboutToBeInserted(vparent, Qt::Horizontal, start, end);
  237. }
  238. //-----------------------------------------------------------------------------
  239. void ctkModelTester::onColumnsAboutToBeRemoved(const QModelIndex & vparent, int start, int end)
  240. {
  241. //qDebug() << "columnsAboutToBeRemoved: " << vparent << start << end;
  242. this->onItemsAboutToBeRemoved(vparent, Qt::Horizontal, start, end);
  243. }
  244. //-----------------------------------------------------------------------------
  245. void ctkModelTester::onColumnsInserted(const QModelIndex & vparent, int start, int end)
  246. {
  247. //qDebug() << "columnsInserted: " << vparent << start << end;
  248. this->onItemsInserted(vparent, Qt::Horizontal, start, end);
  249. }
  250. //-----------------------------------------------------------------------------
  251. void ctkModelTester::onColumnsRemoved(const QModelIndex & vparent, int start, int end)
  252. {
  253. //qDebug() << "columnsRemoved: " << vparent << start << end;
  254. this->onItemsRemoved(vparent, Qt::Horizontal, start, end);
  255. }
  256. //-----------------------------------------------------------------------------
  257. void ctkModelTester::onDataChanged(const QModelIndex & topLeft, const QModelIndex & bottomRight)
  258. {
  259. this->test(topLeft.parent() == bottomRight.parent(), "DataChanged support items with the same parent only");
  260. this->test(topLeft.row() >= bottomRight.row(), "topLeft can't have a row lower than bottomRight");
  261. this->test(bottomRight.column() >= topLeft.column(), "topLeft can't have a column lower than bottomRight");
  262. for (int i = topLeft.row(); i <= bottomRight.row(); ++i)
  263. {
  264. for (int j = topLeft.column(); j < bottomRight.column(); ++j)
  265. {
  266. this->test(topLeft.sibling(i,j).isValid(), "Changed data must be valid");
  267. // do the test on the indexes here, it's easier to debug than in testModel();
  268. this->testModelIndex(topLeft.sibling(i,j));
  269. }
  270. }
  271. this->testModel();
  272. }
  273. //-----------------------------------------------------------------------------
  274. void ctkModelTester::onHeaderDataChanged(Qt::Orientation orientation, int first, int last)
  275. {
  276. CTK_D(ctkModelTester);
  277. this->test(first <= last, "Changed headers have wrong indexes");
  278. switch (orientation)
  279. {
  280. case Qt::Horizontal:
  281. this->test(d->Model->columnCount() > last, "There is no more horizontal headers than columns.");
  282. break;
  283. case Qt::Vertical:
  284. this->test(d->Model->rowCount() > last, "There is no more vertical headers than rows.");
  285. break;
  286. default:
  287. this->test(orientation == Qt::Horizontal || orientation == Qt::Vertical, "Wrong orientation.");
  288. break;
  289. }
  290. this->testModel();
  291. }
  292. //-----------------------------------------------------------------------------
  293. QList<QPersistentModelIndex> ctkModelTester::persistentModelIndexes(const QModelIndex& index)const
  294. {
  295. CTK_D(const ctkModelTester);
  296. QList<QPersistentModelIndex> list;
  297. for (int i = 0; i < d->Model->rowCount(index); ++i)
  298. {
  299. for (int j = 0; j < d->Model->columnCount(index); ++j)
  300. {
  301. QPersistentModelIndex child = d->Model->index(i, j, index);
  302. list.append(child);
  303. list += this->ctkModelTester::persistentModelIndexes(child);
  304. }
  305. }
  306. return list;
  307. }
  308. //-----------------------------------------------------------------------------
  309. void ctkModelTester::onLayoutAboutToBeChanged()
  310. {
  311. CTK_D(ctkModelTester);
  312. d->LayoutAboutToBeChanged = this->persistentModelIndexes(QModelIndex());
  313. this->testModel();
  314. }
  315. //-----------------------------------------------------------------------------
  316. void ctkModelTester::onLayoutChanged()
  317. {
  318. CTK_D(ctkModelTester);
  319. foreach (const QPersistentModelIndex& index, d->LayoutAboutToBeChanged)
  320. {
  321. this->testPersistentModelIndex(index);
  322. }
  323. d->LayoutAboutToBeChanged.clear();
  324. this->testModel();
  325. }
  326. //-----------------------------------------------------------------------------
  327. void ctkModelTester::onModelAboutToBeReset()
  328. {
  329. this->testModel();
  330. }
  331. //-----------------------------------------------------------------------------
  332. void ctkModelTester::onModelReset()
  333. {
  334. this->testModel();
  335. }
  336. //-----------------------------------------------------------------------------
  337. void ctkModelTester::onRowsAboutToBeInserted(const QModelIndex &vparent, int start, int end)
  338. {
  339. //qDebug() << "rowsAboutToBeInserted: " << vparent << start << end;
  340. this->onItemsAboutToBeInserted(vparent, Qt::Vertical, start, end);
  341. }
  342. //-----------------------------------------------------------------------------
  343. void ctkModelTester::onRowsAboutToBeRemoved(const QModelIndex &vparent, int start, int end)
  344. {
  345. //qDebug() << "rowsAboutToBeRemoved: " << vparent << start << end;
  346. this->onItemsAboutToBeRemoved(vparent, Qt::Vertical, start, end);
  347. }
  348. //-----------------------------------------------------------------------------
  349. void ctkModelTester::onRowsInserted(const QModelIndex & vparent, int start, int end)
  350. {
  351. //qDebug() << "rowsInserted: " << vparent << start << end;
  352. this->onItemsInserted(vparent, Qt::Vertical, start, end);
  353. }
  354. //-----------------------------------------------------------------------------
  355. void ctkModelTester::onRowsRemoved(const QModelIndex & vparent, int start, int end)
  356. {
  357. //qDebug() << "rowsRemoved: " << vparent << start << end;
  358. this->onItemsRemoved(vparent, Qt::Vertical, start, end);
  359. }
  360. //-----------------------------------------------------------------------------
  361. void ctkModelTester::onItemsAboutToBeInserted(const QModelIndex &vparent, Qt::Orientation orientation, int start, int end)
  362. {
  363. CTK_D(ctkModelTester);
  364. this->test(start <= end, "Start can't be higher than end");
  365. //Not sure about that
  366. if (!d->NestedInserts)
  367. {
  368. this->test(d->AboutToBeInserted.size() == 0, "While inserting items, you can't insert other items.");
  369. }
  370. //Not sure about that
  371. this->test(d->AboutToBeRemoved.size() == 0, "While removing items, you can't insert other items.");
  372. ctkModelTesterPrivate::Change change;
  373. change.Parent = vparent;
  374. change.Orientation = orientation;
  375. change.Start = start;
  376. change.End = end;
  377. change.Count = (orientation == Qt::Vertical ? d->Model->rowCount(vparent) :d->Model->columnCount(vparent) );
  378. change.Items = this->persistentModelIndexes(vparent);
  379. d->AboutToBeInserted.push(change);
  380. this->testModel();
  381. }
  382. //-----------------------------------------------------------------------------
  383. void ctkModelTester::onItemsAboutToBeRemoved(const QModelIndex &vparent, Qt::Orientation orientation, int start, int end)
  384. {
  385. CTK_D(ctkModelTester);
  386. this->test(start <= end, "Start can't be higher than end");
  387. //Not sure about that
  388. this->test(d->AboutToBeInserted.size() == 0, "While inserting items, you can't remove other items.");
  389. //Not sure about that
  390. this->test(d->AboutToBeRemoved.size() == 0, "While removing items, you can't remove other items.");
  391. int count = (orientation == Qt::Vertical ? d->Model->rowCount(vparent) :d->Model->columnCount(vparent) );
  392. this->test(start < count, "Item to remove can't be invalid");
  393. this->test(end < count, "Item to remove can't be invalid");
  394. ctkModelTesterPrivate::Change change;
  395. change.Parent = vparent;
  396. change.Orientation = orientation;
  397. change.Start = start;
  398. change.End = end;
  399. change.Count = count;
  400. for (int i = 0 ; i < count; ++i)
  401. {
  402. QPersistentModelIndex index;
  403. index = (orientation == Qt::Vertical ? d->Model->index(i, 0, vparent) : d->Model->index(0, i, vparent));
  404. this->test(index.isValid(), "Index invalid");
  405. if (orientation == Qt::Vertical && (index.row() < start || index.row() > end))
  406. {
  407. change.Items.append(index);
  408. }
  409. if (orientation == Qt::Horizontal && (index.column() < start || index.column() > end))
  410. {
  411. change.Items.append(index);
  412. }
  413. }
  414. d->AboutToBeRemoved.push(change);
  415. this->testModel();
  416. //qDebug() << "About to be removed: " << start << " " << end <<vparent << count << change.Items.count();
  417. }
  418. //-----------------------------------------------------------------------------
  419. void ctkModelTester::onItemsInserted(const QModelIndex & vparent, Qt::Orientation orientation, int start, int end)
  420. {
  421. CTK_D(ctkModelTester);
  422. this->test(start <= end, "Start can't be higher end");
  423. this->test(d->AboutToBeInserted.size() != 0, "rowsInserted() has been emitted, but not rowsAboutToBeInserted.");
  424. //Not sure about that
  425. this->test(d->AboutToBeRemoved.size() == 0, "While removing items, you can't insert other items.");
  426. ctkModelTesterPrivate::Change change = d->AboutToBeInserted.pop();
  427. this->test(change.Parent == vparent, "Parent can't be different");
  428. this->test(change.Orientation == Qt::Vertical, "Orientation can't be different");
  429. this->test(change.Start == start, "Start can't be different");
  430. this->test(change.End == end, "End can't be different");
  431. int count = (orientation == Qt::Vertical ? d->Model->rowCount(vparent) :d->Model->columnCount(vparent) );
  432. this->test(change.Count < count, "The new count number can't be lower");
  433. this->test(count - change.Count == (end - start + 1) , "The new count number can't be lower");
  434. foreach(const QPersistentModelIndex& index, change.Items)
  435. {
  436. this->testPersistentModelIndex(index);
  437. }
  438. change.Items.clear();
  439. this->testModel();
  440. }
  441. //-----------------------------------------------------------------------------
  442. void ctkModelTester::onItemsRemoved(const QModelIndex & vparent, Qt::Orientation orientation, int start, int end)
  443. {
  444. CTK_D(ctkModelTester);
  445. this->test(start <= end, "Start can't be higher end");
  446. this->test(d->AboutToBeRemoved.size() != 0, "rowsRemoved() has been emitted, but not rowsAboutToBeRemoved.");
  447. //Not sure about that
  448. this->test(d->AboutToBeInserted.size() == 0, "While inserted items, you can't remove other items.");
  449. ctkModelTesterPrivate::Change change = d->AboutToBeRemoved.pop();
  450. this->test(change.Parent == vparent, "Parent can't be different");
  451. this->test(change.Orientation == Qt::Vertical, "Orientation can't be different");
  452. this->test(change.Start == start, "Start can't be different");
  453. this->test(change.End == end, "End can't be different");
  454. int count = (orientation == Qt::Vertical ? d->Model->rowCount(vparent) :d->Model->columnCount(vparent) );
  455. this->test(change.Count > count, "The new count number can't be higher");
  456. this->test(change.Count - count == (end - start + 1) , "The new count number can't be higher");
  457. foreach(const QPersistentModelIndex& index, change.Items)
  458. {
  459. this->testPersistentModelIndex(index);
  460. }
  461. change.Items.clear();
  462. this->testModel();
  463. }