ctkModelTester.cxx 20 KB

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