ctkModelTester.cpp 20 KB

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