1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2016 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the test suite of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at https://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT |
21 | ** included in the packaging of this file. Please review the following |
22 | ** information to ensure the GNU General Public License requirements will |
23 | ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
24 | ** |
25 | ** $QT_END_LICENSE$ |
26 | ** |
27 | ****************************************************************************/ |
28 | |
29 | #include <QRandomGenerator> |
30 | #include <QStack> |
31 | #include <QStandardItemModel> |
32 | #include <QTest> |
33 | #include "viewstotest.cpp" |
34 | |
35 | /*! |
36 | See viewstotest.cpp for instructions on how to have your view tested with these tests. |
37 | |
38 | Each test such as visualRect have a _data() function which populate the QTest data with |
39 | tests specified by viewstotest.cpp and any extra data needed for that particular test. |
40 | |
41 | setupWithNoTestData() fills QTest data with only the tests it is used by most tests. |
42 | |
43 | There are some basic qDebug statements sprikled about that might be helpfull for |
44 | fixing your issues. |
45 | */ |
46 | class tst_QItemView : public QObject |
47 | { |
48 | Q_OBJECT |
49 | |
50 | private slots: |
51 | void init(); |
52 | void cleanup(); |
53 | |
54 | void nonDestructiveBasicTest_data(); |
55 | void nonDestructiveBasicTest(); |
56 | |
57 | void spider_data(); |
58 | void spider(); |
59 | |
60 | void resize_data(); |
61 | void resize(); |
62 | |
63 | void visualRect_data(); |
64 | void visualRect(); |
65 | |
66 | void indexAt_data(); |
67 | void indexAt(); |
68 | |
69 | void scrollTo_data(); |
70 | void scrollTo(); |
71 | |
72 | void moveCursor_data(); |
73 | void moveCursor(); |
74 | |
75 | private: |
76 | void setupWithNoTestData(); |
77 | void populate(); |
78 | void walkScreen(QAbstractItemView *view); |
79 | |
80 | QAbstractItemView *view; |
81 | QAbstractItemModel *treeModel; |
82 | ViewsToTest *testViews; |
83 | }; |
84 | |
85 | /*! |
86 | * Views should not make invalid requests, sense a model might not check all the bad cases. |
87 | */ |
88 | class CheckerModel : public QStandardItemModel |
89 | { |
90 | Q_OBJECT |
91 | |
92 | public: |
93 | using QStandardItemModel::QStandardItemModel; |
94 | |
95 | QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override |
96 | { |
97 | if (!index.isValid()) { |
98 | qWarning(msg: "%s: index is not valid" , Q_FUNC_INFO); |
99 | return QVariant(); |
100 | } |
101 | return QStandardItemModel::data(index, role); |
102 | } |
103 | |
104 | Qt::ItemFlags flags(const QModelIndex &index) const override |
105 | { |
106 | if (!index.isValid()) { |
107 | qWarning(msg: "%s: index is not valid" , Q_FUNC_INFO); |
108 | return Qt::ItemFlags(); |
109 | } |
110 | if (index.row() == 2 || index.row() == rowCount() - 3 |
111 | || index.column() == 2 || index.column() == columnCount() - 3) { |
112 | Qt::ItemFlags f = QStandardItemModel::flags(index); |
113 | f.setFlag(flag: Qt::ItemIsEnabled, on: false); |
114 | return f; |
115 | } |
116 | return QStandardItemModel::flags(index); |
117 | } |
118 | |
119 | QModelIndex parent(const QModelIndex &child) const override |
120 | { |
121 | if (!child.isValid()) { |
122 | qWarning(msg: "%s: child index is not valid" , Q_FUNC_INFO); |
123 | return QModelIndex(); |
124 | } |
125 | return QStandardItemModel::parent(child); |
126 | } |
127 | |
128 | QVariant (int section, Qt::Orientation orientation, |
129 | int role = Qt::DisplayRole) const override |
130 | { |
131 | if (orientation == Qt::Horizontal |
132 | && (section < 0 || section > columnCount())) { |
133 | qWarning(msg: "%s: invalid section %d, must be in range 0..%d" , |
134 | Q_FUNC_INFO, section, columnCount()); |
135 | return QVariant(); |
136 | } |
137 | if (orientation == Qt::Vertical |
138 | && (section < 0 || section > rowCount())) { |
139 | qWarning(msg: "%s: invalid section %d, must be in range 0..%d" , |
140 | Q_FUNC_INFO, section, rowCount()); |
141 | return QVariant(); |
142 | } |
143 | return QStandardItemModel::headerData(section, orientation, role); |
144 | } |
145 | |
146 | bool setData(const QModelIndex &index, const QVariant &value, |
147 | int role = Qt::EditRole) override |
148 | { |
149 | if (!index.isValid()) { |
150 | qWarning(msg: "%s: index is not valid" , Q_FUNC_INFO); |
151 | return false; |
152 | } |
153 | return QStandardItemModel::setData(index, value, role); |
154 | } |
155 | |
156 | void sort(int column, Qt::SortOrder order = Qt::AscendingOrder) override |
157 | { |
158 | if (column < 0 || column > columnCount()) |
159 | qWarning(msg: "%s: invalid column %d, must be in range 0..%d" , |
160 | Q_FUNC_INFO, column, columnCount()); |
161 | else |
162 | QStandardItemModel::sort(column, order); |
163 | } |
164 | |
165 | QModelIndexList match(const QModelIndex &start, int role, |
166 | const QVariant &value, int hits = 1, |
167 | Qt::MatchFlags flags = Qt::MatchFlags(Qt::MatchStartsWith | Qt::MatchWrap)) const override |
168 | { |
169 | if (hits <= 0) { |
170 | qWarning(msg: "%s: hits must be greater than zero" , Q_FUNC_INFO); |
171 | return QModelIndexList(); |
172 | } |
173 | if (!value.isValid()) { |
174 | qWarning(msg: "%s: value is not valid" , Q_FUNC_INFO); |
175 | return QModelIndexList(); |
176 | } |
177 | return QAbstractItemModel::match(start, role, value, hits, flags); |
178 | } |
179 | |
180 | bool (int section, Qt::Orientation orientation, |
181 | const QVariant &value, int role = Qt::EditRole) override |
182 | { |
183 | if (orientation == Qt::Horizontal |
184 | && (section < 0 || section > columnCount())) { |
185 | qWarning(msg: "%s: invalid section %d, must be in range 0..%d" , |
186 | Q_FUNC_INFO, section, columnCount()); |
187 | return false; |
188 | } |
189 | if (orientation == Qt::Vertical |
190 | && (section < 0 || section > rowCount())) { |
191 | qWarning(msg: "%s: invalid section %d, must be in range 0..%d" , |
192 | Q_FUNC_INFO, section, rowCount()); |
193 | return false; |
194 | } |
195 | return QAbstractItemModel::setHeaderData(section, orientation, value, role); |
196 | } |
197 | }; |
198 | |
199 | void tst_QItemView::init() |
200 | { |
201 | testViews = new ViewsToTest(); |
202 | populate(); |
203 | } |
204 | |
205 | void tst_QItemView::cleanup() |
206 | { |
207 | delete testViews; |
208 | delete view; |
209 | delete treeModel; |
210 | view = nullptr; |
211 | testViews = nullptr; |
212 | treeModel = nullptr; |
213 | } |
214 | |
215 | void tst_QItemView::setupWithNoTestData() |
216 | { |
217 | ViewsToTest testViews; |
218 | QTest::addColumn<QString>(name: "viewType" ); |
219 | QTest::addColumn<bool>(name: "displays" ); |
220 | QTest::addColumn<QAbstractItemView::ScrollMode>(name: "vscroll" ); |
221 | QTest::addColumn<QAbstractItemView::ScrollMode>(name: "hscroll" ); |
222 | for (int i = 0; i < testViews.tests.size(); ++i) { |
223 | QString view = testViews.tests.at(i).viewType; |
224 | QString test = view + " ScrollPerPixel" ; |
225 | bool displayIndexes = (testViews.tests.at(i).display == ViewsToTest::DisplayRoot); |
226 | QTest::newRow(dataTag: test.toLatin1().data()) << view << displayIndexes |
227 | << QAbstractItemView::ScrollPerPixel |
228 | << QAbstractItemView::ScrollPerPixel |
229 | ; |
230 | } |
231 | for (int i = 0; i < testViews.tests.size(); ++i) { |
232 | QString view = testViews.tests.at(i).viewType; |
233 | QString test = view + " ScrollPerItem" ; |
234 | bool displayIndexes = (testViews.tests.at(i).display == ViewsToTest::DisplayRoot); |
235 | QTest::newRow(dataTag: test.toLatin1().data()) << view << displayIndexes |
236 | << QAbstractItemView::ScrollPerItem |
237 | << QAbstractItemView::ScrollPerItem |
238 | ; |
239 | } |
240 | } |
241 | |
242 | void tst_QItemView::populate() |
243 | { |
244 | treeModel = new CheckerModel; |
245 | QModelIndex parent; |
246 | #if defined(Q_PROCESSOR_ARM) |
247 | const int baseInsert = 4; |
248 | #else |
249 | const int baseInsert = 26; |
250 | #endif |
251 | for (int i = 0; i < 40; ++i) { |
252 | const QString iS = QString::number(i); |
253 | parent = treeModel->index(row: 0, column: 0, parent); |
254 | treeModel->insertRows(row: 0, count: baseInsert + i, parent); |
255 | treeModel->insertColumns(column: 0, count: baseInsert + i, parent); |
256 | // Fill in some values to make it easier to debug |
257 | for (int x = 0; x < treeModel->rowCount(); ++x) { |
258 | const QString xS = QString::number(x); |
259 | for (int y = 0; y < treeModel->columnCount(); ++y) { |
260 | QModelIndex index = treeModel->index(row: x, column: y, parent); |
261 | treeModel->setData(index, value: xS + QLatin1Char('_') + QString::number(y) + QLatin1Char('_') + iS); |
262 | treeModel->setData(index, value: QVariant(QColor(Qt::blue)), role: Qt::ForegroundRole); |
263 | } |
264 | } |
265 | } |
266 | } |
267 | |
268 | void tst_QItemView::nonDestructiveBasicTest_data() |
269 | { |
270 | setupWithNoTestData(); |
271 | } |
272 | |
273 | /*! |
274 | nonDestructiveBasicTest tries to call a number of the basic functions (not all) |
275 | to make sure the view doesn't segfault, testing the functions that makes sense. |
276 | */ |
277 | void tst_QItemView::nonDestructiveBasicTest() |
278 | { |
279 | QFETCH(QString, viewType); |
280 | QFETCH(QAbstractItemView::ScrollMode, vscroll); |
281 | QFETCH(QAbstractItemView::ScrollMode, hscroll); |
282 | |
283 | view = testViews->createView(viewType); |
284 | QVERIFY(view); |
285 | view->setVerticalScrollMode(vscroll); |
286 | view->setHorizontalScrollMode(hscroll); |
287 | |
288 | // setSelectionModel() will assert |
289 | //view->setSelectionModel(0); |
290 | // setItemDelegate() will assert |
291 | //view->setItemDelegate(0); |
292 | |
293 | // setSelectionMode |
294 | view->setSelectionMode(QAbstractItemView::SingleSelection); |
295 | QCOMPARE(view->selectionMode(), QAbstractItemView::SingleSelection); |
296 | view->setSelectionMode(QAbstractItemView::ContiguousSelection); |
297 | QCOMPARE(view->selectionMode(), QAbstractItemView::ContiguousSelection); |
298 | view->setSelectionMode(QAbstractItemView::ExtendedSelection); |
299 | QCOMPARE(view->selectionMode(), QAbstractItemView::ExtendedSelection); |
300 | view->setSelectionMode(QAbstractItemView::MultiSelection); |
301 | QCOMPARE(view->selectionMode(), QAbstractItemView::MultiSelection); |
302 | view->setSelectionMode(QAbstractItemView::NoSelection); |
303 | QCOMPARE(view->selectionMode(), QAbstractItemView::NoSelection); |
304 | |
305 | // setSelectionBehavior |
306 | view->setSelectionBehavior(QAbstractItemView::SelectItems); |
307 | QCOMPARE(view->selectionBehavior(), QAbstractItemView::SelectItems); |
308 | view->setSelectionBehavior(QAbstractItemView::SelectRows); |
309 | QCOMPARE(view->selectionBehavior(), QAbstractItemView::SelectRows); |
310 | view->setSelectionBehavior(QAbstractItemView::SelectColumns); |
311 | QCOMPARE(view->selectionBehavior(), QAbstractItemView::SelectColumns); |
312 | |
313 | // setEditTriggers |
314 | view->setEditTriggers(QAbstractItemView::EditKeyPressed); |
315 | QCOMPARE(view->editTriggers(), QAbstractItemView::EditKeyPressed); |
316 | view->setEditTriggers(QAbstractItemView::NoEditTriggers); |
317 | QCOMPARE(view->editTriggers(), QAbstractItemView::NoEditTriggers); |
318 | view->setEditTriggers(QAbstractItemView::CurrentChanged); |
319 | QCOMPARE(view->editTriggers(), QAbstractItemView::CurrentChanged); |
320 | view->setEditTriggers(QAbstractItemView::DoubleClicked); |
321 | QCOMPARE(view->editTriggers(), QAbstractItemView::DoubleClicked); |
322 | view->setEditTriggers(QAbstractItemView::SelectedClicked); |
323 | QCOMPARE(view->editTriggers(), QAbstractItemView::SelectedClicked); |
324 | view->setEditTriggers(QAbstractItemView::AnyKeyPressed); |
325 | QCOMPARE(view->editTriggers(), QAbstractItemView::AnyKeyPressed); |
326 | view->setEditTriggers(QAbstractItemView::AllEditTriggers); |
327 | QCOMPARE(view->editTriggers(), QAbstractItemView::AllEditTriggers); |
328 | |
329 | // setAutoScroll |
330 | view->setAutoScroll(false); |
331 | QCOMPARE(view->hasAutoScroll(), false); |
332 | view->setAutoScroll(true); |
333 | QCOMPARE(view->hasAutoScroll(), true); |
334 | |
335 | // setTabKeyNavigation |
336 | view->setTabKeyNavigation(false); |
337 | QCOMPARE(view->tabKeyNavigation(), false); |
338 | view->setTabKeyNavigation(true); |
339 | QCOMPARE(view->tabKeyNavigation(), true); |
340 | #if QT_CONFIG(draganddrop) |
341 | // setDropIndicatorShown |
342 | view->setDropIndicatorShown(false); |
343 | QCOMPARE(view->showDropIndicator(), false); |
344 | view->setDropIndicatorShown(true); |
345 | QCOMPARE(view->showDropIndicator(), true); |
346 | |
347 | // setDragEnabled |
348 | view->setDragEnabled(false); |
349 | QCOMPARE(view->dragEnabled(), false); |
350 | view->setDragEnabled(true); |
351 | QCOMPARE(view->dragEnabled(), true); |
352 | #endif |
353 | |
354 | // setAlternatingRowColors |
355 | view->setAlternatingRowColors(false); |
356 | QCOMPARE(view->alternatingRowColors(), false); |
357 | view->setAlternatingRowColors(true); |
358 | QCOMPARE(view->alternatingRowColors(), true); |
359 | |
360 | // setIconSize |
361 | view->setIconSize(QSize(16, 16)); |
362 | QCOMPARE(view->iconSize(), QSize(16, 16)); |
363 | view->setIconSize(QSize(32, 32)); |
364 | QCOMPARE(view->iconSize(), QSize(32, 32)); |
365 | // Should this happen? |
366 | view->setIconSize(QSize(-1, -1)); |
367 | QCOMPARE(view->iconSize(), QSize(-1, -1)); |
368 | |
369 | QCOMPARE(view->currentIndex(), QModelIndex()); |
370 | QCOMPARE(view->rootIndex(), QModelIndex()); |
371 | |
372 | view->keyboardSearch(search: "" ); |
373 | view->keyboardSearch(search: "foo" ); |
374 | view->keyboardSearch(search: "1" ); |
375 | |
376 | QCOMPARE(view->visualRect(QModelIndex()), QRect()); |
377 | |
378 | view->scrollTo(index: QModelIndex()); |
379 | |
380 | QCOMPARE(view->sizeHintForIndex(QModelIndex()), QSize()); |
381 | QCOMPARE(view->indexAt(QPoint(-1, -1)), QModelIndex()); |
382 | |
383 | if (!view->model()){ |
384 | QCOMPARE(view->indexAt(QPoint(10, 10)), QModelIndex()); |
385 | QCOMPARE(view->sizeHintForRow(0), -1); |
386 | QCOMPARE(view->sizeHintForColumn(0), -1); |
387 | } else if (view->itemDelegate()){ |
388 | view->sizeHintForRow(row: 0); |
389 | view->sizeHintForColumn(column: 0); |
390 | } |
391 | view->openPersistentEditor(index: QModelIndex()); |
392 | view->closePersistentEditor(index: QModelIndex()); |
393 | |
394 | view->reset(); |
395 | view->setRootIndex(QModelIndex()); |
396 | view->doItemsLayout(); |
397 | view->selectAll(); |
398 | // edit() causes warning by default |
399 | //view->edit(QModelIndex()); |
400 | view->clearSelection(); |
401 | view->setCurrentIndex(QModelIndex()); |
402 | } |
403 | |
404 | void tst_QItemView::spider_data() |
405 | { |
406 | setupWithNoTestData(); |
407 | } |
408 | |
409 | void touch(QWidget *widget, Qt::KeyboardModifier modifier, Qt::Key keyPress) |
410 | { |
411 | int width = widget->width(); |
412 | int height = widget->height(); |
413 | for (int i = 0; i < 5; ++i) { |
414 | QTest::mouseClick(widget, button: Qt::LeftButton, stateKey: modifier, |
415 | pos: QPoint(QRandomGenerator::global()->bounded(highest: width), QRandomGenerator::global()->bounded(highest: height))); |
416 | QTest::mouseDClick(widget, button: Qt::LeftButton, stateKey: modifier, |
417 | pos: QPoint(QRandomGenerator::global()->bounded(highest: width), QRandomGenerator::global()->bounded(highest: height))); |
418 | QPoint press(QRandomGenerator::global()->bounded(highest: width), QRandomGenerator::global()->bounded(highest: height)); |
419 | QPoint releasePoint(QRandomGenerator::global()->bounded(highest: width), QRandomGenerator::global()->bounded(highest: height)); |
420 | QTest::mousePress(widget, button: Qt::LeftButton, stateKey: modifier, pos: press); |
421 | QTest::mouseMove(widget, pos: releasePoint); |
422 | if (QRandomGenerator::global()->bounded(highest: 1) == 0) |
423 | QTest::mouseRelease(widget, button: Qt::LeftButton, stateKey: {}, pos: releasePoint); |
424 | else |
425 | QTest::mouseRelease(widget, button: Qt::LeftButton, stateKey: modifier, pos: releasePoint); |
426 | QTest::keyClick(widget, key: keyPress); |
427 | } |
428 | } |
429 | |
430 | /*! |
431 | This is a basic stress testing application that tries a few basics such as clicking around |
432 | the screen, and key presses. |
433 | |
434 | The main goal is to catch any easy segfaults, not to test every case. |
435 | */ |
436 | void tst_QItemView::spider() |
437 | { |
438 | if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland" ), cs: Qt::CaseInsensitive)) |
439 | QSKIP("Wayland: This fails. Figure out why." ); |
440 | |
441 | QFETCH(QString, viewType); |
442 | QFETCH(QAbstractItemView::ScrollMode, vscroll); |
443 | QFETCH(QAbstractItemView::ScrollMode, hscroll); |
444 | |
445 | view = testViews->createView(viewType); |
446 | QVERIFY(view); |
447 | view->setVerticalScrollMode(vscroll); |
448 | view->setHorizontalScrollMode(hscroll); |
449 | view->setModel(treeModel); |
450 | view->show(); |
451 | QVERIFY(QTest::qWaitForWindowActive(view)); |
452 | touch(widget: view->viewport(), modifier: Qt::NoModifier, keyPress: Qt::Key_Left); |
453 | touch(widget: view->viewport(), modifier: Qt::ShiftModifier, keyPress: Qt::Key_Enter); |
454 | touch(widget: view->viewport(), modifier: Qt::ControlModifier, keyPress: Qt::Key_Backspace); |
455 | touch(widget: view->viewport(), modifier: Qt::AltModifier, keyPress: Qt::Key_Up); |
456 | } |
457 | |
458 | void tst_QItemView::resize_data() |
459 | { |
460 | setupWithNoTestData(); |
461 | } |
462 | |
463 | /*! |
464 | The main goal is to catch any infinite loops from layouting |
465 | */ |
466 | void tst_QItemView::resize() |
467 | { |
468 | QSKIP("This test needs to be re-thought out, it takes too long and doesn't really catch the problem." ); |
469 | |
470 | QFETCH(QString, viewType); |
471 | QFETCH(QAbstractItemView::ScrollMode, vscroll); |
472 | QFETCH(QAbstractItemView::ScrollMode, hscroll); |
473 | |
474 | view = testViews->createView(viewType); |
475 | QVERIFY(view); |
476 | view->setVerticalScrollMode(vscroll); |
477 | view->setHorizontalScrollMode(hscroll); |
478 | view->setModel(treeModel); |
479 | view->show(); |
480 | |
481 | for (int w = 100; w < 400; w += 10) { |
482 | for (int h = 100; h < 400; h += 10) { |
483 | view->resize(w, h); |
484 | QTest::qWait(ms: 1); |
485 | QCoreApplication::processEvents(); |
486 | } |
487 | } |
488 | } |
489 | |
490 | void tst_QItemView::visualRect_data() |
491 | { |
492 | setupWithNoTestData(); |
493 | } |
494 | |
495 | void tst_QItemView::visualRect() |
496 | { |
497 | QFETCH(QString, viewType); |
498 | QFETCH(QAbstractItemView::ScrollMode, vscroll); |
499 | QFETCH(QAbstractItemView::ScrollMode, hscroll); |
500 | |
501 | view = testViews->createView(viewType); |
502 | QVERIFY(view); |
503 | view->setVerticalScrollMode(vscroll); |
504 | view->setHorizontalScrollMode(hscroll); |
505 | QCOMPARE(view->visualRect(QModelIndex()), QRect()); |
506 | |
507 | // Add model |
508 | view->setModel(treeModel); |
509 | QCOMPARE(view->visualRect(QModelIndex()), QRect()); |
510 | |
511 | QModelIndex topIndex = treeModel->index(row: 0,column: 0); |
512 | |
513 | QFETCH(bool, displays); |
514 | if (!displays){ |
515 | QCOMPARE(view->visualRect(topIndex), QRect()); |
516 | return; |
517 | } |
518 | |
519 | QVERIFY(view->visualRect(topIndex) != QRect()); |
520 | view->show(); |
521 | QVERIFY(view->visualRect(topIndex) != QRect()); |
522 | |
523 | QCOMPARE(topIndex, view->indexAt(view->visualRect(topIndex).center())); |
524 | QCOMPARE(topIndex, view->indexAt(view->visualRect(topIndex).bottomLeft())); |
525 | QCOMPARE(topIndex, view->indexAt(view->visualRect(topIndex).bottomRight())); |
526 | QCOMPARE(topIndex, view->indexAt(view->visualRect(topIndex).topLeft())); |
527 | QCOMPARE(topIndex, view->indexAt(view->visualRect(topIndex).topRight())); |
528 | |
529 | testViews->hideIndexes(view); |
530 | QModelIndex hiddenIndex = treeModel->index(row: 1, column: 0); |
531 | QCOMPARE(view->visualRect(hiddenIndex), QRect()); |
532 | } |
533 | |
534 | void tst_QItemView::walkScreen(QAbstractItemView *view) |
535 | { |
536 | QModelIndex hiddenIndex = view->model() ? view->model()->index(row: 1, column: 0) : QModelIndex(); |
537 | int width = view->width(); |
538 | int height = view->height(); |
539 | for (int w = 0; w < width; ++w) |
540 | { |
541 | for (int h = 0; h < height; ++h) |
542 | { |
543 | QPoint point(w, h); |
544 | QModelIndex index = view->indexAt(point); |
545 | |
546 | // If we have no model then we should *never* get a valid index |
547 | if (!view->model() || !view->isVisible()) |
548 | QVERIFY(!index.isValid()); |
549 | // index should not be the hidden one |
550 | if (hiddenIndex.isValid()) |
551 | QVERIFY(hiddenIndex != index); |
552 | // If we are valid then check the visualRect for that index |
553 | if (index.isValid()){ |
554 | QRect visualRect = view->visualRect(index); |
555 | if (!visualRect.contains(p: point)) |
556 | qDebug() << point << visualRect; |
557 | QVERIFY(visualRect.contains(point)); |
558 | } |
559 | } |
560 | } |
561 | } |
562 | |
563 | void walkIndex(const QModelIndex &index, const QAbstractItemView *view) |
564 | { |
565 | const QRect visualRect = view->visualRect(index); |
566 | const int width = visualRect.width(); |
567 | const int height = visualRect.height(); |
568 | |
569 | if (width == 0 || height == 0) |
570 | return; |
571 | |
572 | const auto widths = (width < 2) ? QVector<int>({ 0, 1 }) : QVector<int>({ 0, 1, width / 2, width - 2, width - 1 }); |
573 | const auto heights = (height < 2) ? QVector<int>({ 0, 1 }) : QVector<int>({ 0, 1, height / 2, height - 2, height - 1 }); |
574 | for (int w : widths) |
575 | { |
576 | for (int h : heights) |
577 | { |
578 | const QPoint point(visualRect.x() + w, visualRect.y() + h); |
579 | const auto idxAt = view->indexAt(point); |
580 | if (idxAt != index) |
581 | qDebug() << "index" << index << "visualRect" << visualRect << point << view->indexAt(point); |
582 | QCOMPARE(idxAt, index); |
583 | } |
584 | } |
585 | |
586 | } |
587 | |
588 | /*! |
589 | A model that returns an index of parent X should also return X when asking |
590 | for the parent of the index. |
591 | |
592 | This recursive function does pretty extensive testing on the whole model in an |
593 | effort to catch edge cases. |
594 | |
595 | This function assumes that rowCount(), columnCount() and index() work. If they have |
596 | a bug it will point it out, but the above tests should have already found the basic bugs |
597 | because it is easier to figure out the problem in those tests then this one. |
598 | */ |
599 | void checkChildren(const QAbstractItemView *currentView, const QModelIndex &parent = QModelIndex(), int currentDepth = 0) |
600 | { |
601 | QAbstractItemModel *currentModel = currentView->model(); |
602 | |
603 | int rows = currentModel->rowCount(parent); |
604 | int columns = currentModel->columnCount(parent); |
605 | |
606 | for (int r = 0; r < rows; ++r) { |
607 | for (int c = 0; c < columns; ++c) { |
608 | QModelIndex index = currentModel->index(row: r, column: c, parent); |
609 | walkIndex(index, view: currentView); |
610 | if (QTest::currentTestFailed()) |
611 | return; |
612 | |
613 | // recursivly go down |
614 | if (currentModel->hasChildren(parent: index) && currentDepth < 2) { |
615 | checkChildren(currentView, parent: index, currentDepth: ++currentDepth); |
616 | // Because this is recursive we will return at the first failure rather then |
617 | // reporting it over and over |
618 | if (QTest::currentTestFailed()) |
619 | return; |
620 | } |
621 | } |
622 | } |
623 | } |
624 | |
625 | |
626 | void tst_QItemView::indexAt_data() |
627 | { |
628 | setupWithNoTestData(); |
629 | } |
630 | |
631 | void tst_QItemView::indexAt() |
632 | { |
633 | QFETCH(QString, viewType); |
634 | QFETCH(QAbstractItemView::ScrollMode, vscroll); |
635 | QFETCH(QAbstractItemView::ScrollMode, hscroll); |
636 | |
637 | view = testViews->createView(viewType); |
638 | QVERIFY(view); |
639 | view->setVerticalScrollMode(vscroll); |
640 | view->setHorizontalScrollMode(hscroll); |
641 | view->show(); |
642 | view->setModel(treeModel); |
643 | checkChildren(currentView: view); |
644 | |
645 | QModelIndex index = view->model()->index(row: 0, column: 0); |
646 | while (view->model()->hasChildren(parent: index)) |
647 | index = view->model()->index(row: 0, column: 0, parent: index); |
648 | QCOMPARE(view->model()->hasChildren(index), false); |
649 | QVERIFY(index.isValid()); |
650 | view->setRootIndex(index); |
651 | //qDebug() << view->indexAt(QPoint(view->width()/2, view->height()/2)) << view->rootIndex(); |
652 | QPoint p(1, view->height()/2); |
653 | QModelIndex idx = view->indexAt(point: p); |
654 | QCOMPARE(idx, QModelIndex()); |
655 | } |
656 | |
657 | void tst_QItemView::scrollTo_data() |
658 | { |
659 | setupWithNoTestData(); |
660 | } |
661 | |
662 | void tst_QItemView::scrollTo() |
663 | { |
664 | QFETCH(QString, viewType); |
665 | QFETCH(QAbstractItemView::ScrollMode, vscroll); |
666 | QFETCH(QAbstractItemView::ScrollMode, hscroll); |
667 | |
668 | view = testViews->createView(viewType); |
669 | QVERIFY(view); |
670 | view->setVerticalScrollMode(vscroll); |
671 | view->setHorizontalScrollMode(hscroll); |
672 | view->setModel(treeModel); |
673 | view->show(); |
674 | |
675 | QModelIndex parent; |
676 | for (int row = 0; row < treeModel->rowCount(parent); ++row) { |
677 | for (int column = 0; column < treeModel->columnCount(parent); ++column) { |
678 | QModelIndex idx = treeModel->index(row, column, parent); |
679 | view->scrollTo(index: idx); |
680 | QRect rect = view->visualRect(index: idx); |
681 | view->scrollTo(index: idx); |
682 | QCOMPARE(rect, view->visualRect(idx)); |
683 | } |
684 | } |
685 | |
686 | QModelIndex idx = treeModel->index(row: 0, column: 0, parent); |
687 | view->scrollTo(index: idx); |
688 | QRect rect = view->visualRect(index: idx); |
689 | view->scrollToBottom(); |
690 | view->scrollTo(index: idx); |
691 | QCOMPARE(rect, view->visualRect(idx)); |
692 | } |
693 | |
694 | void tst_QItemView::moveCursor_data() |
695 | { |
696 | setupWithNoTestData(); |
697 | } |
698 | |
699 | struct Event |
700 | { |
701 | Event(Qt::Key k, const QModelIndex &s, const QModelIndex &e, const QString &n) |
702 | : key(k), start(s), end(e), name(n){} |
703 | Qt::Key key; |
704 | QModelIndex start; |
705 | QModelIndex end; |
706 | QString name; |
707 | }; |
708 | |
709 | |
710 | void tst_QItemView::moveCursor() |
711 | { |
712 | QFETCH(QString, viewType); |
713 | view = testViews->createView(viewType); |
714 | QVERIFY(view); |
715 | if (view->objectName() == "QHeaderView" ) |
716 | return; |
717 | |
718 | view->setModel(treeModel); |
719 | testViews->hideIndexes(view); |
720 | view->resize(w: 100, h: 100); |
721 | |
722 | QModelIndex invalidIndex = QModelIndex(); |
723 | QModelIndex firstRow = treeModel->index(row: 0, column: 0); |
724 | QModelIndex hiddenRowT = treeModel->index(row: 1, column: 0); |
725 | QModelIndex disabledRowT = treeModel->index(row: 2, column: 0); |
726 | QModelIndex secondRow = treeModel->index(row: 3, column: 0); |
727 | |
728 | QModelIndex secondToLastRow = treeModel->index(row: treeModel->rowCount() - 4, column: 0); |
729 | QModelIndex disabledRowB = treeModel->index(row: treeModel->rowCount() - 3, column: 0); |
730 | QModelIndex hiddenRowB = treeModel->index(row: treeModel->rowCount() - 2, column: 0); |
731 | QModelIndex lastRow = treeModel->index(row: treeModel->rowCount() - 1, column: 0); |
732 | |
733 | QStack<Event> events; |
734 | |
735 | events.push(t: Event(Qt::Key_Up, invalidIndex, firstRow, "inv, first" )); |
736 | events.push(t: Event(Qt::Key_Up, hiddenRowT, firstRow, "hid, first" )); |
737 | events.push(t: Event(Qt::Key_Up, disabledRowT, firstRow, "dis, first" )); |
738 | events.push(t: Event(Qt::Key_Up, firstRow, firstRow, "first, first" )); |
739 | events.push(t: Event(Qt::Key_Up, secondRow, firstRow, "sec, first" )); |
740 | events.push(t: Event(Qt::Key_Up, hiddenRowB, firstRow, "hidB, first" )); |
741 | events.push(t: Event(Qt::Key_Up, disabledRowB, secondToLastRow, "disB, secLast" )); |
742 | events.push(t: Event(Qt::Key_Up, lastRow, secondToLastRow, "last, secLast" )); |
743 | |
744 | events.push(t: Event(Qt::Key_Down, invalidIndex, firstRow, "inv, first" )); |
745 | events.push(t: Event(Qt::Key_Down, hiddenRowT, firstRow, "hid, first" )); |
746 | events.push(t: Event(Qt::Key_Down, disabledRowT, secondRow, "dis, sec" )); |
747 | events.push(t: Event(Qt::Key_Down, firstRow, secondRow, "first, sec" )); |
748 | events.push(t: Event(Qt::Key_Down, secondToLastRow, lastRow, "secLast, last" )); |
749 | events.push(t: Event(Qt::Key_Down, disabledRowB, lastRow, "disB, last" )); |
750 | events.push(t: Event(Qt::Key_Down, hiddenRowB, firstRow, "hidB, first" )); |
751 | events.push(t: Event(Qt::Key_Down, lastRow, lastRow, "last, last" )); |
752 | |
753 | events.push(t: Event(Qt::Key_Home, invalidIndex, firstRow, "inv, first" )); |
754 | events.push(t: Event(Qt::Key_End, invalidIndex, firstRow, "inv, first" )); |
755 | |
756 | if (view->objectName() == "QTableView" ) { |
757 | // In a table we move to the first/last column |
758 | events.push(t: Event(Qt::Key_Home, hiddenRowT, firstRow, "hid, first" )); |
759 | events.push(t: Event(Qt::Key_Home, disabledRowT, disabledRowT, "dis, dis" )); |
760 | events.push(t: Event(Qt::Key_Home, firstRow, firstRow, "first, first" )); |
761 | events.push(t: Event(Qt::Key_Home, secondRow, secondRow, "sec, sec" )); |
762 | events.push(t: Event(Qt::Key_Home, disabledRowB, disabledRowB, "disB, disB" )); |
763 | events.push(t: Event(Qt::Key_Home, hiddenRowB, firstRow, "hidB, first" )); |
764 | events.push(t: Event(Qt::Key_Home, secondToLastRow, secondToLastRow, "secLast, secLast" )); |
765 | events.push(t: Event(Qt::Key_Home, lastRow, lastRow, "last, last" )); |
766 | |
767 | int col = treeModel->columnCount() - 1; |
768 | events.push(t: Event(Qt::Key_End, hiddenRowT, firstRow, "hidT, hidT" )); |
769 | events.push(t: Event(Qt::Key_End, disabledRowT, disabledRowT, "disT, disT" )); |
770 | events.push(t: Event(Qt::Key_End, firstRow, firstRow.sibling(arow: firstRow.row(), acolumn: col), "first, first_C" )); |
771 | events.push(t: Event(Qt::Key_End, secondRow, secondRow.sibling(arow: secondRow.row(), acolumn: col), "sec, sec_C" )); |
772 | events.push(t: Event(Qt::Key_End, disabledRowB, disabledRowB, "disB, disB" )); |
773 | events.push(t: Event(Qt::Key_End, hiddenRowB, firstRow, "hidB, hidB" )); |
774 | events.push(t: Event(Qt::Key_End, secondToLastRow, secondToLastRow.sibling(arow: secondToLastRow.row(), acolumn: col), "secLast, secLast_C" )); |
775 | events.push(t: Event(Qt::Key_End, lastRow, lastRow.sibling(arow: lastRow.row(), acolumn: col), "last, last_C" )); |
776 | } else { |
777 | events.push(t: Event(Qt::Key_Home, hiddenRowT, firstRow, "hid, first" )); |
778 | events.push(t: Event(Qt::Key_Home, disabledRowT, firstRow, "dis, first" )); |
779 | events.push(t: Event(Qt::Key_Home, firstRow, firstRow, "first, first" )); |
780 | events.push(t: Event(Qt::Key_Home, secondRow, firstRow, "sec, first" )); |
781 | events.push(t: Event(Qt::Key_Home, disabledRowB, firstRow, "disB, first" )); |
782 | events.push(t: Event(Qt::Key_Home, hiddenRowB, firstRow, "hidB, first" )); |
783 | events.push(t: Event(Qt::Key_Home, secondToLastRow, firstRow, "sec, first" )); |
784 | events.push(t: Event(Qt::Key_Home, lastRow, firstRow, "last, first" )); |
785 | |
786 | events.push(t: Event(Qt::Key_End, hiddenRowT, firstRow, "hid, last" )); |
787 | events.push(t: Event(Qt::Key_End, disabledRowT, lastRow, "dis, last" )); |
788 | events.push(t: Event(Qt::Key_End, firstRow, lastRow, "first, last" )); |
789 | events.push(t: Event(Qt::Key_End, secondRow, lastRow, "sec, last" )); |
790 | events.push(t: Event(Qt::Key_End, disabledRowB, lastRow, "disB, last" )); |
791 | events.push(t: Event(Qt::Key_End, hiddenRowB, firstRow, "hidB, last" )); |
792 | events.push(t: Event(Qt::Key_End, secondToLastRow, lastRow, "sec, last" )); |
793 | events.push(t: Event(Qt::Key_End, lastRow, lastRow, "last, last" )); |
794 | } |
795 | |
796 | events.push(t: Event(Qt::Key_PageDown, invalidIndex, firstRow, "inv, first" )); |
797 | events.push(t: Event(Qt::Key_PageDown, firstRow, QModelIndex(), "first, x" )); |
798 | events.push(t: Event(Qt::Key_PageDown, secondRow, QModelIndex(), "sec, x" )); |
799 | events.push(t: Event(Qt::Key_PageDown, hiddenRowT, QModelIndex(), "hid, x" )); |
800 | events.push(t: Event(Qt::Key_PageDown, disabledRowT, QModelIndex(), "dis, x" )); |
801 | events.push(t: Event(Qt::Key_PageDown, disabledRowB, lastRow, "disB, last" )); |
802 | events.push(t: Event(Qt::Key_PageDown, hiddenRowB, lastRow, "hidB, last" )); |
803 | events.push(t: Event(Qt::Key_PageDown, secondToLastRow, lastRow, "secLast, last" )); |
804 | events.push(t: Event(Qt::Key_PageDown, lastRow, lastRow, "last, last" )); |
805 | |
806 | events.push(t: Event(Qt::Key_PageUp, invalidIndex, firstRow, "inv, first" )); |
807 | events.push(t: Event(Qt::Key_PageUp, firstRow, firstRow, "first, first" )); |
808 | events.push(t: Event(Qt::Key_PageUp, secondRow, firstRow, "sec, first" )); |
809 | events.push(t: Event(Qt::Key_PageUp, secondToLastRow, QModelIndex(), "secLast, x" )); |
810 | events.push(t: Event(Qt::Key_PageUp, lastRow, QModelIndex(), "last, x" )); |
811 | |
812 | if (view->objectName() == "QTableView" ) { |
813 | events.push(t: Event(Qt::Key_Left, firstRow, firstRow, "first_0, first" )); |
814 | events.push(t: Event(Qt::Key_Left, firstRow.sibling(arow: 0, acolumn: 1), firstRow, "first_1, first" )); |
815 | events.push(t: Event(Qt::Key_Left, firstRow.sibling(arow: 0, acolumn: 2), firstRow, "first_2, first" )); |
816 | events.push(t: Event(Qt::Key_Left, firstRow.sibling(arow: 0, acolumn: 3), firstRow, "first_3, first" )); |
817 | events.push(t: Event(Qt::Key_Left, secondRow, secondRow, "sec, sec" )); |
818 | |
819 | events.push(t: Event(Qt::Key_Right, firstRow, firstRow.sibling(arow: 0, acolumn: 3), "first, first_3" )); |
820 | events.push(t: Event(Qt::Key_Right, firstRow.sibling(arow: 0, acolumn: 1), firstRow, "first_1, first" )); |
821 | events.push(t: Event(Qt::Key_Right, firstRow.sibling(arow: 0, acolumn: 2), firstRow.sibling(arow: 0, acolumn: 3), "first_2, first_3" )); |
822 | events.push(t: Event(Qt::Key_Right, firstRow.sibling(arow: 0, acolumn: treeModel->columnCount()-1), firstRow.sibling(arow: 0, acolumn: treeModel->columnCount()-1), "first_3, sec" )); |
823 | } |
824 | } |
825 | |
826 | QTEST_MAIN(tst_QItemView) |
827 | #include "tst_qitemview.moc" |
828 | |