| 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 | |