ctkModelTester.cpp 20 KB

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