1/****************************************************************************
2**
3** Copyright (C) 2018 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 <QtTest/QtTest>
30#include <QtQuickTest/quicktest.h>
31
32#include <QtQuick/qquickview.h>
33#include <QtQuick/private/qquicktableview_p.h>
34#include <QtQuick/private/qquicktableview_p_p.h>
35#include <QtQuick/private/qquickloader_p.h>
36
37#include <QtQml/qqmlengine.h>
38#include <QtQml/qqmlcontext.h>
39#include <QtQml/qqmlexpression.h>
40#include <QtQml/qqmlincubator.h>
41#include <QtQmlModels/private/qqmlobjectmodel_p.h>
42#include <QtQmlModels/private/qqmllistmodel_p.h>
43
44#include "testmodel.h"
45
46#include "../../shared/util.h"
47#include "../shared/viewtestutil.h"
48#include "../shared/visualtestutil.h"
49
50using namespace QQuickViewTestUtil;
51using namespace QQuickVisualTestUtil;
52
53static const char* kDelegateObjectName = "tableViewDelegate";
54static const char *kDelegatesCreatedCountProp = "delegatesCreatedCount";
55static const char *kModelDataBindingProp = "modelDataBinding";
56
57Q_DECLARE_METATYPE(QMarginsF);
58
59#define GET_QML_TABLEVIEW(PROPNAME) \
60 auto PROPNAME = view->rootObject()->property(#PROPNAME).value<QQuickTableView *>(); \
61 QVERIFY(PROPNAME); \
62 auto PROPNAME ## Private = QQuickTableViewPrivate::get(PROPNAME); \
63 Q_UNUSED(PROPNAME ## Private) void()
64
65#define LOAD_TABLEVIEW(fileName) \
66 view->setSource(testFileUrl(fileName)); \
67 view->show(); \
68 QVERIFY(QTest::qWaitForWindowActive(view)); \
69 GET_QML_TABLEVIEW(tableView)
70
71#define LOAD_TABLEVIEW_ASYNC(fileName) \
72 view->setSource(testFileUrl("asyncloader.qml")); \
73 view->show(); \
74 QVERIFY(QTest::qWaitForWindowActive(view)); \
75 auto loader = view->rootObject()->property("loader").value<QQuickLoader *>(); \
76 loader->setSource(QUrl::fromLocalFile("data/" fileName)); \
77 QTRY_VERIFY(loader->item()); \
78 QCOMPARE(loader->status(), QQuickLoader::Status::Ready); \
79 GET_QML_TABLEVIEW(tableView)
80
81#define WAIT_UNTIL_POLISHED_ARG(item) \
82 QVERIFY(QQuickTest::qIsPolishScheduled(item)); \
83 QVERIFY(QQuickTest::qWaitForItemPolished(item))
84#define WAIT_UNTIL_POLISHED WAIT_UNTIL_POLISHED_ARG(tableView)
85
86class tst_QQuickTableView : public QQmlDataTest
87{
88 Q_OBJECT
89public:
90 tst_QQuickTableView();
91
92 QQuickTableViewAttached *getAttachedObject(const QObject *object) const;
93 QPoint getContextRowAndColumn(const QQuickItem *item) const;
94
95private:
96 QQuickView *view = nullptr;
97
98private slots:
99 void initTestCase() override;
100 void cleanupTestCase();
101
102 void setAndGetModel_data();
103 void setAndGetModel();
104 void emptyModel_data();
105 void emptyModel();
106 void checkPreload_data();
107 void checkPreload();
108 void checkZeroSizedDelegate();
109 void checkImplicitSizeDelegate();
110 void checkColumnWidthWithoutProvider();
111 void checkDelegateWithAnchors();
112 void checkColumnWidthProvider();
113 void checkColumnWidthProviderInvalidReturnValues();
114 void checkColumnWidthProviderNegativeReturnValue();
115 void checkColumnWidthProviderNotCallable();
116 void checkRowHeightWithoutProvider();
117 void checkRowHeightProvider();
118 void checkRowHeightProviderInvalidReturnValues();
119 void checkRowHeightProviderNegativeReturnValue();
120 void checkRowHeightProviderNotCallable();
121 void checkForceLayoutFunction();
122 void checkForceLayoutEndUpDoingALayout();
123 void checkForceLayoutDuringModelChange();
124 void checkForceLayoutWhenAllItemsAreHidden();
125 void checkContentWidthAndHeight();
126 void checkContentWidthAndHeightForSmallTables();
127 void checkPageFlicking();
128 void checkExplicitContentWidthAndHeight();
129 void checkExtents_origin();
130 void checkExtents_endExtent();
131 void checkExtents_moveTableToEdge();
132 void checkContentXY();
133 void noDelegate();
134 void changeDelegateDuringUpdate();
135 void changeModelDuringUpdate();
136 void countDelegateItems_data();
137 void countDelegateItems();
138 void checkLayoutOfEqualSizedDelegateItems_data();
139 void checkLayoutOfEqualSizedDelegateItems();
140 void checkFocusRemoved_data();
141 void checkFocusRemoved();
142 void fillTableViewButNothingMore_data();
143 void fillTableViewButNothingMore();
144 void checkInitialAttachedProperties_data();
145 void checkInitialAttachedProperties();
146 void checkSpacingValues();
147 void checkDelegateParent();
148 void flick_data();
149 void flick();
150 void flickOvershoot_data();
151 void flickOvershoot();
152 void checkRowColumnCount();
153 void modelSignals();
154 void checkModelSignalsUpdateLayout();
155 void dataChangedSignal();
156 void checkThatPoolIsDrainedWhenReuseIsFalse();
157 void checkIfDelegatesAreReused_data();
158 void checkIfDelegatesAreReused();
159 void checkIfDelegatesAreReusedAsymmetricTableSize();
160 void checkContextProperties_data();
161 void checkContextProperties();
162 void checkContextPropertiesQQmlListProperyModel_data();
163 void checkContextPropertiesQQmlListProperyModel();
164 void checkRowAndColumnChangedButNotIndex();
165 void checkThatWeAlwaysEmitChangedUponItemReused();
166 void checkChangingModelFromDelegate();
167 void checkRebuildViewportOnly();
168 void useDelegateChooserWithoutDefault();
169 void checkTableviewInsideAsyncLoader();
170 void hideRowsAndColumns_data();
171 void hideRowsAndColumns();
172 void hideAndShowFirstColumn();
173 void hideAndShowFirstRow();
174 void checkThatRevisionedPropertiesCannotBeUsedInOldImports();
175 void checkSyncView_rootView_data();
176 void checkSyncView_rootView();
177 void checkSyncView_childViews_data();
178 void checkSyncView_childViews();
179 void checkSyncView_differentSizedModels();
180 void checkSyncView_connect_late_data();
181 void checkSyncView_connect_late();
182 void checkSyncView_pageFlicking();
183 void checkSyncView_emptyModel();
184 void delegateWithRequiredProperties();
185 void checkThatFetchMoreIsCalledWhenScrolledToTheEndOfTable();
186 void replaceModel();
187 void checkContentSize_data();
188 void checkContentSize();
189};
190
191tst_QQuickTableView::tst_QQuickTableView()
192{
193}
194
195void tst_QQuickTableView::initTestCase()
196{
197 QQmlDataTest::initTestCase();
198 qmlRegisterType<TestModel>(uri: "TestModel", versionMajor: 0, versionMinor: 1, qmlName: "TestModel");
199 view = createView();
200}
201
202void tst_QQuickTableView::cleanupTestCase()
203{
204 delete view;
205}
206
207QQuickTableViewAttached *tst_QQuickTableView::getAttachedObject(const QObject *object) const
208{
209 QObject *attachedObject = qmlAttachedPropertiesObject<QQuickTableView>(obj: object);
210 return static_cast<QQuickTableViewAttached *>(attachedObject);
211}
212
213QPoint tst_QQuickTableView::getContextRowAndColumn(const QQuickItem *item) const
214{
215 const auto context = qmlContext(item);
216 const int row = context->contextProperty("row").toInt();
217 const int column = context->contextProperty("column").toInt();
218 return QPoint(column, row);
219}
220
221void tst_QQuickTableView::setAndGetModel_data()
222{
223 QTest::addColumn<QVariant>(name: "model");
224
225 QTest::newRow(dataTag: "QAIM 1x1") << TestModelAsVariant(1, 1);
226 QTest::newRow(dataTag: "Number model 1") << QVariant::fromValue(value: 1);
227 QTest::newRow(dataTag: "QStringList 1") << QVariant::fromValue(value: QStringList() << "one");
228}
229
230void tst_QQuickTableView::setAndGetModel()
231{
232 // Test that we can set and get different kind of models
233 QFETCH(QVariant, model);
234 LOAD_TABLEVIEW("plaintableview.qml");
235
236 tableView->setModel(model);
237 QCOMPARE(model, tableView->model());
238}
239
240void tst_QQuickTableView::emptyModel_data()
241{
242 QTest::addColumn<QVariant>(name: "model");
243
244 QTest::newRow(dataTag: "QAIM") << TestModelAsVariant(0, 0);
245 QTest::newRow(dataTag: "Number model") << QVariant::fromValue(value: 0);
246 QTest::newRow(dataTag: "QStringList") << QVariant::fromValue(value: QStringList());
247}
248
249void tst_QQuickTableView::emptyModel()
250{
251 // Check that if we assign an empty model to
252 // TableView, no delegate items will be created.
253 QFETCH(QVariant, model);
254 LOAD_TABLEVIEW("plaintableview.qml");
255
256 tableView->setModel(model);
257 WAIT_UNTIL_POLISHED;
258 QCOMPARE(tableViewPrivate->loadedItems.count(), 0);
259}
260
261void tst_QQuickTableView::checkPreload_data()
262{
263 QTest::addColumn<bool>(name: "reuseItems");
264
265 QTest::newRow(dataTag: "reuse") << true;
266 QTest::newRow(dataTag: "not reuse") << false;
267}
268
269void tst_QQuickTableView::checkPreload()
270{
271 // Check that the reuse pool is filled up with one extra row and
272 // column (pluss corner) after rebuilding the table, but only if we reuse items.
273 QFETCH(bool, reuseItems);
274 LOAD_TABLEVIEW("plaintableview.qml");
275
276 auto model = TestModelAsVariant(100, 100);
277 tableView->setModel(model);
278 tableView->setReuseItems(reuseItems);
279
280 WAIT_UNTIL_POLISHED;
281
282 if (reuseItems) {
283 const int rowCount = tableViewPrivate->loadedRows.count();
284 const int columnCount = tableViewPrivate->loadedColumns.count();
285 const int expectedPoolSize = rowCount + columnCount + 1;
286 QCOMPARE(tableViewPrivate->tableModel->poolSize(), expectedPoolSize);
287 } else {
288 QCOMPARE(tableViewPrivate->tableModel->poolSize(), 0);
289 }
290}
291
292void tst_QQuickTableView::checkZeroSizedDelegate()
293{
294 // Check that if we assign a delegate with empty width and height, we
295 // fall back to use kDefaultColumnWidth and kDefaultRowHeight as
296 // column/row sizes.
297 LOAD_TABLEVIEW("plaintableview.qml");
298
299 auto model = TestModelAsVariant(100, 100);
300 tableView->setModel(model);
301
302 view->rootObject()->setProperty(name: "delegateWidth", value: 0);
303 view->rootObject()->setProperty(name: "delegateHeight", value: 0);
304
305 QTest::ignoreMessage(type: QtWarningMsg, messagePattern: QRegularExpression(".*implicit"));
306
307 WAIT_UNTIL_POLISHED;
308
309 auto items = tableViewPrivate->loadedItems;
310 QVERIFY(!items.isEmpty());
311
312 for (auto fxItem : tableViewPrivate->loadedItems) {
313 auto item = fxItem->item;
314 QCOMPARE(item->width(), kDefaultColumnWidth);
315 QCOMPARE(item->height(), kDefaultRowHeight);
316 }
317}
318
319void tst_QQuickTableView::checkImplicitSizeDelegate()
320{
321 // Check that we can set the size of delegate items using
322 // implicit width/height, instead of forcing the user to
323 // create an attached object by using implicitWidth/Height.
324 LOAD_TABLEVIEW("tableviewimplicitsize.qml");
325
326 auto model = TestModelAsVariant(100, 100);
327 tableView->setModel(model);
328
329 WAIT_UNTIL_POLISHED;
330
331 auto items = tableViewPrivate->loadedItems;
332 QVERIFY(!items.isEmpty());
333
334 for (auto fxItem : tableViewPrivate->loadedItems) {
335 auto item = fxItem->item;
336 QCOMPARE(item->width(), 90);
337 QCOMPARE(item->height(), 60);
338 }
339}
340
341void tst_QQuickTableView::checkColumnWidthWithoutProvider()
342{
343 // Checks that a function isn't assigned to the columnWidthProvider property
344 // and that the column width is then equal to sizeHintForColumn.
345 LOAD_TABLEVIEW("alternatingrowheightcolumnwidth.qml");
346
347 auto model = TestModelAsVariant(10, 10);
348
349 tableView->setModel(model);
350 QVERIFY(tableView->columnWidthProvider().isUndefined());
351
352 WAIT_UNTIL_POLISHED;
353
354 for (const int column : tableViewPrivate->loadedColumns.keys()) {
355 const qreal expectedColumnWidth = tableViewPrivate->sizeHintForColumn(column);
356 for (const int row : tableViewPrivate->loadedRows.keys()) {
357 const auto item = tableViewPrivate->loadedTableItem(cell: QPoint(column, row))->item;
358 QCOMPARE(item->width(), expectedColumnWidth);
359 }
360 }
361}
362
363void tst_QQuickTableView::checkDelegateWithAnchors()
364{
365 // Checks that we issue a warning if the delegate has anchors
366 LOAD_TABLEVIEW("delegatewithanchors.qml");
367
368 QTest::ignoreMessage(type: QtWarningMsg, messagePattern: QRegularExpression(".*anchors"));
369
370 auto model = TestModelAsVariant(1, 1);
371 tableView->setModel(model);
372 WAIT_UNTIL_POLISHED;
373}
374
375void tst_QQuickTableView::checkColumnWidthProvider()
376{
377 // Check that you can assign a function to the columnWidthProvider property, and
378 // that it's used to control (and override) the width of the columns.
379 LOAD_TABLEVIEW("userowcolumnprovider.qml");
380
381 auto model = TestModelAsVariant(10, 10);
382
383 tableView->setModel(model);
384 QVERIFY(tableView->columnWidthProvider().isCallable());
385
386 WAIT_UNTIL_POLISHED;
387
388 for (auto fxItem : tableViewPrivate->loadedItems) {
389 // expectedWidth mirrors the expected return value of the assigned javascript function
390 qreal expectedWidth = fxItem->cell.x() + 10;
391 QCOMPARE(fxItem->item->width(), expectedWidth);
392 }
393}
394
395void tst_QQuickTableView::checkColumnWidthProviderInvalidReturnValues()
396{
397 // Check that we fall back to use default columns widths, if you
398 // assign a function to columnWidthProvider that returns invalid values.
399 LOAD_TABLEVIEW("usefaultyrowcolumnprovider.qml");
400
401 auto model = TestModelAsVariant(10, 10);
402
403 tableView->setModel(model);
404
405 QTest::ignoreMessage(type: QtWarningMsg, messagePattern: QRegularExpression(".*implicit.*zero"));
406
407 WAIT_UNTIL_POLISHED;
408
409 for (auto fxItem : tableViewPrivate->loadedItems)
410 QCOMPARE(fxItem->item->width(), kDefaultColumnWidth);
411}
412
413void tst_QQuickTableView::checkColumnWidthProviderNegativeReturnValue()
414{
415 // Check that we fall back to use the implicit width of the delegate
416 // items if the columnWidthProvider return a negative number.
417 LOAD_TABLEVIEW("userowcolumnprovider.qml");
418
419 auto model = TestModelAsVariant(10, 10);
420 view->rootObject()->setProperty(name: "returnNegativeColumnWidth", value: true);
421
422 tableView->setModel(model);
423
424 WAIT_UNTIL_POLISHED;
425
426 for (auto fxItem : tableViewPrivate->loadedItems)
427 QCOMPARE(fxItem->item->width(), 20);
428}
429
430void tst_QQuickTableView::checkColumnWidthProviderNotCallable()
431{
432 // Check that we fall back to use default columns widths, if you
433 // assign something to columnWidthProvider that is not callable.
434 LOAD_TABLEVIEW("usefaultyrowcolumnprovider.qml");
435
436 auto model = TestModelAsVariant(10, 10);
437
438 tableView->setModel(model);
439 tableView->setRowHeightProvider(QJSValue());
440 tableView->setColumnWidthProvider(QJSValue(10));
441
442 QTest::ignoreMessage(type: QtWarningMsg, messagePattern: QRegularExpression(".Provider.*function"));
443
444 WAIT_UNTIL_POLISHED;
445
446 for (auto fxItem : tableViewPrivate->loadedItems)
447 QCOMPARE(fxItem->item->width(), kDefaultColumnWidth);
448}
449
450void tst_QQuickTableView::checkRowHeightWithoutProvider()
451{
452 // Checks that a function isn't assigned to the rowHeightProvider property
453 // and that the row height is then equal to sizeHintForRow.
454 LOAD_TABLEVIEW("alternatingrowheightcolumnwidth.qml");
455
456 auto model = TestModelAsVariant(10, 10);
457 QVERIFY(tableView->rowHeightProvider().isUndefined());
458
459 tableView->setModel(model);
460
461 WAIT_UNTIL_POLISHED;
462
463 for (const int row : tableViewPrivate->loadedRows.keys()) {
464 const qreal expectedRowHeight = tableViewPrivate->sizeHintForRow(row);
465 for (const int column : tableViewPrivate->loadedColumns.keys()) {
466 const auto item = tableViewPrivate->loadedTableItem(cell: QPoint(column, row))->item;
467 QCOMPARE(item->height(), expectedRowHeight);
468 }
469 }
470}
471
472void tst_QQuickTableView::checkRowHeightProvider()
473{
474 // Check that you can assign a function to the columnWidthProvider property, and
475 // that it's used to control (and override) the width of the columns.
476 LOAD_TABLEVIEW("userowcolumnprovider.qml");
477
478 auto model = TestModelAsVariant(10, 10);
479
480 tableView->setModel(model);
481 QVERIFY(tableView->rowHeightProvider().isCallable());
482
483 WAIT_UNTIL_POLISHED;
484
485 for (auto fxItem : tableViewPrivate->loadedItems) {
486 // expectedWidth mirrors the expected return value of the assigned javascript function
487 qreal expectedHeight = fxItem->cell.y() + 10;
488 QCOMPARE(fxItem->item->height(), expectedHeight);
489 }
490}
491
492void tst_QQuickTableView::checkRowHeightProviderInvalidReturnValues()
493{
494 // Check that we fall back to use default row heights, if you
495 // assign a function to rowHeightProvider that returns invalid values.
496 LOAD_TABLEVIEW("usefaultyrowcolumnprovider.qml");
497
498 auto model = TestModelAsVariant(10, 10);
499
500 tableView->setModel(model);
501
502 QTest::ignoreMessage(type: QtWarningMsg, messagePattern: QRegularExpression(".*implicit.*zero"));
503
504 WAIT_UNTIL_POLISHED;
505
506 for (auto fxItem : tableViewPrivate->loadedItems)
507 QCOMPARE(fxItem->item->height(), kDefaultRowHeight);
508}
509
510void tst_QQuickTableView::checkRowHeightProviderNegativeReturnValue()
511{
512 // Check that we fall back to use the implicit height of the delegate
513 // items if the rowHeightProvider return a negative number.
514 LOAD_TABLEVIEW("userowcolumnprovider.qml");
515
516 auto model = TestModelAsVariant(10, 10);
517 view->rootObject()->setProperty(name: "returnNegativeRowHeight", value: true);
518
519 tableView->setModel(model);
520
521 WAIT_UNTIL_POLISHED;
522
523 for (auto fxItem : tableViewPrivate->loadedItems)
524 QCOMPARE(fxItem->item->height(), 20);
525}
526
527void tst_QQuickTableView::checkRowHeightProviderNotCallable()
528{
529 // Check that we fall back to use default row heights, if you
530 // assign something to rowHeightProvider that is not callable.
531 LOAD_TABLEVIEW("usefaultyrowcolumnprovider.qml");
532
533 auto model = TestModelAsVariant(10, 10);
534
535 tableView->setModel(model);
536
537 tableView->setColumnWidthProvider(QJSValue());
538 tableView->setRowHeightProvider(QJSValue(10));
539
540 QTest::ignoreMessage(type: QtWarningMsg, messagePattern: QRegularExpression(".*Provider.*function"));
541
542 WAIT_UNTIL_POLISHED;
543
544 for (auto fxItem : tableViewPrivate->loadedItems)
545 QCOMPARE(fxItem->item->height(), kDefaultRowHeight);
546}
547
548void tst_QQuickTableView::checkForceLayoutFunction()
549{
550 // When we set the 'columnWidths' property in the test file, the
551 // columnWidthProvider should return other values than it did during
552 // start-up. Check that this takes effect after a call to the 'forceLayout()' function.
553 LOAD_TABLEVIEW("forcelayout.qml");
554
555 const char *propertyName = "columnWidths";
556 auto model = TestModelAsVariant(10, 10);
557
558 tableView->setModel(model);
559
560 WAIT_UNTIL_POLISHED;
561
562 // Check that the initial column widths are as specified in the QML file
563 const qreal initialColumnWidth = view->rootObject()->property(name: propertyName).toReal();
564 for (auto fxItem : tableViewPrivate->loadedItems)
565 QCOMPARE(fxItem->item->width(), initialColumnWidth);
566
567 // Change the return value from the columnWidthProvider to something else
568 const qreal newColumnWidth = 100;
569 view->rootObject()->setProperty(name: propertyName, value: newColumnWidth);
570 tableView->forceLayout();
571 // We don't have to polish; The re-layout happens immediately
572
573 for (auto fxItem : tableViewPrivate->loadedItems)
574 QCOMPARE(fxItem->item->width(), newColumnWidth);
575}
576
577void tst_QQuickTableView::checkForceLayoutEndUpDoingALayout()
578{
579 // QTBUG-77074
580 // Check that we change the implicit size of the delegate after
581 // the initial loading, and at the same time hide some rows or
582 // columns, and then do a forceLayout(), we end up with a
583 // complete relayout that respects the new implicit size.
584 LOAD_TABLEVIEW("tweakimplicitsize.qml");
585
586 auto model = TestModelAsVariant(10, 10);
587
588 tableView->setModel(model);
589
590 WAIT_UNTIL_POLISHED;
591
592 const qreal newDelegateSize = 20;
593 view->rootObject()->setProperty(name: "delegateSize", value: newDelegateSize);
594 // Hide a row, just to force the following relayout to
595 // do a complete reload (and not just a relayout)
596 view->rootObject()->setProperty(name: "hideRow", value: 1);
597 tableView->forceLayout();
598
599 for (auto fxItem : tableViewPrivate->loadedItems)
600 QCOMPARE(fxItem->item->height(), newDelegateSize);
601
602 // Check that the content height has been updated as well
603 const qreal rowSpacing = tableView->rowSpacing();
604 const qreal colSpacing = tableView->columnSpacing();
605 QCOMPARE(tableView->contentWidth(), (10 * (newDelegateSize + colSpacing)) - colSpacing);
606 QCOMPARE(tableView->contentHeight(), (9 * (newDelegateSize + rowSpacing)) - rowSpacing);
607}
608
609void tst_QQuickTableView::checkForceLayoutDuringModelChange()
610{
611 // Check that TableView doesn't assert if we call
612 // forceLayout() in the middle of a model change.
613 LOAD_TABLEVIEW("plaintableview.qml");
614
615 const int initialRowCount = 10;
616 TestModel model(initialRowCount, 10);
617 tableView->setModel(QVariant::fromValue(value: &model));
618
619 connect(sender: &model, signal: &QAbstractItemModel::rowsInserted, slot: [=](){
620 QCOMPARE(tableView->rows(), initialRowCount);
621 tableView->forceLayout();
622 QCOMPARE(tableView->rows(), initialRowCount + 1);
623 });
624
625 WAIT_UNTIL_POLISHED;
626
627 QCOMPARE(tableView->rows(), initialRowCount);
628 model.addRow(row: 0);
629 QCOMPARE(tableView->rows(), initialRowCount + 1);
630}
631
632void tst_QQuickTableView::checkForceLayoutWhenAllItemsAreHidden()
633{
634 // Check that you can have a TableView where all columns are
635 // initially hidden, and then show some columns and call
636 // forceLayout(). This should make the columns become visible.
637 LOAD_TABLEVIEW("forcelayout.qml");
638
639 // Tell all columns to be hidden
640 const char *propertyName = "columnWidths";
641 view->rootObject()->setProperty(name: propertyName, value: 0);
642
643 const int rows = 3;
644 const int columns = 3;
645 auto model = TestModelAsVariant(rows, columns);
646 tableView->setModel(model);
647
648 WAIT_UNTIL_POLISHED;
649
650 // Check that the we have no items loaded
651 QCOMPARE(tableViewPrivate->loadedColumns.count(), 0);
652 QCOMPARE(tableViewPrivate->loadedRows.count(), 0);
653 QCOMPARE(tableViewPrivate->loadedItems.count(), 0);
654
655 // Tell all columns to be visible
656 view->rootObject()->setProperty(name: propertyName, value: 10);
657 tableView->forceLayout();
658
659 QCOMPARE(tableViewPrivate->loadedRows.count(), rows);
660 QCOMPARE(tableViewPrivate->loadedColumns.count(), columns);
661 QCOMPARE(tableViewPrivate->loadedItems.count(), rows * columns);
662}
663
664void tst_QQuickTableView::checkContentWidthAndHeight()
665{
666 // Check that contentWidth/Height reports the correct size of the
667 // table, based on knowledge of the rows and columns that has been loaded.
668 LOAD_TABLEVIEW("contentwidthheight.qml");
669
670 // Vertical and horizontal properties should be mirrored, so we only have
671 // to do the calculations once, and use them for both axis, below.
672 QCOMPARE(tableView->width(), tableView->height());
673 QCOMPARE(tableView->rowSpacing(), tableView->columnSpacing());
674
675 const int tableSize = 100;
676 const int cellSizeSmall = 100;
677 const int spacing = 1;
678 auto model = TestModelAsVariant(tableSize, tableSize);
679
680 tableView->setModel(model);
681
682 WAIT_UNTIL_POLISHED;
683
684 const qreal expectedSizeInit = (tableSize * cellSizeSmall) + ((tableSize - 1) * spacing);
685 QCOMPARE(tableView->contentWidth(), expectedSizeInit);
686 QCOMPARE(tableView->contentHeight(), expectedSizeInit);
687 QCOMPARE(tableViewPrivate->averageEdgeSize.width(), cellSizeSmall);
688 QCOMPARE(tableViewPrivate->averageEdgeSize.height(), cellSizeSmall);
689
690 // Flick to the end, and check that content width/height stays unchanged
691 tableView->setContentX(tableView->contentWidth() - tableView->width());
692 tableView->setContentY(tableView->contentHeight() - tableView->height());
693
694 QCOMPARE(tableView->contentWidth(), expectedSizeInit);
695 QCOMPARE(tableView->contentHeight(), expectedSizeInit);
696
697 // Flick back to start
698 tableView->setContentX(0);
699 tableView->setContentY(0);
700
701 // Since we move the viewport more than a page, tableview
702 // will jump to the new position and do a rebuild.
703 QVERIFY(tableViewPrivate->polishScheduled);
704 QVERIFY(tableViewPrivate->scheduledRebuildOptions);
705 WAIT_UNTIL_POLISHED;
706
707 // We should still have the same content width/height as when we started
708 QCOMPARE(tableView->contentWidth(), expectedSizeInit);
709 QCOMPARE(tableView->contentHeight(), expectedSizeInit);
710}
711
712void tst_QQuickTableView::checkContentWidthAndHeightForSmallTables()
713{
714 // For tables where all the columns in the model are loaded, we know
715 // the exact table width, and can therefore update the content width
716 // if e.g new rows are added or removed. The same is true for rows.
717 // This test will check that we do so.
718 LOAD_TABLEVIEW("sizefromdelegate.qml");
719
720 TestModel model(3, 3);
721 tableView->setModel(QVariant::fromValue(value: &model));
722 WAIT_UNTIL_POLISHED;
723
724 const qreal initialContentWidth = tableView->contentWidth();
725 const qreal initialContentHeight = tableView->contentHeight();
726 const QString longText = QStringLiteral("Adding a row with a very long text");
727 model.insertRow(arow: 0);
728 model.setModelData(cell: QPoint(0, 0), span: QSize(1, 1), string: longText);
729
730 WAIT_UNTIL_POLISHED;
731
732 QVERIFY(tableView->contentWidth() > initialContentWidth);
733 QVERIFY(tableView->contentHeight() > initialContentHeight);
734}
735
736void tst_QQuickTableView::checkPageFlicking()
737{
738 // Check that we rebuild the table instead of refilling edges, if the viewport moves
739 // more than a page (the size of TableView).
740 LOAD_TABLEVIEW("plaintableview.qml");
741
742 const int cellWidth = 100;
743 const int cellHeight = 50;
744 auto model = TestModelAsVariant(10000, 10000);
745 const auto &loadedRows = tableViewPrivate->loadedRows;
746 const auto &loadedColumns = tableViewPrivate->loadedColumns;
747
748 tableView->setModel(model);
749
750 WAIT_UNTIL_POLISHED;
751
752 // Sanity check startup table
753 QCOMPARE(tableViewPrivate->topRow(), 0);
754 QCOMPARE(tableViewPrivate->leftColumn(), 0);
755 QCOMPARE(loadedRows.count(), tableView->height() / cellHeight);
756 QCOMPARE(loadedColumns.count(), tableView->width() / cellWidth);
757
758 // Since all cells have the same size, the average row/column
759 // size found by TableView should be exactly equal to this.
760 QCOMPARE(tableViewPrivate->averageEdgeSize.width(), cellWidth);
761 QCOMPARE(tableViewPrivate->averageEdgeSize.height(), cellHeight);
762
763 QCOMPARE(tableViewPrivate->scheduledRebuildOptions, QQuickTableViewPrivate::RebuildOption::None);
764
765 // Flick 5000 columns to the right, and check that this triggers a
766 // rebuild, and that we end up at the expected top-left.
767 const int flickToColumn = 5000;
768 const qreal columnSpacing = tableView->columnSpacing();
769 const qreal flickToColumnInPixels = ((cellWidth + columnSpacing) * flickToColumn) - columnSpacing;
770 tableView->setContentX(flickToColumnInPixels);
771
772 QVERIFY(tableViewPrivate->scheduledRebuildOptions & QQuickTableViewPrivate::RebuildOption::ViewportOnly);
773 QVERIFY(tableViewPrivate->scheduledRebuildOptions & QQuickTableViewPrivate::RebuildOption::CalculateNewTopLeftColumn);
774 QVERIFY(!(tableViewPrivate->scheduledRebuildOptions & QQuickTableViewPrivate::RebuildOption::CalculateNewTopLeftRow));
775
776 WAIT_UNTIL_POLISHED;
777
778 QCOMPARE(tableViewPrivate->topRow(), 0);
779 QCOMPARE(tableViewPrivate->leftColumn(), flickToColumn);
780 QCOMPARE(loadedColumns.count(), tableView->width() / cellWidth);
781 QCOMPARE(loadedRows.count(), tableView->height() / cellHeight);
782
783 // Flick 5000 rows down as well. Since flicking down should only calculate a new row (but
784 // keep the current column), we deliberatly change the average width to check that it's
785 // actually ignored by the rebuild, and that the column stays the same.
786 tableViewPrivate->averageEdgeSize.rwidth() /= 2;
787
788 const int flickToRow = 5000;
789 const qreal rowSpacing = tableView->rowSpacing();
790 const qreal flickToRowInPixels = ((cellHeight + rowSpacing) * flickToRow) - rowSpacing;
791 tableView->setContentY(flickToRowInPixels);
792
793 QVERIFY(tableViewPrivate->scheduledRebuildOptions & QQuickTableViewPrivate::RebuildOption::ViewportOnly);
794 QVERIFY(!(tableViewPrivate->scheduledRebuildOptions & QQuickTableViewPrivate::RebuildOption::CalculateNewTopLeftColumn));
795 QVERIFY(tableViewPrivate->scheduledRebuildOptions & QQuickTableViewPrivate::RebuildOption::CalculateNewTopLeftRow);
796
797 WAIT_UNTIL_POLISHED;
798
799 QCOMPARE(tableViewPrivate->topRow(), flickToColumn);
800 QCOMPARE(tableViewPrivate->leftColumn(), flickToRow);
801 QCOMPARE(loadedRows.count(), tableView->height() / cellHeight);
802 QCOMPARE(loadedColumns.count(), tableView->width() / cellWidth);
803}
804
805void tst_QQuickTableView::checkExplicitContentWidthAndHeight()
806{
807 // Check that you can set a custom contentWidth/Height, and that
808 // TableView doesn't override it while loading more rows and columns.
809 LOAD_TABLEVIEW("contentwidthheight.qml");
810
811 tableView->setContentWidth(1000);
812 tableView->setContentHeight(1000);
813 QCOMPARE(tableView->contentWidth(), 1000);
814 QCOMPARE(tableView->contentHeight(), 1000);
815
816 auto model = TestModelAsVariant(100, 100);
817 tableView->setModel(model);
818 WAIT_UNTIL_POLISHED;
819
820 // Flick somewhere. It should not affect the contentWidth/Height
821 tableView->setContentX(500);
822 tableView->setContentY(500);
823 QCOMPARE(tableView->contentWidth(), 1000);
824 QCOMPARE(tableView->contentHeight(), 1000);
825}
826
827void tst_QQuickTableView::checkExtents_origin()
828{
829 // Check that if the beginning of the content view doesn't match the
830 // actual size of the table, origin will be adjusted to make it fit.
831 LOAD_TABLEVIEW("contentwidthheight.qml");
832
833 const int rows = 10;
834 const int columns = rows;
835 const qreal columnWidth = 100;
836 const qreal rowHeight = 100;
837 const qreal actualTableSize = columns * columnWidth;
838
839 // Set a content size that is far too large
840 // compared to the size of the table.
841 tableView->setContentWidth(actualTableSize * 2);
842 tableView->setContentHeight(actualTableSize * 2);
843 tableView->setRowSpacing(0);
844 tableView->setColumnSpacing(0);
845 tableView->setLeftMargin(0);
846 tableView->setRightMargin(0);
847 tableView->setTopMargin(0);
848 tableView->setBottomMargin(0);
849
850 auto model = TestModelAsVariant(rows, columns);
851 tableView->setModel(model);
852
853 WAIT_UNTIL_POLISHED;
854
855 // Flick slowly to column 5 (to avoid rebuilds). Flick two columns at a
856 // time to ensure that we create a gap before TableView gets a chance to
857 // adjust endExtent first. This gap on the right side will make TableView
858 // move the table to move to the edge. Because of this, the table will not
859 // be aligned at the start of the content view when we next flick back again.
860 // And this will cause origin to move.
861 for (int x = 0; x <= 6; x += 2) {
862 tableView->setContentX(x * columnWidth);
863 tableView->setContentY(x * rowHeight);
864 }
865
866 // Check that the table has now been moved one column to the right
867 // (One column because that's how far outside the table we ended up flicking above).
868 QCOMPARE(tableViewPrivate->loadedTableOuterRect.right(), actualTableSize + columnWidth);
869
870 // Flick back one column at a time so that TableView detects that the first
871 // column is not at the origin before the "table move" logic kicks in. This
872 // will make TableView adjust the origin.
873 for (int x = 6; x >= 0; x -= 1) {
874 tableView->setContentX(x * columnWidth);
875 tableView->setContentY(x * rowHeight);
876 }
877
878 // The origin will be moved with the same offset that the table was
879 // moved on the right side earlier, which is one column length.
880 QCOMPARE(tableViewPrivate->origin.x(), columnWidth);
881 QCOMPARE(tableViewPrivate->origin.y(), rowHeight);
882}
883
884void tst_QQuickTableView::checkExtents_endExtent()
885{
886 // Check that if we the content view size doesn't match the actual size
887 // of the table, endExtent will be adjusted to make it fit (so that
888 // e.g the the flicking will bounce to a stop at the edge of the table).
889 LOAD_TABLEVIEW("contentwidthheight.qml");
890
891 const int rows = 10;
892 const int columns = rows;
893 const qreal columnWidth = 100;
894 const qreal rowHeight = 100;
895 const qreal actualTableSize = columns * columnWidth;
896
897 // Set a content size that is far too large
898 // compared to the size of the table.
899 tableView->setContentWidth(actualTableSize * 2);
900 tableView->setContentHeight(actualTableSize * 2);
901 tableView->setRowSpacing(0);
902 tableView->setColumnSpacing(0);
903 tableView->setLeftMargin(0);
904 tableView->setRightMargin(0);
905 tableView->setTopMargin(0);
906 tableView->setBottomMargin(0);
907
908 auto model = TestModelAsVariant(rows, columns);
909 tableView->setModel(model);
910
911 WAIT_UNTIL_POLISHED;
912
913 // Flick slowly to column 5 (to avoid rebuilds). This will flick the table to
914 // the last column in the model. But since there still is a lot space left in
915 // the content view, endExtent will be set accordingly to compensate.
916 for (int x = 1; x <= 5; x++)
917 tableView->setContentX(x * columnWidth);
918 QCOMPARE(tableViewPrivate->rightColumn(), columns - 1);
919 qreal expectedEndExtentWidth = actualTableSize - tableView->contentWidth();
920 QCOMPARE(tableViewPrivate->endExtent.width(), expectedEndExtentWidth);
921
922 for (int y = 1; y <= 5; y++)
923 tableView->setContentY(y * rowHeight);
924 QCOMPARE(tableViewPrivate->bottomRow(), rows - 1);
925 qreal expectedEndExtentHeight = actualTableSize - tableView->contentHeight();
926 QCOMPARE(tableViewPrivate->endExtent.height(), expectedEndExtentHeight);
927}
928
929void tst_QQuickTableView::checkExtents_moveTableToEdge()
930{
931 // Check that if we the content view size doesn't match the actual
932 // size of the table, and we fast-flick the viewport to outside
933 // the table, we end up moving the table back into the viewport to
934 // avoid any visual glitches.
935 LOAD_TABLEVIEW("contentwidthheight.qml");
936
937 const int rows = 10;
938 const int columns = rows;
939 const qreal columnWidth = 100;
940 const qreal rowHeight = 100;
941 const qreal actualTableSize = columns * columnWidth;
942
943 // Set a content size that is far to large
944 // compared to the size of the table.
945 tableView->setContentWidth(actualTableSize * 2);
946 tableView->setContentHeight(actualTableSize * 2);
947 tableView->setRowSpacing(0);
948 tableView->setColumnSpacing(0);
949 tableView->setLeftMargin(0);
950 tableView->setRightMargin(0);
951 tableView->setTopMargin(0);
952 tableView->setBottomMargin(0);
953
954 auto model = TestModelAsVariant(rows, columns);
955 tableView->setModel(model);
956
957 WAIT_UNTIL_POLISHED;
958
959 // Flick slowly to column 5 (to avoid rebuilds). Flick two columns at a
960 // time to ensure that we create a gap before TableView gets a chance to
961 // adjust endExtent first. This gap on the right side will make TableView
962 // move the table to the edge (in addition to adjusting the extents, but that
963 // will happen in a subsequent polish, and is not for this test verify).
964 for (int x = 0; x <= 6; x += 2)
965 tableView->setContentX(x * columnWidth);
966 QCOMPARE(tableViewPrivate->rightColumn(), columns - 1);
967 QCOMPARE(tableViewPrivate->loadedTableOuterRect, tableViewPrivate->viewportRect);
968
969 for (int y = 0; y <= 6; y += 2)
970 tableView->setContentY(y * rowHeight);
971 QCOMPARE(tableViewPrivate->bottomRow(), rows - 1);
972 QCOMPARE(tableViewPrivate->loadedTableOuterRect, tableViewPrivate->viewportRect);
973
974 for (int x = 6; x >= 0; x -= 2)
975 tableView->setContentX(x * columnWidth);
976 QCOMPARE(tableViewPrivate->leftColumn(), 0);
977 QCOMPARE(tableViewPrivate->loadedTableOuterRect, tableViewPrivate->viewportRect);
978
979 for (int y = 6; y >= 0; y -= 2)
980 tableView->setContentY(y * rowHeight);
981 QCOMPARE(tableViewPrivate->topRow(), 0);
982 QCOMPARE(tableViewPrivate->loadedTableOuterRect, tableViewPrivate->viewportRect);
983}
984
985void tst_QQuickTableView::checkContentXY()
986{
987 // Check that you can bind contentX and contentY to
988 // e.g show the center of the table at start-up
989 LOAD_TABLEVIEW("setcontentpos.qml");
990
991 auto model = TestModelAsVariant(10, 10);
992 tableView->setModel(model);
993 WAIT_UNTIL_POLISHED;
994
995 QCOMPARE(tableView->width(), 400);
996 QCOMPARE(tableView->height(), 400);
997 QCOMPARE(tableView->contentWidth(), 1000);
998 QCOMPARE(tableView->contentHeight(), 1000);
999
1000 // Check that the content item is positioned according
1001 // to the binding in the QML file (which will set the
1002 // viewport to be at the center of the table).
1003 const qreal expectedXY = (tableView->contentWidth() - tableView->width()) / 2;
1004 QCOMPARE(tableView->contentX(), expectedXY);
1005 QCOMPARE(tableView->contentY(), expectedXY);
1006
1007 // Check that we end up at the correct top-left cell:
1008 const qreal delegateWidth = tableViewPrivate->loadedItems.values().first()->item->width();
1009 const int expectedCellXY = qCeil(v: expectedXY / delegateWidth);
1010 QCOMPARE(tableViewPrivate->leftColumn(), expectedCellXY);
1011 QCOMPARE(tableViewPrivate->topRow(), expectedCellXY);
1012}
1013
1014void tst_QQuickTableView::noDelegate()
1015{
1016 // Check that you can skip setting a delegate without
1017 // it causing any problems (like crashing or asserting).
1018 // And then set a delegate, and do a quick check that the
1019 // view gets populated as expected.
1020 LOAD_TABLEVIEW("plaintableview.qml");
1021
1022 const int rows = 5;
1023 const int columns = 5;
1024 auto model = TestModelAsVariant(columns, rows);
1025 tableView->setModel(model);
1026
1027 // Start with no delegate, and check
1028 // that we end up with no items in the table.
1029 tableView->setDelegate(nullptr);
1030
1031 WAIT_UNTIL_POLISHED;
1032
1033 auto items = tableViewPrivate->loadedItems;
1034 QVERIFY(items.isEmpty());
1035
1036 // Set a delegate, and check that we end
1037 // up with the expected number of items.
1038 auto delegate = view->rootObject()->property(name: "delegate").value<QQmlComponent *>();
1039 QVERIFY(delegate);
1040 tableView->setDelegate(delegate);
1041
1042 WAIT_UNTIL_POLISHED;
1043
1044 items = tableViewPrivate->loadedItems;
1045 QCOMPARE(items.count(), rows * columns);
1046
1047 // And then unset the delegate again, and check
1048 // that we end up with no items.
1049 tableView->setDelegate(nullptr);
1050
1051 WAIT_UNTIL_POLISHED;
1052
1053 items = tableViewPrivate->loadedItems;
1054 QVERIFY(items.isEmpty());
1055}
1056
1057void tst_QQuickTableView::changeDelegateDuringUpdate()
1058{
1059 // Check that you can change the delegate (set it to null)
1060 // while the TableView is busy loading the table.
1061 LOAD_TABLEVIEW("changemodelordelegateduringupdate.qml");
1062
1063 auto model = TestModelAsVariant(1, 1);
1064 tableView->setModel(model);
1065 view->rootObject()->setProperty(name: "changeDelegate", value: true);
1066
1067 WAIT_UNTIL_POLISHED;
1068
1069 // We should no longer have a delegate, and no
1070 // items should therefore be loaded.
1071 QCOMPARE(tableView->delegate(), nullptr);
1072 QCOMPARE(tableViewPrivate->loadedItems.size(), 0);
1073
1074 // Even if the delegate is missing, we still report
1075 // the correct size of the model
1076 QCOMPARE(tableView->rows(), 1);
1077 QCOMPARE(tableView->columns(), 1);
1078};
1079
1080void tst_QQuickTableView::changeModelDuringUpdate()
1081{
1082 // Check that you can change the model (set it to null)
1083 // while the TableView is buzy loading the table.
1084 LOAD_TABLEVIEW("changemodelordelegateduringupdate.qml");
1085
1086 auto model = TestModelAsVariant(1, 1);
1087 tableView->setModel(model);
1088 view->rootObject()->setProperty(name: "changeModel", value: true);
1089
1090 WAIT_UNTIL_POLISHED;
1091
1092 // We should no longer have a model, and the no
1093 // items should therefore be loaded.
1094 QVERIFY(tableView->model().isNull());
1095 QCOMPARE(tableViewPrivate->loadedItems.size(), 0);
1096
1097 // The empty model has no rows or columns
1098 QCOMPARE(tableView->rows(), 0);
1099 QCOMPARE(tableView->columns(), 0);
1100};
1101
1102void tst_QQuickTableView::countDelegateItems_data()
1103{
1104 QTest::addColumn<QVariant>(name: "model");
1105 QTest::addColumn<int>(name: "count");
1106
1107 QTest::newRow(dataTag: "QAIM 1x1") << TestModelAsVariant(1, 1) << 1;
1108 QTest::newRow(dataTag: "QAIM 2x1") << TestModelAsVariant(2, 1) << 2;
1109 QTest::newRow(dataTag: "QAIM 1x2") << TestModelAsVariant(1, 2) << 2;
1110 QTest::newRow(dataTag: "QAIM 2x2") << TestModelAsVariant(2, 2) << 4;
1111 QTest::newRow(dataTag: "QAIM 4x4") << TestModelAsVariant(4, 4) << 16;
1112
1113 QTest::newRow(dataTag: "Number model 1") << QVariant::fromValue(value: 1) << 1;
1114 QTest::newRow(dataTag: "Number model 4") << QVariant::fromValue(value: 4) << 4;
1115
1116 QTest::newRow(dataTag: "QStringList 1") << QVariant::fromValue(value: QStringList() << "one") << 1;
1117 QTest::newRow(dataTag: "QStringList 4") << QVariant::fromValue(value: QStringList() << "one" << "two" << "three" << "four") << 4;
1118}
1119
1120void tst_QQuickTableView::countDelegateItems()
1121{
1122 // Assign different models of various sizes, and check that the number of
1123 // delegate items in the view matches the size of the model. Note that for
1124 // this test to be valid, all items must be within the visible area of the view.
1125 QFETCH(QVariant, model);
1126 QFETCH(int, count);
1127 LOAD_TABLEVIEW("plaintableview.qml");
1128
1129 tableView->setModel(model);
1130 WAIT_UNTIL_POLISHED;
1131
1132 // Check that tableview internals contain the expected number of items
1133 auto const items = tableViewPrivate->loadedItems;
1134 QCOMPARE(items.count(), count);
1135
1136 // Check that this also matches the items found in the view
1137 auto foundItems = findItems<QQuickItem>(parent: tableView, objectName: kDelegateObjectName);
1138 QCOMPARE(foundItems.count(), count);
1139}
1140
1141void tst_QQuickTableView::checkLayoutOfEqualSizedDelegateItems_data()
1142{
1143 QTest::addColumn<QVariant>(name: "model");
1144 QTest::addColumn<QSize>(name: "tableSize");
1145 QTest::addColumn<QSizeF>(name: "spacing");
1146 QTest::addColumn<QMarginsF>(name: "margins");
1147
1148 // Check spacing together with different table setups
1149 QTest::newRow(dataTag: "QAIM 1x1 1,1") << TestModelAsVariant(1, 1) << QSize(1, 1) << QSizeF(1, 1) << QMarginsF(0, 0, 0, 0);
1150 QTest::newRow(dataTag: "QAIM 5x5 0,0") << TestModelAsVariant(5, 5) << QSize(5, 5) << QSizeF(0, 0) << QMarginsF(0, 0, 0, 0);
1151 QTest::newRow(dataTag: "QAIM 5x5 1,0") << TestModelAsVariant(5, 5) << QSize(5, 5) << QSizeF(1, 0) << QMarginsF(0, 0, 0, 0);
1152 QTest::newRow(dataTag: "QAIM 5x5 0,1") << TestModelAsVariant(5, 5) << QSize(5, 5) << QSizeF(0, 1) << QMarginsF(0, 0, 0, 0);
1153
1154 // Check spacing together with margins
1155 QTest::newRow(dataTag: "QAIM 1x1 1,1 5555") << TestModelAsVariant(1, 1) << QSize(1, 1) << QSizeF(1, 1) << QMarginsF(5, 5, 5, 5);
1156 QTest::newRow(dataTag: "QAIM 4x4 0,0 3333") << TestModelAsVariant(4, 4) << QSize(4, 4) << QSizeF(0, 0) << QMarginsF(3, 3, 3, 3);
1157 QTest::newRow(dataTag: "QAIM 4x4 2,2 1234") << TestModelAsVariant(4, 4) << QSize(4, 4) << QSizeF(2, 2) << QMarginsF(1, 2, 3, 4);
1158
1159 // Check "list" models
1160 QTest::newRow(dataTag: "NumberModel 1x4, 0000") << QVariant::fromValue(value: 4) << QSize(1, 4) << QSizeF(1, 1) << QMarginsF(0, 0, 0, 0);
1161 QTest::newRow(dataTag: "QStringList 1x4, 0,0 1111") << QVariant::fromValue(value: QStringList() << "one" << "two" << "three" << "four")
1162 << QSize(1, 4) << QSizeF(0, 0) << QMarginsF(1, 1, 1, 1);
1163}
1164
1165void tst_QQuickTableView::checkLayoutOfEqualSizedDelegateItems()
1166{
1167 // Check that the geometry of the delegate items are correct
1168 QFETCH(QVariant, model);
1169 QFETCH(QSize, tableSize);
1170 QFETCH(QSizeF, spacing);
1171 QFETCH(QMarginsF, margins);
1172 LOAD_TABLEVIEW("plaintableview.qml");
1173
1174 const qreal expectedItemWidth = 100;
1175 const qreal expectedItemHeight = 50;
1176 const int expectedItemCount = tableSize.width() * tableSize.height();
1177
1178 tableView->setModel(model);
1179 tableView->setRowSpacing(spacing.height());
1180 tableView->setColumnSpacing(spacing.width());
1181
1182 // Setting margins on Flickable should not affect the layout of the
1183 // delegate items, since the margins is "transparent" to the TableView.
1184 tableView->setLeftMargin(margins.left());
1185 tableView->setTopMargin(margins.top());
1186 tableView->setRightMargin(margins.right());
1187 tableView->setBottomMargin(margins.bottom());
1188
1189 WAIT_UNTIL_POLISHED;
1190
1191 auto const items = tableViewPrivate->loadedItems;
1192 QVERIFY(!items.isEmpty());
1193
1194 for (int i = 0; i < expectedItemCount; ++i) {
1195 const QQuickItem *item = items[i]->item;
1196 QVERIFY(item);
1197 QCOMPARE(item->parentItem(), tableView->contentItem());
1198
1199 const QPoint cell = getContextRowAndColumn(item);
1200 qreal expectedX = cell.x() * (expectedItemWidth + spacing.width());
1201 qreal expectedY = cell.y() * (expectedItemHeight + spacing.height());
1202 QCOMPARE(item->x(), expectedX);
1203 QCOMPARE(item->y(), expectedY);
1204 QCOMPARE(item->z(), 1);
1205 QCOMPARE(item->width(), expectedItemWidth);
1206 QCOMPARE(item->height(), expectedItemHeight);
1207 }
1208}
1209
1210void tst_QQuickTableView::checkFocusRemoved_data()
1211{
1212 QTest::addColumn<QString>(name: "focusedItemProp");
1213
1214 QTest::newRow(dataTag: "delegate root") << QStringLiteral("delegateRoot");
1215 QTest::newRow(dataTag: "delegate child") << QStringLiteral("delegateChild");
1216}
1217
1218void tst_QQuickTableView::checkFocusRemoved()
1219{
1220 // Check that we clear the focus of a delegate item when
1221 // a child of the delegate item has focus, and the cell is
1222 // flicked out of view.
1223 QFETCH(QString, focusedItemProp);
1224 LOAD_TABLEVIEW("tableviewfocus.qml");
1225
1226 auto model = TestModelAsVariant(100, 100);
1227 tableView->setModel(model);
1228
1229 WAIT_UNTIL_POLISHED;
1230
1231 auto const item = tableViewPrivate->loadedTableItem(cell: QPoint(0, 0))->item;
1232 auto const focusedItem = qvariant_cast<QQuickItem *>(v: item->property(name: focusedItemProp.toUtf8().data()));
1233 QVERIFY(focusedItem);
1234 QCOMPARE(tableView->hasActiveFocus(), false);
1235 QCOMPARE(focusedItem->hasActiveFocus(), false);
1236
1237 focusedItem->forceActiveFocus();
1238 QCOMPARE(tableView->hasActiveFocus(), true);
1239 QCOMPARE(focusedItem->hasActiveFocus(), true);
1240
1241 // Flick the focused cell out, and check that none of the
1242 // items in the table has focus (which means that the reused
1243 // item lost focus when it was flicked out). But the tableview
1244 // itself will maintain active focus.
1245 tableView->setContentX(500);
1246 QCOMPARE(tableView->hasActiveFocus(), true);
1247 for (auto fxItem : tableViewPrivate->loadedItems) {
1248 auto const focusedItem2 = qvariant_cast<QQuickItem *>(v: fxItem->item->property(name: focusedItemProp.toUtf8().data()));
1249 QCOMPARE(focusedItem2->hasActiveFocus(), false);
1250 }
1251}
1252
1253void tst_QQuickTableView::fillTableViewButNothingMore_data()
1254{
1255 QTest::addColumn<QSizeF>(name: "spacing");
1256
1257 QTest::newRow(dataTag: "0 0,0 0") << QSizeF(0, 0);
1258 QTest::newRow(dataTag: "0 10,10 0") << QSizeF(10, 10);
1259 QTest::newRow(dataTag: "100 10,10 0") << QSizeF(10, 10);
1260 QTest::newRow(dataTag: "0 0,0 100") << QSizeF(0, 0);
1261 QTest::newRow(dataTag: "0 10,10 100") << QSizeF(10, 10);
1262 QTest::newRow(dataTag: "100 10,10 100") << QSizeF(10, 10);
1263}
1264
1265void tst_QQuickTableView::fillTableViewButNothingMore()
1266{
1267 // Check that we end up filling the whole visible part of
1268 // the tableview with cells, but nothing more.
1269 QFETCH(QSizeF, spacing);
1270 LOAD_TABLEVIEW("plaintableview.qml");
1271
1272 const int rows = 100;
1273 const int columns = 100;
1274 auto model = TestModelAsVariant(rows, columns);
1275
1276 tableView->setModel(model);
1277 tableView->setRowSpacing(spacing.height());
1278 tableView->setColumnSpacing(spacing.width());
1279
1280 WAIT_UNTIL_POLISHED;
1281
1282 auto const topLeftFxItem = tableViewPrivate->loadedTableItem(cell: QPoint(0, 0));
1283 auto const topLeftItem = topLeftFxItem->item;
1284
1285 auto const bottomRightLoadedCell = QPoint(tableViewPrivate->rightColumn(), tableViewPrivate->bottomRow());
1286 auto const bottomRightFxItem = tableViewPrivate->loadedTableItem(cell: bottomRightLoadedCell);
1287 auto const bottomRightItem = bottomRightFxItem->item;
1288 const QPoint bottomRightCell = getContextRowAndColumn(item: bottomRightItem.data());
1289
1290 // Check that the right-most item is overlapping the right edge of the view
1291 QVERIFY(bottomRightItem->x() < tableView->width());
1292 QVERIFY(bottomRightItem->x() + bottomRightItem->width() >= tableView->width() - spacing.width());
1293
1294 // Check that the actual number of columns matches what we expect
1295 qreal cellWidth = bottomRightItem->width() + spacing.width();
1296 int expectedColumns = qCeil(v: tableView->width() / cellWidth);
1297 int actualColumns = bottomRightCell.x() + 1;
1298 QCOMPARE(actualColumns, expectedColumns);
1299
1300 // Check that the bottom-most item is overlapping the bottom edge of the view
1301 QVERIFY(bottomRightItem->y() < tableView->height());
1302 QVERIFY(bottomRightItem->y() + bottomRightItem->height() >= tableView->height() - spacing.height());
1303
1304 // Check that the actual number of rows matches what we expect
1305 qreal cellHeight = bottomRightItem->height() + spacing.height();
1306 int expectedRows = qCeil(v: tableView->height() / cellHeight);
1307 int actualRows = bottomRightCell.y() + 1;
1308 QCOMPARE(actualRows, expectedRows);
1309}
1310
1311void tst_QQuickTableView::checkInitialAttachedProperties_data()
1312{
1313 QTest::addColumn<QVariant>(name: "model");
1314
1315 QTest::newRow(dataTag: "QAIM") << TestModelAsVariant(4, 4);
1316 QTest::newRow(dataTag: "Number model") << QVariant::fromValue(value: 4);
1317 QTest::newRow(dataTag: "QStringList") << QVariant::fromValue(value: QStringList() << "0" << "1" << "2" << "3");
1318}
1319
1320void tst_QQuickTableView::checkInitialAttachedProperties()
1321{
1322 // Check that the context and attached properties inside
1323 // the delegate items are what we expect at start-up.
1324 QFETCH(QVariant, model);
1325 LOAD_TABLEVIEW("plaintableview.qml");
1326
1327 tableView->setModel(model);
1328
1329 WAIT_UNTIL_POLISHED;
1330
1331 for (auto fxItem : tableViewPrivate->loadedItems) {
1332 const int index = fxItem->index;
1333 const auto item = fxItem->item;
1334 const auto context = qmlContext(item.data());
1335 const QPoint cell = tableViewPrivate->cellAtModelIndex(modelIndex: index);
1336 const int contextIndex = context->contextProperty("index").toInt();
1337 const QPoint contextCell = getContextRowAndColumn(item: item.data());
1338 const QString contextModelData = context->contextProperty("modelData").toString();
1339
1340 QCOMPARE(contextCell.y(), cell.y());
1341 QCOMPARE(contextCell.x(), cell.x());
1342 QCOMPARE(contextIndex, index);
1343 QCOMPARE(contextModelData, QStringLiteral("%1").arg(cell.y()));
1344 QCOMPARE(getAttachedObject(item)->view(), tableView);
1345 }
1346}
1347
1348void tst_QQuickTableView::checkSpacingValues()
1349{
1350 LOAD_TABLEVIEW("tableviewdefaultspacing.qml");
1351
1352 int rowCount = 9;
1353 int columnCount = 9;
1354 int delegateWidth = 15;
1355 int delegateHeight = 10;
1356 auto model = TestModelAsVariant(rowCount, columnCount);
1357 tableView->setModel(model);
1358
1359 WAIT_UNTIL_POLISHED;
1360
1361 // Default spacing : 0
1362 QCOMPARE(tableView->rowSpacing(), 0);
1363 QCOMPARE(tableView->columnSpacing(), 0);
1364
1365 tableView->polish();
1366 WAIT_UNTIL_POLISHED;
1367
1368 qreal expectedContentWidth = columnCount * (delegateWidth + tableView->columnSpacing()) - tableView->columnSpacing();
1369 qreal expectedContentHeight = rowCount * (delegateHeight + tableView->rowSpacing()) - tableView->rowSpacing();
1370 QCOMPARE(tableView->contentWidth(), expectedContentWidth);
1371 QCOMPARE(tableView->contentHeight(), expectedContentHeight);
1372
1373 // Valid spacing assignment
1374 tableView->setRowSpacing(42);
1375 tableView->setColumnSpacing(12);
1376 QCOMPARE(tableView->rowSpacing(), 42);
1377 QCOMPARE(tableView->columnSpacing(), 12);
1378
1379 tableView->polish();
1380 WAIT_UNTIL_POLISHED;
1381
1382 expectedContentWidth = columnCount * (delegateWidth + tableView->columnSpacing()) - tableView->columnSpacing();
1383 expectedContentHeight = rowCount * (delegateHeight + tableView->rowSpacing()) - tableView->rowSpacing();
1384 QCOMPARE(tableView->contentWidth(), expectedContentWidth);
1385 QCOMPARE(tableView->contentHeight(), expectedContentHeight);
1386
1387 // Negative spacing is allowed, and can be used to eliminate double edges
1388 // in the grid if the delegate is a rectangle with a border.
1389 tableView->setRowSpacing(-1);
1390 tableView->setColumnSpacing(-1);
1391 QCOMPARE(tableView->rowSpacing(), -1);
1392 QCOMPARE(tableView->columnSpacing(), -1);
1393
1394 tableView->setRowSpacing(10);
1395 tableView->setColumnSpacing(10);
1396 // Invalid assignments (should ignore)
1397 tableView->setRowSpacing(INFINITY);
1398 tableView->setColumnSpacing(INFINITY);
1399 tableView->setRowSpacing(NAN);
1400 tableView->setColumnSpacing(NAN);
1401 QCOMPARE(tableView->rowSpacing(), 10);
1402 QCOMPARE(tableView->columnSpacing(), 10);
1403}
1404
1405void tst_QQuickTableView::checkDelegateParent()
1406{
1407 // Check that TableView sets the delegate parent before
1408 // bindings are evaluated, so that the app can bind to it.
1409 LOAD_TABLEVIEW("plaintableview.qml");
1410
1411 auto model = TestModelAsVariant(100, 100);
1412 tableView->setModel(model);
1413
1414 WAIT_UNTIL_POLISHED;
1415
1416 QVERIFY(view->rootObject()->property("delegateParentSetBeforeCompleted").toBool());
1417}
1418
1419void tst_QQuickTableView::flick_data()
1420{
1421 QTest::addColumn<QSizeF>(name: "spacing");
1422 QTest::addColumn<QMarginsF>(name: "margins");
1423 QTest::addColumn<bool>(name: "reuseItems");
1424
1425 QTest::newRow(dataTag: "s:0 m:0 reuse") << QSizeF(0, 0) << QMarginsF(0, 0, 0, 0) << true;
1426 QTest::newRow(dataTag: "s:5 m:0 reuse") << QSizeF(5, 5) << QMarginsF(0, 0, 0, 0) << true;
1427 QTest::newRow(dataTag: "s:0 m:20 reuse") << QSizeF(0, 0) << QMarginsF(20, 20, 20, 20) << true;
1428 QTest::newRow(dataTag: "s:5 m:20 reuse") << QSizeF(5, 5) << QMarginsF(20, 20, 20, 20) << true;
1429 QTest::newRow(dataTag: "s:0 m:0") << QSizeF(0, 0) << QMarginsF(0, 0, 0, 0) << false;
1430 QTest::newRow(dataTag: "s:5 m:0") << QSizeF(5, 5) << QMarginsF(0, 0, 0, 0) << false;
1431 QTest::newRow(dataTag: "s:0 m:20") << QSizeF(0, 0) << QMarginsF(20, 20, 20, 20) << false;
1432 QTest::newRow(dataTag: "s:5 m:20") << QSizeF(5, 5) << QMarginsF(20, 20, 20, 20) << false;
1433}
1434
1435void tst_QQuickTableView::flick()
1436{
1437 // Check that if we end up with the correct start and end column/row as we flick around
1438 // with different table configurations.
1439 QFETCH(QSizeF, spacing);
1440 QFETCH(QMarginsF, margins);
1441 QFETCH(bool, reuseItems);
1442 LOAD_TABLEVIEW("plaintableview.qml");
1443
1444 const qreal delegateWidth = 100;
1445 const qreal delegateHeight = 50;
1446 const int visualColumnCount = 4;
1447 const int visualRowCount = 4;
1448 const qreal cellWidth = delegateWidth + spacing.width();
1449 const qreal cellHeight = delegateHeight + spacing.height();
1450 auto model = TestModelAsVariant(100, 100);
1451
1452 tableView->setModel(model);
1453 tableView->setRowSpacing(spacing.height());
1454 tableView->setColumnSpacing(spacing.width());
1455 tableView->setLeftMargin(margins.left());
1456 tableView->setTopMargin(margins.top());
1457 tableView->setRightMargin(margins.right());
1458 tableView->setBottomMargin(margins.bottom());
1459 tableView->setReuseItems(reuseItems);
1460 tableView->setWidth(margins.left() + (visualColumnCount * cellWidth) - spacing.width());
1461 tableView->setHeight(margins.top() + (visualRowCount * cellHeight) - spacing.height());
1462
1463 WAIT_UNTIL_POLISHED;
1464
1465 // Check the "simple" case if the cells never lands egde-to-edge with the viewport. For
1466 // that case we only accept that visible row/columns are loaded.
1467 qreal flickValues[] = {0.5, 1.5, 4.5, 20.5, 10.5, 3.5, 1.5, 0.5};
1468
1469 for (qreal cellsToFlick : flickValues) {
1470 // Flick to the beginning of the cell
1471 tableView->setContentX(cellsToFlick * cellWidth);
1472 tableView->setContentY(cellsToFlick * cellHeight);
1473 tableView->polish();
1474
1475 WAIT_UNTIL_POLISHED;
1476
1477 const int expectedTableLeft = int(cellsToFlick - int((margins.left() + spacing.width()) / cellWidth));
1478 const int expectedTableTop = int(cellsToFlick - int((margins.top() + spacing.height()) / cellHeight));
1479
1480 QCOMPARE(tableViewPrivate->leftColumn(), expectedTableLeft);
1481 QCOMPARE(tableViewPrivate->rightColumn(), expectedTableLeft + visualColumnCount);
1482 QCOMPARE(tableViewPrivate->topRow(), expectedTableTop);
1483 QCOMPARE(tableViewPrivate->bottomRow(), expectedTableTop + visualRowCount);
1484 }
1485}
1486
1487void tst_QQuickTableView::flickOvershoot_data()
1488{
1489 QTest::addColumn<QSizeF>(name: "spacing");
1490 QTest::addColumn<QMarginsF>(name: "margins");
1491 QTest::addColumn<bool>(name: "reuseItems");
1492
1493 QTest::newRow(dataTag: "s:0 m:0 reuse") << QSizeF(0, 0) << QMarginsF(0, 0, 0, 0) << true;
1494 QTest::newRow(dataTag: "s:5 m:0 reuse") << QSizeF(5, 5) << QMarginsF(0, 0, 0, 0) << true;
1495 QTest::newRow(dataTag: "s:0 m:20 reuse") << QSizeF(0, 0) << QMarginsF(20, 20, 20, 20) << true;
1496 QTest::newRow(dataTag: "s:5 m:20 reuse") << QSizeF(5, 5) << QMarginsF(20, 20, 20, 20) << true;
1497 QTest::newRow(dataTag: "s:0 m:0") << QSizeF(0, 0) << QMarginsF(0, 0, 0, 0) << false;
1498 QTest::newRow(dataTag: "s:5 m:0") << QSizeF(5, 5) << QMarginsF(0, 0, 0, 0) << false;
1499 QTest::newRow(dataTag: "s:0 m:20") << QSizeF(0, 0) << QMarginsF(20, 20, 20, 20) << false;
1500 QTest::newRow(dataTag: "s:5 m:20") << QSizeF(5, 5) << QMarginsF(20, 20, 20, 20) << false;
1501}
1502
1503void tst_QQuickTableView::flickOvershoot()
1504{
1505 // Flick the table completely out and then in again, and see
1506 // that we still contains the expected rows/columns
1507 // Note that TableView always keeps top-left item loaded, even
1508 // when everything is flicked out of view.
1509 QFETCH(QSizeF, spacing);
1510 QFETCH(QMarginsF, margins);
1511 QFETCH(bool, reuseItems);
1512 LOAD_TABLEVIEW("plaintableview.qml");
1513
1514 const int rowCount = 5;
1515 const int columnCount = 5;
1516 const qreal delegateWidth = 100;
1517 const qreal delegateHeight = 50;
1518 const qreal cellWidth = delegateWidth + spacing.width();
1519 const qreal cellHeight = delegateHeight + spacing.height();
1520 const qreal tableWidth = margins.left() + margins.right() + (cellWidth * columnCount) - spacing.width();
1521 const qreal tableHeight = margins.top() + margins.bottom() + (cellHeight * rowCount) - spacing.height();
1522 const int outsideMargin = 10;
1523 auto model = TestModelAsVariant(rowCount, columnCount);
1524
1525 tableView->setModel(model);
1526 tableView->setRowSpacing(spacing.height());
1527 tableView->setColumnSpacing(spacing.width());
1528 tableView->setLeftMargin(margins.left());
1529 tableView->setTopMargin(margins.top());
1530 tableView->setRightMargin(margins.right());
1531 tableView->setBottomMargin(margins.bottom());
1532 tableView->setReuseItems(reuseItems);
1533 tableView->setWidth(tableWidth - margins.right() - cellWidth / 2);
1534 tableView->setHeight(tableHeight - margins.bottom() - cellHeight / 2);
1535
1536 WAIT_UNTIL_POLISHED;
1537
1538 // Flick table out of view left
1539 tableView->setContentX(-tableView->width() - outsideMargin);
1540 tableView->setContentY(0);
1541 tableView->polish();
1542
1543 WAIT_UNTIL_POLISHED;
1544
1545 QCOMPARE(tableViewPrivate->leftColumn(), 0);
1546 QCOMPARE(tableViewPrivate->rightColumn(), 0);
1547 QCOMPARE(tableViewPrivate->topRow(), 0);
1548 QCOMPARE(tableViewPrivate->bottomRow(), rowCount - 1);
1549
1550 // Flick table out of view right
1551 tableView->setContentX(tableWidth + outsideMargin);
1552 tableView->setContentY(0);
1553 tableView->polish();
1554
1555 WAIT_UNTIL_POLISHED;
1556
1557 QCOMPARE(tableViewPrivate->leftColumn(), columnCount - 1);
1558 QCOMPARE(tableViewPrivate->rightColumn(), columnCount - 1);
1559 QCOMPARE(tableViewPrivate->topRow(), 0);
1560 QCOMPARE(tableViewPrivate->bottomRow(), rowCount - 1);
1561
1562 // Flick table out of view on top
1563 tableView->setContentX(0);
1564 tableView->setContentY(-tableView->height() - outsideMargin);
1565 tableView->polish();
1566
1567 WAIT_UNTIL_POLISHED;
1568
1569 QCOMPARE(tableViewPrivate->leftColumn(), 0);
1570 QCOMPARE(tableViewPrivate->rightColumn(), columnCount - 1);
1571 QCOMPARE(tableViewPrivate->topRow(), 0);
1572 QCOMPARE(tableViewPrivate->bottomRow(), 0);
1573
1574 // Flick table out of view at the bottom
1575 tableView->setContentX(0);
1576 tableView->setContentY(tableHeight + outsideMargin);
1577 tableView->polish();
1578
1579 WAIT_UNTIL_POLISHED;
1580
1581 QCOMPARE(tableViewPrivate->leftColumn(), 0);
1582 QCOMPARE(tableViewPrivate->rightColumn(), columnCount - 1);
1583 QCOMPARE(tableViewPrivate->topRow(), rowCount - 1);
1584 QCOMPARE(tableViewPrivate->bottomRow(), rowCount - 1);
1585
1586 // Flick table out of view left and top at the same time
1587 tableView->setContentX(-tableView->width() - outsideMargin);
1588 tableView->setContentY(-tableView->height() - outsideMargin);
1589 tableView->polish();
1590
1591 WAIT_UNTIL_POLISHED;
1592
1593 QCOMPARE(tableViewPrivate->leftColumn(), 0);
1594 QCOMPARE(tableViewPrivate->rightColumn(), 0);
1595 QCOMPARE(tableViewPrivate->topRow(), 0);
1596 QCOMPARE(tableViewPrivate->bottomRow(), 0);
1597
1598 // Flick table back to origo
1599 tableView->setContentX(0);
1600 tableView->setContentY(0);
1601 tableView->polish();
1602
1603 WAIT_UNTIL_POLISHED;
1604
1605 QCOMPARE(tableViewPrivate->leftColumn(), 0);
1606 QCOMPARE(tableViewPrivate->rightColumn(), columnCount - 1);
1607 QCOMPARE(tableViewPrivate->topRow(), 0);
1608 QCOMPARE(tableViewPrivate->bottomRow(), rowCount - 1);
1609
1610 // Flick table out of view right and bottom at the same time
1611 tableView->setContentX(tableWidth + outsideMargin);
1612 tableView->setContentY(tableHeight + outsideMargin);
1613 tableView->polish();
1614
1615 WAIT_UNTIL_POLISHED;
1616
1617 QCOMPARE(tableViewPrivate->leftColumn(), columnCount - 1);
1618 QCOMPARE(tableViewPrivate->rightColumn(), columnCount - 1);
1619 QCOMPARE(tableViewPrivate->topRow(), rowCount - 1);
1620 QCOMPARE(tableViewPrivate->bottomRow(), rowCount - 1);
1621
1622 // Flick table back to origo
1623 tableView->setContentX(0);
1624 tableView->setContentY(0);
1625 tableView->polish();
1626
1627 WAIT_UNTIL_POLISHED;
1628
1629 QCOMPARE(tableViewPrivate->leftColumn(), 0);
1630 QCOMPARE(tableViewPrivate->rightColumn(), columnCount - 1);
1631 QCOMPARE(tableViewPrivate->topRow(), 0);
1632 QCOMPARE(tableViewPrivate->bottomRow(), rowCount - 1);
1633}
1634
1635void tst_QQuickTableView::checkRowColumnCount()
1636{
1637 // If we flick several columns (rows) at the same time, check that we don't
1638 // end up with loading more delegate items into memory than necessary. We
1639 // should free up columns as we go before loading new ones.
1640 LOAD_TABLEVIEW("countingtableview.qml");
1641
1642 const char *maxDelegateCountProp = "maxDelegateCount";
1643 const qreal delegateWidth = 100;
1644 const qreal delegateHeight = 50;
1645 auto model = TestModelAsVariant(100, 100);
1646 const auto &loadedRows = tableViewPrivate->loadedRows;
1647 const auto &loadedColumns = tableViewPrivate->loadedColumns;
1648
1649 tableView->setModel(model);
1650
1651 WAIT_UNTIL_POLISHED;
1652
1653 // We expect that the number of created items after start-up should match
1654 //the size of the visible table, pluss one extra preloaded row and column.
1655 const int qmlCountAfterInit = view->rootObject()->property(name: maxDelegateCountProp).toInt();
1656 const int expectedCount = (loadedColumns.count() + 1) * (loadedRows.count() + 1);
1657 QCOMPARE(qmlCountAfterInit, expectedCount);
1658
1659 // This test will keep track of the maximum number of delegate items TableView
1660 // had to show at any point while flicking (in countingtableview.qml). Because
1661 // of the geometries chosen for TableView and the delegate, only complete columns
1662 // will be shown at start-up.
1663 QVERIFY(loadedRows.count() > loadedColumns.count());
1664 QCOMPARE(tableViewPrivate->loadedTableOuterRect.width(), tableView->width());
1665 QCOMPARE(tableViewPrivate->loadedTableOuterRect.height(), tableView->height());
1666
1667 // Flick half an item to the left+up, to force one extra column and row to load before we
1668 // start. By doing so, we end up showing the maximum number of rows and columns that will
1669 // ever be shown in the view. This will make things less complicated below, when checking
1670 // how many items that end up visible while flicking.
1671 tableView->setContentX(delegateWidth / 2);
1672 tableView->setContentY(delegateHeight / 2);
1673 const int qmlCountAfterFirstFlick = view->rootObject()->property(name: maxDelegateCountProp).toInt();
1674
1675 // Flick a long distance right
1676 tableView->setContentX(tableView->width() * 2);
1677
1678 const int qmlCountAfterLongFlick = view->rootObject()->property(name: maxDelegateCountProp).toInt();
1679 QCOMPARE(qmlCountAfterLongFlick, qmlCountAfterFirstFlick);
1680
1681 // Flick a long distance down
1682 tableView->setContentX(tableView->height() * 2);
1683
1684 const int qmlCountAfterDownFlick = view->rootObject()->property(name: maxDelegateCountProp).toInt();
1685 QCOMPARE(qmlCountAfterDownFlick, qmlCountAfterFirstFlick);
1686
1687 // Flick a long distance left
1688 tableView->setContentX(0);
1689
1690 const int qmlCountAfterLeftFlick = view->rootObject()->property(name: maxDelegateCountProp).toInt();
1691 QCOMPARE(qmlCountAfterLeftFlick, qmlCountAfterFirstFlick);
1692
1693 // Flick a long distance up
1694 tableView->setContentY(0);
1695
1696 const int qmlCountAfterUpFlick = view->rootObject()->property(name: maxDelegateCountProp).toInt();
1697 QCOMPARE(qmlCountAfterUpFlick, qmlCountAfterFirstFlick);
1698}
1699
1700void tst_QQuickTableView::modelSignals()
1701{
1702 LOAD_TABLEVIEW("plaintableview.qml");
1703
1704 TestModel model(1, 1);
1705 tableView->setModel(QVariant::fromValue(value: &model));
1706 WAIT_UNTIL_POLISHED;
1707 QCOMPARE(tableView->rows(), 1);
1708 QCOMPARE(tableView->columns(), 1);
1709
1710 QVERIFY(model.insertRows(0, 1));
1711 WAIT_UNTIL_POLISHED;
1712 QCOMPARE(tableView->rows(), 2);
1713 QCOMPARE(tableView->columns(), 1);
1714
1715 QVERIFY(model.removeRows(1, 1));
1716 WAIT_UNTIL_POLISHED;
1717 QCOMPARE(tableView->rows(), 1);
1718 QCOMPARE(tableView->columns(), 1);
1719
1720 model.insertColumns(column: 1, count: 1);
1721 WAIT_UNTIL_POLISHED;
1722 QCOMPARE(tableView->rows(), 1);
1723 QCOMPARE(tableView->columns(), 2);
1724
1725 model.removeColumns(column: 1, count: 1);
1726 WAIT_UNTIL_POLISHED;
1727 QCOMPARE(tableView->rows(), 1);
1728 QCOMPARE(tableView->columns(), 1);
1729
1730 model.setRowCount(10);
1731 WAIT_UNTIL_POLISHED;
1732 QCOMPARE(tableView->rows(), 10);
1733 QCOMPARE(tableView->columns(), 1);
1734
1735 model.setColumnCount(10);
1736 WAIT_UNTIL_POLISHED;
1737 QCOMPARE(tableView->rows(), 10);
1738 QCOMPARE(tableView->columns(), 10);
1739
1740 model.setRowCount(0);
1741 WAIT_UNTIL_POLISHED;
1742 QCOMPARE(tableView->rows(), 0);
1743 QCOMPARE(tableView->columns(), 10);
1744
1745 model.setColumnCount(1);
1746 WAIT_UNTIL_POLISHED;
1747 QCOMPARE(tableView->rows(), 0);
1748 QCOMPARE(tableView->columns(), 1);
1749
1750 model.setRowCount(10);
1751 WAIT_UNTIL_POLISHED;
1752 QCOMPARE(tableView->rows(), 10);
1753 QCOMPARE(tableView->columns(), 1);
1754
1755 model.setColumnCount(10);
1756 WAIT_UNTIL_POLISHED;
1757 QCOMPARE(tableView->rows(), 10);
1758 QCOMPARE(tableView->columns(), 10);
1759
1760 model.clear();
1761 model.setColumnCount(1);
1762 WAIT_UNTIL_POLISHED;
1763 QCOMPARE(tableView->rows(), 0);
1764 QCOMPARE(tableView->columns(), 1);
1765}
1766
1767void tst_QQuickTableView::checkModelSignalsUpdateLayout()
1768{
1769 // Check that if the model rearranges rows and emit the
1770 // 'layoutChanged' signal, TableView will be updated correctly.
1771 LOAD_TABLEVIEW("plaintableview.qml");
1772
1773 TestModel model(0, 1);
1774 tableView->setModel(QVariant::fromValue(value: &model));
1775 WAIT_UNTIL_POLISHED;
1776
1777 QCOMPARE(tableView->rows(), 0);
1778 QCOMPARE(tableView->columns(), 1);
1779
1780 QString modelRow1Text = QStringLiteral("firstRow");
1781 QString modelRow2Text = QStringLiteral("secondRow");
1782 model.insertRow(arow: 0);
1783 model.insertRow(arow: 0);
1784 model.setModelData(cell: QPoint(0, 0), span: QSize(1, 1), string: modelRow1Text);
1785 model.setModelData(cell: QPoint(0, 1), span: QSize(1, 1), string: modelRow2Text);
1786 WAIT_UNTIL_POLISHED;
1787
1788 QCOMPARE(tableView->rows(), 2);
1789 QCOMPARE(tableView->columns(), 1);
1790
1791 QString delegate1text = tableViewPrivate->loadedTableItem(cell: QPoint(0, 0))->item->property(name: "modelDataBinding").toString();
1792 QString delegate2text = tableViewPrivate->loadedTableItem(cell: QPoint(0, 1))->item->property(name: "modelDataBinding").toString();
1793 QCOMPARE(delegate1text, modelRow1Text);
1794 QCOMPARE(delegate2text, modelRow2Text);
1795
1796 model.swapRows(row1: 0, row2: 1);
1797 WAIT_UNTIL_POLISHED;
1798
1799 delegate1text = tableViewPrivate->loadedTableItem(cell: QPoint(0, 0))->item->property(name: "modelDataBinding").toString();
1800 delegate2text = tableViewPrivate->loadedTableItem(cell: QPoint(0, 1))->item->property(name: "modelDataBinding").toString();
1801 QCOMPARE(delegate1text, modelRow2Text);
1802 QCOMPARE(delegate2text, modelRow1Text);
1803}
1804
1805void tst_QQuickTableView::dataChangedSignal()
1806{
1807 // Check that bindings to the model inside a delegate gets updated
1808 // when the model item they bind to changes.
1809 LOAD_TABLEVIEW("plaintableview.qml");
1810
1811 const QString prefix(QStringLiteral("changed"));
1812
1813 TestModel model(10, 10);
1814 tableView->setModel(QVariant::fromValue(value: &model));
1815
1816 WAIT_UNTIL_POLISHED;
1817
1818 for (auto fxItem : tableViewPrivate->loadedItems) {
1819 const auto item = tableViewPrivate->loadedTableItem(cell: fxItem->cell)->item;
1820 const QString modelDataBindingProperty = item->property(name: kModelDataBindingProp).toString();
1821 QString expectedModelData = QString::number(fxItem->cell.y());
1822 QCOMPARE(modelDataBindingProperty, expectedModelData);
1823 }
1824
1825 // Change one cell in the model
1826 model.setModelData(cell: QPoint(0, 0), span: QSize(1, 1), string: prefix);
1827
1828 for (auto fxItem : tableViewPrivate->loadedItems) {
1829 const QPoint cell = fxItem->cell;
1830 const auto modelIndex = model.index(row: cell.y(), column: cell.x());
1831 QString expectedModelData = model.data(index: modelIndex, role: Qt::DisplayRole).toString();
1832
1833 const auto item = tableViewPrivate->loadedTableItem(cell: fxItem->cell)->item;
1834 const QString modelDataBindingProperty = item->property(name: kModelDataBindingProp).toString();
1835
1836 QCOMPARE(modelDataBindingProperty, expectedModelData);
1837 }
1838
1839 // Change four cells in one go
1840 model.setModelData(cell: QPoint(1, 0), span: QSize(2, 2), string: prefix);
1841
1842 for (auto fxItem : tableViewPrivate->loadedItems) {
1843 const QPoint cell = fxItem->cell;
1844 const auto modelIndex = model.index(row: cell.y(), column: cell.x());
1845 QString expectedModelData = model.data(index: modelIndex, role: Qt::DisplayRole).toString();
1846
1847 const auto item = tableViewPrivate->loadedTableItem(cell: fxItem->cell)->item;
1848 const QString modelDataBindingProperty = item->property(name: kModelDataBindingProp).toString();
1849
1850 QCOMPARE(modelDataBindingProperty, expectedModelData);
1851 }
1852}
1853
1854void tst_QQuickTableView::checkThatPoolIsDrainedWhenReuseIsFalse()
1855{
1856 // Check that the reuse pool is drained
1857 // immediately when setting reuseItems to false.
1858 LOAD_TABLEVIEW("countingtableview.qml");
1859
1860 auto model = TestModelAsVariant(100, 100);
1861 tableView->setModel(model);
1862
1863 WAIT_UNTIL_POLISHED;
1864
1865 // The pool should now contain preloaded items
1866 QVERIFY(tableViewPrivate->tableModel->poolSize() > 0);
1867 tableView->setReuseItems(false);
1868 // The pool should now be empty
1869 QCOMPARE(tableViewPrivate->tableModel->poolSize(), 0);
1870}
1871
1872void tst_QQuickTableView::checkIfDelegatesAreReused_data()
1873{
1874 QTest::addColumn<bool>(name: "reuseItems");
1875
1876 QTest::newRow(dataTag: "reuse = true") << true;
1877 QTest::newRow(dataTag: "reuse = false") << false;
1878}
1879
1880void tst_QQuickTableView::checkIfDelegatesAreReused()
1881{
1882 // Check that we end up reusing delegate items while flicking if
1883 // TableView has reuseItems set to true, but otherwise not.
1884 QFETCH(bool, reuseItems);
1885 LOAD_TABLEVIEW("countingtableview.qml");
1886
1887 const qreal delegateWidth = 100;
1888 const qreal delegateHeight = 50;
1889 const int pageFlickCount = 4;
1890
1891 auto model = TestModelAsVariant(100, 100);
1892 tableView->setModel(model);
1893 tableView->setReuseItems(reuseItems);
1894
1895 WAIT_UNTIL_POLISHED;
1896
1897 // Flick half an item to the side, to force one extra row and column to load before we start.
1898 // This will make things less complicated below, when checking how many times the items
1899 // have been reused (all items will then report the same number).
1900 tableView->setContentX(delegateWidth / 2);
1901 tableView->setContentY(delegateHeight / 2);
1902 QCOMPARE(tableViewPrivate->tableModel->poolSize(), 0);
1903
1904 // Some items have already been pooled and reused after we moved the content view, because
1905 // we preload one extra row and column at start-up. So reset the count-properties back to 0
1906 // before we continue.
1907 for (auto fxItem : tableViewPrivate->loadedItems) {
1908 fxItem->item->setProperty(name: "pooledCount", value: 0);
1909 fxItem->item->setProperty(name: "reusedCount", value: 0);
1910 }
1911
1912 const int visibleColumnCount = tableViewPrivate->loadedColumns.count();
1913 const int visibleRowCount = tableViewPrivate->loadedRows.count();
1914 const int delegateCountAfterInit = view->rootObject()->property(name: kDelegatesCreatedCountProp).toInt();
1915
1916 for (int column = 1; column <= (visibleColumnCount * pageFlickCount); ++column) {
1917 // Flick columns to the left (and add one pixel to ensure the left column is completely out)
1918 tableView->setContentX((delegateWidth * column) + 1);
1919 // Check that the number of delegate items created so far is what we expect.
1920 const int delegatesCreatedCount = view->rootObject()->property(name: kDelegatesCreatedCountProp).toInt();
1921 int expectedCount = delegateCountAfterInit + (reuseItems ? 0 : visibleRowCount * column);
1922 QCOMPARE(delegatesCreatedCount, expectedCount);
1923 }
1924
1925 // Check that each delegate item has been reused as many times
1926 // as we have flicked pages (if reuse is enabled).
1927 for (auto fxItem : tableViewPrivate->loadedItems) {
1928 int pooledCount = fxItem->item->property(name: "pooledCount").toInt();
1929 int reusedCount = fxItem->item->property(name: "reusedCount").toInt();
1930 if (reuseItems) {
1931 QCOMPARE(pooledCount, pageFlickCount);
1932 QCOMPARE(reusedCount, pageFlickCount);
1933 } else {
1934 QCOMPARE(pooledCount, 0);
1935 QCOMPARE(reusedCount, 0);
1936 }
1937 }
1938}
1939
1940void tst_QQuickTableView::checkIfDelegatesAreReusedAsymmetricTableSize()
1941{
1942 // Check that we end up reusing all delegate items while flicking, also if the table contain
1943 // more columns than rows. In that case, if we flick out a whole row, we'll move a lot of
1944 // items into the pool. And if we then start flicking in columns, we'll only reuse a few of
1945 // them for each column. Still, we don't want the pool to release the superfluous items after
1946 // each load, since they are still in circulation and will be needed once we flick in a new
1947 // row at the end of the test.
1948 LOAD_TABLEVIEW("countingtableview.qml");
1949
1950 const int columnCount = 20;
1951 const int rowCount = 2;
1952 const qreal delegateWidth = tableView->width() / columnCount;
1953 const qreal delegateHeight = (tableView->height() / rowCount) + 10;
1954
1955 auto model = TestModelAsVariant(100, 100);
1956 tableView->setModel(model);
1957
1958 // Let the height of each row be much bigger than the width of each column.
1959 view->rootObject()->setProperty(name: "delegateWidth", value: delegateWidth);
1960 view->rootObject()->setProperty(name: "delegateHeight", value: delegateHeight);
1961
1962 WAIT_UNTIL_POLISHED;
1963
1964 auto initialTopLeftItem = tableViewPrivate->loadedTableItem(cell: QPoint(0, 0))->item;
1965 QVERIFY(initialTopLeftItem);
1966 int pooledCount = initialTopLeftItem->property(name: "pooledCount").toInt();
1967 int reusedCount = initialTopLeftItem->property(name: "reusedCount").toInt();
1968 QCOMPARE(pooledCount, 0);
1969 QCOMPARE(reusedCount, 0);
1970
1971 // Flick half an item left+down, to force one extra row and column to load. By doing
1972 // so, we force the maximum number of rows and columns to show before we start the test.
1973 // This will make things less complicated below, when checking how many
1974 // times the items have been reused (all items will then report the same number).
1975 tableView->setContentX(delegateWidth * 0.5);
1976 tableView->setContentY(delegateHeight * 0.5);
1977
1978 // Since we have flicked half a delegate to the left, the number of visible
1979 // columns is now one more than the column count were when we started the test.
1980 const int visibleColumnCount = tableViewPrivate->loadedColumns.count();
1981 QCOMPARE(visibleColumnCount, columnCount + 1);
1982
1983 // We expect no items to have been pooled so far
1984 pooledCount = initialTopLeftItem->property(name: "pooledCount").toInt();
1985 reusedCount = initialTopLeftItem->property(name: "reusedCount").toInt();
1986 QCOMPARE(pooledCount, 0);
1987 QCOMPARE(reusedCount, 0);
1988 QCOMPARE(tableViewPrivate->tableModel->poolSize(), 0);
1989
1990 // Flick one row out of view. This will move one whole row of items into the
1991 // pool without reusing them, since no new row is exposed at the bottom.
1992 tableView->setContentY(delegateHeight + 1);
1993 pooledCount = initialTopLeftItem->property(name: "pooledCount").toInt();
1994 reusedCount = initialTopLeftItem->property(name: "reusedCount").toInt();
1995 QCOMPARE(pooledCount, 1);
1996 QCOMPARE(reusedCount, 0);
1997 QCOMPARE(tableViewPrivate->tableModel->poolSize(), visibleColumnCount);
1998
1999 const int delegateCountAfterInit = view->rootObject()->property(name: kDelegatesCreatedCountProp).toInt();
2000
2001 // Start flicking in a lot of columns, and check that the created count stays the same
2002 for (int column = 1; column <= 10; ++column) {
2003 tableView->setContentX((delegateWidth * column) + 10);
2004 const int delegatesCreatedCount = view->rootObject()->property(name: kDelegatesCreatedCountProp).toInt();
2005 // Since we reuse items while flicking, the created count should stay the same
2006 QCOMPARE(delegatesCreatedCount, delegateCountAfterInit);
2007 // Since we flick out just as many columns as we flick in, the pool size should stay the same
2008 QCOMPARE(tableViewPrivate->tableModel->poolSize(), visibleColumnCount);
2009 }
2010
2011 // Finally, flick one row back into view (but without flicking so far that we push the third
2012 // row out and into the pool). The pool should still contain the exact amount of items that
2013 // we had after we flicked the first row out. And this should be exactly the amount of items
2014 // needed to load the row back again. And this also means that the pool count should then return
2015 // back to 0.
2016 tableView->setContentY(delegateHeight - 1);
2017 const int delegatesCreatedCount = view->rootObject()->property(name: kDelegatesCreatedCountProp).toInt();
2018 QCOMPARE(delegatesCreatedCount, delegateCountAfterInit);
2019 QCOMPARE(tableViewPrivate->tableModel->poolSize(), 0);
2020}
2021
2022void tst_QQuickTableView::checkContextProperties_data()
2023{
2024 QTest::addColumn<QVariant>(name: "model");
2025 QTest::addColumn<bool>(name: "reuseItems");
2026
2027 auto stringList = QStringList();
2028 for (int i = 0; i < 100; ++i)
2029 stringList.append(t: QString::number(i));
2030
2031 QTest::newRow(dataTag: "QAIM, reuse=false") << TestModelAsVariant(100, 100) << false;
2032 QTest::newRow(dataTag: "QAIM, reuse=true") << TestModelAsVariant(100, 100) << true;
2033 QTest::newRow(dataTag: "Number model, reuse=false") << QVariant::fromValue(value: 100) << false;
2034 QTest::newRow(dataTag: "Number model, reuse=true") << QVariant::fromValue(value: 100) << true;
2035 QTest::newRow(dataTag: "QStringList, reuse=false") << QVariant::fromValue(value: stringList) << false;
2036 QTest::newRow(dataTag: "QStringList, reuse=true") << QVariant::fromValue(value: stringList) << true;
2037}
2038
2039void tst_QQuickTableView::checkContextProperties()
2040{
2041 // Check that the context properties of the delegate items
2042 // are what we expect while flicking, with or without item recycling.
2043 QFETCH(QVariant, model);
2044 QFETCH(bool, reuseItems);
2045 LOAD_TABLEVIEW("countingtableview.qml");
2046
2047 const qreal delegateWidth = 100;
2048 const qreal delegateHeight = 50;
2049 const int rowCount = 100;
2050 const int pageFlickCount = 3;
2051
2052 tableView->setModel(model);
2053 tableView->setReuseItems(reuseItems);
2054
2055 WAIT_UNTIL_POLISHED;
2056
2057 const int visibleRowCount = qMin(a: tableView->rows(), b: qCeil(v: tableView->height() / delegateHeight));
2058 const int visibleColumnCount = qMin(a: tableView->columns(), b: qCeil(v: tableView->width() / delegateWidth));
2059
2060 for (int row = 1; row <= (visibleRowCount * pageFlickCount); ++row) {
2061 // Flick rows up
2062 tableView->setContentY((delegateHeight * row) + (delegateHeight / 2));
2063 tableView->polish();
2064
2065 WAIT_UNTIL_POLISHED;
2066
2067 for (int col = 0; col < visibleColumnCount; ++col) {
2068 const auto item = tableViewPrivate->loadedTableItem(cell: QPoint(col, row))->item;
2069 const auto context = qmlContext(item.data());
2070 const int contextIndex = context->contextProperty("index").toInt();
2071 const int contextRow = context->contextProperty("row").toInt();
2072 const int contextColumn = context->contextProperty("column").toInt();
2073 const QString contextModelData = context->contextProperty("modelData").toString();
2074
2075 QCOMPARE(contextIndex, row + (col * rowCount));
2076 QCOMPARE(contextRow, row);
2077 QCOMPARE(contextColumn, col);
2078 QCOMPARE(contextModelData, QStringLiteral("%1").arg(row));
2079 }
2080 }
2081}
2082
2083void tst_QQuickTableView::checkContextPropertiesQQmlListProperyModel_data()
2084{
2085 QTest::addColumn<bool>(name: "reuseItems");
2086
2087 QTest::newRow(dataTag: "reuse=false") << false;
2088 QTest::newRow(dataTag: "reuse=true") << true;
2089}
2090
2091void tst_QQuickTableView::checkContextPropertiesQQmlListProperyModel()
2092{
2093 // Check that the context properties of the delegate items
2094 // are what we expect while flicking, with or without item recycling.
2095 // This test hard-codes the model to be a QQmlListPropertyModel from
2096 // within the qml file.
2097 QFETCH(bool, reuseItems);
2098 LOAD_TABLEVIEW("qqmllistpropertymodel.qml");
2099
2100 const qreal delegateWidth = 100;
2101 const qreal delegateHeight = 50;
2102 const int rowCount = 100;
2103 const int pageFlickCount = 3;
2104
2105 tableView->setReuseItems(reuseItems);
2106 tableView->polish();
2107
2108 WAIT_UNTIL_POLISHED;
2109
2110 const int visibleRowCount = qMin(a: tableView->rows(), b: qCeil(v: tableView->height() / delegateHeight));
2111 const int visibleColumnCount = qMin(a: tableView->columns(), b: qCeil(v: tableView->width() / delegateWidth));
2112
2113 for (int row = 1; row <= (visibleRowCount * pageFlickCount); ++row) {
2114 // Flick rows up
2115 tableView->setContentY((delegateHeight * row) + (delegateHeight / 2));
2116 tableView->polish();
2117
2118 WAIT_UNTIL_POLISHED;
2119
2120 for (int col = 0; col < visibleColumnCount; ++col) {
2121 const auto item = tableViewPrivate->loadedTableItem(cell: QPoint(col, row))->item;
2122 const auto context = qmlContext(item.data());
2123 const int contextIndex = context->contextProperty("index").toInt();
2124 const int contextRow = context->contextProperty("row").toInt();
2125 const int contextColumn = context->contextProperty("column").toInt();
2126 const QObject *contextModelData = qvariant_cast<QObject *>(v: context->contextProperty("modelData"));
2127 const QString modelDataProperty = contextModelData->property(name: "someCustomProperty").toString();
2128
2129 QCOMPARE(contextIndex, row + (col * rowCount));
2130 QCOMPARE(contextRow, row);
2131 QCOMPARE(contextColumn, col);
2132 QCOMPARE(modelDataProperty, QStringLiteral("%1").arg(row));
2133 }
2134 }
2135}
2136
2137void tst_QQuickTableView::checkRowAndColumnChangedButNotIndex()
2138{
2139 // Check that context row and column changes even if the index stays the
2140 // same when the item is reused. This can happen in rare cases if the item
2141 // is first used at e.g (row 1, col 0), but then reused at (row 0, col 1)
2142 // while the model has changed row count in-between.
2143 LOAD_TABLEVIEW("checkrowandcolumnnotchanged.qml");
2144
2145 TestModel model(2, 1);
2146 tableView->setModel(QVariant::fromValue(value: &model));
2147
2148 WAIT_UNTIL_POLISHED;
2149
2150 model.removeRow(arow: 1);
2151 model.insertColumn(acolumn: 1);
2152 tableView->forceLayout();
2153
2154 const auto item = tableViewPrivate->loadedTableItem(cell: QPoint(1, 0))->item;
2155 const auto context = qmlContext(item.data());
2156 const int contextIndex = context->contextProperty("index").toInt();
2157 const int contextRow = context->contextProperty("row").toInt();
2158 const int contextColumn = context->contextProperty("column").toInt();
2159
2160 QCOMPARE(contextIndex, 1);
2161 QCOMPARE(contextRow, 0);
2162 QCOMPARE(contextColumn, 1);
2163}
2164
2165void tst_QQuickTableView::checkThatWeAlwaysEmitChangedUponItemReused()
2166{
2167 // Check that we always emit changes to index when we reuse an item, even
2168 // if it doesn't change. This is needed since the model can have changed
2169 // row or column count while the item was in the pool, which means that
2170 // any data referred to by the index property inside the delegate
2171 // will change too. So we need to refresh any bindings to index.
2172 // QTBUG-79209
2173 LOAD_TABLEVIEW("checkalwaysemit.qml");
2174
2175 TestModel model(1, 1);
2176 tableView->setModel(QVariant::fromValue(value: &model));
2177 model.setModelData(cell: QPoint(0, 0), span: QSize(1, 1), string: "old value");
2178
2179 WAIT_UNTIL_POLISHED;
2180
2181 const auto reuseItem = tableViewPrivate->loadedTableItem(cell: QPoint(0, 0))->item;
2182 const auto context = qmlContext(reuseItem.data());
2183
2184 // Remove the cell/row that has "old value" as model data, and
2185 // add a new one right after. The new cell will have the same
2186 // index, but with no model data assigned.
2187 // This change will not be detected by items in the pool. But since
2188 // we emit indexChanged when the item is reused, it will be updated then.
2189 model.removeRow(arow: 0);
2190 model.insertRow(arow: 0);
2191
2192 WAIT_UNTIL_POLISHED;
2193
2194 QCOMPARE(context->contextProperty("index").toInt(), 0);
2195 QCOMPARE(context->contextProperty("row").toInt(), 0);
2196 QCOMPARE(context->contextProperty("column").toInt(), 0);
2197 QCOMPARE(context->contextProperty("modelDataFromIndex").toString(), "");
2198}
2199
2200void tst_QQuickTableView::checkChangingModelFromDelegate()
2201{
2202 // Check that we don't restart a rebuild of the table
2203 // while we're in the middle of rebuilding it from before
2204 LOAD_TABLEVIEW("changemodelfromdelegate.qml");
2205
2206 // Set addRowFromDelegate. This will trigger the QML code to add a new
2207 // row and call forceLayout(). When TableView instantiates the first
2208 // delegate in the new row, the Component.onCompleted handler will try to
2209 // add a new row. But since we're currently rebuilding, this should be
2210 // scheduled for later.
2211 view->rootObject()->setProperty(name: "addRowFromDelegate", value: true);
2212
2213 // We now expect two rows in the table, one more than initially
2214 QCOMPARE(tableViewPrivate->tableSize.height(), 2);
2215 QCOMPARE(tableViewPrivate->loadedRows.count(), 2);
2216
2217 // And since the QML code tried to add another row as well, we
2218 // expect rebuildScheduled to be true, and a polish event to be pending.
2219 QVERIFY(tableViewPrivate->scheduledRebuildOptions);
2220 QCOMPARE(tableViewPrivate->polishScheduled, true);
2221 WAIT_UNTIL_POLISHED;
2222
2223 // After handling the polish event, we expect also the third row to now be added
2224 QCOMPARE(tableViewPrivate->tableSize.height(), 3);
2225 QCOMPARE(tableViewPrivate->loadedRows.count(), 3);
2226}
2227
2228void tst_QQuickTableView::checkRebuildViewportOnly()
2229{
2230 // Check that we only rebuild from the current top-left cell
2231 // when you add or remove rows and columns. There should be
2232 // no need to do a rebuild from scratch in such cases.
2233 LOAD_TABLEVIEW("countingtableview.qml");
2234
2235 const char *propName = "delegatesCreatedCount";
2236 const qreal delegateWidth = 100;
2237 const qreal delegateHeight = 50;
2238
2239 TestModel model(100, 100);
2240 tableView->setModel(QVariant::fromValue(value: &model));
2241
2242 WAIT_UNTIL_POLISHED;
2243
2244 // Flick to row/column 50, 50
2245 tableView->setContentX(delegateWidth * 50);
2246 tableView->setContentY(delegateHeight * 50);
2247
2248 // Set reuse items to false, just to make it easier to
2249 // check the number of items created during a rebuild.
2250 tableView->setReuseItems(false);
2251 const int itemCountBeforeRebuild = tableViewPrivate->loadedItems.count();
2252
2253 // Since all cells have the same size, we expect that we end up creating
2254 // the same amount of items that were already showing before, even after
2255 // adding or removing rows and columns.
2256 view->rootObject()->setProperty(name: propName, value: 0);
2257 model.insertRow(arow: 51);
2258 WAIT_UNTIL_POLISHED;
2259 int countAfterRebuild = view->rootObject()->property(name: propName).toInt();
2260 QCOMPARE(countAfterRebuild, itemCountBeforeRebuild);
2261
2262 view->rootObject()->setProperty(name: propName, value: 0);
2263 model.removeRow(arow: 51);
2264 WAIT_UNTIL_POLISHED;
2265 countAfterRebuild = view->rootObject()->property(name: propName).toInt();
2266 QCOMPARE(countAfterRebuild, itemCountBeforeRebuild);
2267
2268 view->rootObject()->setProperty(name: propName, value: 0);
2269 model.insertColumn(acolumn: 51);
2270 WAIT_UNTIL_POLISHED;
2271 countAfterRebuild = view->rootObject()->property(name: propName).toInt();
2272 QCOMPARE(countAfterRebuild, itemCountBeforeRebuild);
2273
2274 view->rootObject()->setProperty(name: propName, value: 0);
2275 model.removeColumn(acolumn: 51);
2276 WAIT_UNTIL_POLISHED;
2277 countAfterRebuild = view->rootObject()->property(name: propName).toInt();
2278 QCOMPARE(countAfterRebuild, itemCountBeforeRebuild);
2279}
2280
2281void tst_QQuickTableView::useDelegateChooserWithoutDefault()
2282{
2283 // Check that the application issues a warning (but doesn't e.g
2284 // crash) if the delegate chooser doesn't cover all cells
2285 QTest::ignoreMessage(type: QtWarningMsg, messagePattern: QRegularExpression(".*failed"));
2286 LOAD_TABLEVIEW("usechooserwithoutdefault.qml");
2287 auto model = TestModelAsVariant(2, 1);
2288 tableView->setModel(model);
2289 WAIT_UNTIL_POLISHED;
2290};
2291
2292void tst_QQuickTableView::checkTableviewInsideAsyncLoader()
2293{
2294 // Check that you can put a TableView inside an async Loader, and
2295 // that the delegate items are created before the loader is ready.
2296 LOAD_TABLEVIEW_ASYNC("asyncplain.qml");
2297
2298 // At this point the Loader has finished
2299 QCOMPARE(loader->status(), QQuickLoader::Ready);
2300
2301 // Check that TableView has finished building
2302 QVERIFY(!tableViewPrivate->scheduledRebuildOptions);
2303 QCOMPARE(tableViewPrivate->rebuildState, QQuickTableViewPrivate::RebuildState::Done);
2304
2305 // Check that all expected delegate items have been loaded
2306 const qreal delegateWidth = 100;
2307 const qreal delegateHeight = 50;
2308 int expectedColumns = qCeil(v: tableView->width() / delegateWidth);
2309 int expectedRows = qCeil(v: tableView->height() / delegateHeight);
2310 QCOMPARE(tableViewPrivate->loadedColumns.count(), expectedColumns);
2311 QCOMPARE(tableViewPrivate->loadedRows.count(), expectedRows);
2312
2313 // Check that the loader was still in a loading state while TableView was creating
2314 // delegate items. If we delayed creating delegate items until we got the first
2315 // updatePolish() callback in QQuickTableView, this would not be the case.
2316 auto statusWhenDelegate0_0Completed = qvariant_cast<QQuickLoader::Status>(
2317 v: loader->item()->property(name: "statusWhenDelegate0_0Created"));
2318 auto statusWhenDelegate5_5Completed = qvariant_cast<QQuickLoader::Status>(
2319 v: loader->item()->property(name: "statusWhenDelegate5_5Created"));
2320 QCOMPARE(statusWhenDelegate0_0Completed, QQuickLoader::Loading);
2321 QCOMPARE(statusWhenDelegate5_5Completed, QQuickLoader::Loading);
2322
2323 // Check that TableView had a valid geometry when we started to build. If the build
2324 // was started too early (e.g upon QQuickTableView::componentComplete), width and
2325 // height would still be 0 since the bindings would not have been evaluated yet.
2326 qreal width = loader->item()->property(name: "tableViewWidthWhileBuilding").toReal();
2327 qreal height = loader->item()->property(name: "tableViewHeightWhileBuilding").toReal();
2328 QVERIFY(width > 0);
2329 QVERIFY(height > 0);
2330};
2331
2332#define INT_LIST(indices) QVariant::fromValue(QList<int>() << indices)
2333
2334void tst_QQuickTableView::hideRowsAndColumns_data()
2335{
2336 QTest::addColumn<QVariant>(name: "rowsToHide");
2337 QTest::addColumn<QVariant>(name: "columnsToHide");
2338
2339 const auto emptyList = QVariant::fromValue(value: QList<int>());
2340
2341 // Hide rows
2342 QTest::newRow(dataTag: "first") << INT_LIST(0) << emptyList;
2343 QTest::newRow(dataTag: "middle 1") << INT_LIST(1) << emptyList;
2344 QTest::newRow(dataTag: "middle 3") << INT_LIST(3) << emptyList;
2345 QTest::newRow(dataTag: "last") << INT_LIST(4) << emptyList;
2346
2347 QTest::newRow(dataTag: "subsequent 0,1") << INT_LIST(0 << 1) << emptyList;
2348 QTest::newRow(dataTag: "subsequent 1,2") << INT_LIST(1 << 2) << emptyList;
2349 QTest::newRow(dataTag: "subsequent 3,4") << INT_LIST(3 << 4) << emptyList;
2350
2351 QTest::newRow(dataTag: "all but first") << INT_LIST(1 << 2 << 3 << 4) << emptyList;
2352 QTest::newRow(dataTag: "all but last") << INT_LIST(0 << 1 << 2 << 3) << emptyList;
2353 QTest::newRow(dataTag: "all but middle") << INT_LIST(0 << 1 << 3 << 4) << emptyList;
2354
2355 // Hide columns
2356 QTest::newRow(dataTag: "first") << emptyList << INT_LIST(0);
2357 QTest::newRow(dataTag: "middle 1") << emptyList << INT_LIST(1);
2358 QTest::newRow(dataTag: "middle 3") << emptyList << INT_LIST(3);
2359 QTest::newRow(dataTag: "last") << emptyList << INT_LIST(4);
2360
2361 QTest::newRow(dataTag: "subsequent 0,1") << emptyList << INT_LIST(0 << 1);
2362 QTest::newRow(dataTag: "subsequent 1,2") << emptyList << INT_LIST(1 << 2);
2363 QTest::newRow(dataTag: "subsequent 3,4") << emptyList << INT_LIST(3 << 4);
2364
2365 QTest::newRow(dataTag: "all but first") << emptyList << INT_LIST(1 << 2 << 3 << 4);
2366 QTest::newRow(dataTag: "all but last") << emptyList << INT_LIST(0 << 1 << 2 << 3);
2367 QTest::newRow(dataTag: "all but middle") << emptyList << INT_LIST(0 << 1 << 3 << 4);
2368
2369 // Hide both rows and columns at the same time
2370 QTest::newRow(dataTag: "first") << INT_LIST(0) << INT_LIST(0);
2371 QTest::newRow(dataTag: "middle 1") << INT_LIST(1) << INT_LIST(1);
2372 QTest::newRow(dataTag: "middle 3") << INT_LIST(3) << INT_LIST(3);
2373 QTest::newRow(dataTag: "last") << INT_LIST(4) << INT_LIST(4);
2374
2375 QTest::newRow(dataTag: "subsequent 0,1") << INT_LIST(0 << 1) << INT_LIST(0 << 1);
2376 QTest::newRow(dataTag: "subsequent 1,2") << INT_LIST(1 << 2) << INT_LIST(1 << 2);
2377 QTest::newRow(dataTag: "subsequent 3,4") << INT_LIST(3 << 4) << INT_LIST(3 << 4);
2378
2379 QTest::newRow(dataTag: "all but first") << INT_LIST(1 << 2 << 3 << 4) << INT_LIST(1 << 2 << 3 << 4);
2380 QTest::newRow(dataTag: "all but last") << INT_LIST(0 << 1 << 2 << 3) << INT_LIST(0 << 1 << 2 << 3);
2381 QTest::newRow(dataTag: "all but middle") << INT_LIST(0 << 1 << 3 << 4) << INT_LIST(0 << 1 << 3 << 4);
2382
2383 // Hide all rows and columns
2384 QTest::newRow(dataTag: "all") << INT_LIST(0 << 1 << 2 << 3 << 4) << INT_LIST(0 << 1 << 2 << 3 << 4);
2385}
2386
2387void tst_QQuickTableView::hideRowsAndColumns()
2388{
2389 // Check that you can hide the first row (corner case)
2390 // and that we load the other columns as expected.
2391 QFETCH(QVariant, rowsToHide);
2392 QFETCH(QVariant, columnsToHide);
2393 LOAD_TABLEVIEW("hiderowsandcolumns.qml");
2394
2395 const QList<int> rowsToHideList = qvariant_cast<QList<int>>(v: rowsToHide);
2396 const QList<int> columnsToHideList = qvariant_cast<QList<int>>(v: columnsToHide);
2397 const int modelSize = 5;
2398 auto model = TestModelAsVariant(modelSize, modelSize);
2399 view->rootObject()->setProperty(name: "rowsToHide", value: rowsToHide);
2400 view->rootObject()->setProperty(name: "columnsToHide", value: columnsToHide);
2401
2402 tableView->setModel(model);
2403
2404 WAIT_UNTIL_POLISHED;
2405
2406 const int expectedRowCount = modelSize - rowsToHideList.count();
2407 const int expectedColumnCount = modelSize - columnsToHideList.count();
2408 QCOMPARE(tableViewPrivate->loadedRows.count(), expectedRowCount);
2409 QCOMPARE(tableViewPrivate->loadedColumns.count(), expectedColumnCount);
2410
2411 for (const int row : tableViewPrivate->loadedRows.keys())
2412 QVERIFY(!rowsToHideList.contains(row));
2413
2414 for (const int column : tableViewPrivate->loadedColumns.keys())
2415 QVERIFY(!columnsToHideList.contains(column));
2416}
2417
2418void tst_QQuickTableView::hideAndShowFirstColumn()
2419{
2420 // Check that if we hide the first column, it will move
2421 // the second column to the origin of the viewport. Then check
2422 // that if we show the first column again, it will reappear at
2423 // the origin of the viewport, and as such, pushing the second
2424 // column to the right of it.
2425 LOAD_TABLEVIEW("hiderowsandcolumns.qml");
2426
2427 const int modelSize = 5;
2428 auto model = TestModelAsVariant(modelSize, modelSize);
2429 tableView->setModel(model);
2430
2431 // Start by making the first column hidden
2432 const auto columnsToHideList = QList<int>() << 0;
2433 view->rootObject()->setProperty(name: "columnsToHide", value: QVariant::fromValue(value: columnsToHideList));
2434
2435 WAIT_UNTIL_POLISHED;
2436
2437 const int expectedColumnCount = modelSize - columnsToHideList.count();
2438 QCOMPARE(tableViewPrivate->loadedColumns.count(), expectedColumnCount);
2439 QCOMPARE(tableViewPrivate->leftColumn(), 1);
2440 QCOMPARE(tableView->contentX(), 0);
2441 QCOMPARE(tableViewPrivate->loadedTableOuterRect.x(), 0);
2442
2443 // Make the first column in the model visible again
2444 const auto emptyList = QList<int>();
2445 view->rootObject()->setProperty(name: "columnsToHide", value: QVariant::fromValue(value: emptyList));
2446 tableView->forceLayout();
2447
2448 WAIT_UNTIL_POLISHED;
2449
2450 QCOMPARE(tableViewPrivate->loadedColumns.count(), modelSize);
2451 QCOMPARE(tableViewPrivate->leftColumn(), 0);
2452 QCOMPARE(tableView->contentX(), 0);
2453 QCOMPARE(tableViewPrivate->loadedTableOuterRect.x(), 0);
2454}
2455
2456void tst_QQuickTableView::hideAndShowFirstRow()
2457{
2458 // Check that if we hide the first row, it will move
2459 // the second row to the origin of the viewport. Then check
2460 // that if we show the first row again, it will reappear at
2461 // the origin of the viewport, and as such, pushing the second
2462 // row below it.
2463 LOAD_TABLEVIEW("hiderowsandcolumns.qml");
2464
2465 const int modelSize = 5;
2466 auto model = TestModelAsVariant(modelSize, modelSize);
2467 tableView->setModel(model);
2468
2469 // Start by making the first row hidden
2470 const auto rowsToHideList = QList<int>() << 0;
2471 view->rootObject()->setProperty(name: "rowsToHide", value: QVariant::fromValue(value: rowsToHideList));
2472
2473 WAIT_UNTIL_POLISHED;
2474
2475 const int expectedRowsCount = modelSize - rowsToHideList.count();
2476 QCOMPARE(tableViewPrivate->loadedRows.count(), expectedRowsCount);
2477 QCOMPARE(tableViewPrivate->topRow(), 1);
2478 QCOMPARE(tableView->contentY(), 0);
2479 QCOMPARE(tableViewPrivate->loadedTableOuterRect.y(), 0);
2480
2481 // Make the first row in the model visible again
2482 const auto emptyList = QList<int>();
2483 view->rootObject()->setProperty(name: "rowsToHide", value: QVariant::fromValue(value: emptyList));
2484 tableView->forceLayout();
2485
2486 WAIT_UNTIL_POLISHED;
2487
2488 QCOMPARE(tableViewPrivate->loadedRows.count(), modelSize);
2489 QCOMPARE(tableViewPrivate->topRow(), 0);
2490 QCOMPARE(tableView->contentY(), 0);
2491 QCOMPARE(tableViewPrivate->loadedTableOuterRect.y(), 0);
2492}
2493
2494void tst_QQuickTableView::checkThatRevisionedPropertiesCannotBeUsedInOldImports()
2495{
2496 // Check that if you use a QQmlAdaptorModel together with a Repeater, the
2497 // revisioned context properties 'row' and 'column' are not accessible.
2498 LOAD_TABLEVIEW("checkmodelpropertyrevision.qml");
2499 const int resolvedRow = view->rootObject()->property(name: "resolvedDelegateRow").toInt();
2500 const int resolvedColumn = view->rootObject()->property(name: "resolvedDelegateColumn").toInt();
2501 QCOMPARE(resolvedRow, 42);
2502 QCOMPARE(resolvedColumn, 42);
2503}
2504
2505void tst_QQuickTableView::checkSyncView_rootView_data()
2506{
2507 QTest::addColumn<qreal>(name: "flickToPos");
2508
2509 QTest::newRow(dataTag: "pos:110") << 110.;
2510 QTest::newRow(dataTag: "pos:2010") << 2010.;
2511}
2512
2513void tst_QQuickTableView::checkSyncView_rootView()
2514{
2515 // Check that if you flick on the root tableview (the view that has
2516 // no other view as syncView), all the other tableviews will sync
2517 // their content view position according to their syncDirection flag.
2518 QFETCH(qreal, flickToPos);
2519 LOAD_TABLEVIEW("syncviewsimple.qml");
2520 GET_QML_TABLEVIEW(tableViewH);
2521 GET_QML_TABLEVIEW(tableViewV);
2522 GET_QML_TABLEVIEW(tableViewHV);
2523 QQuickTableView *views[] = {tableViewH, tableViewV, tableViewHV};
2524
2525 auto model = TestModelAsVariant(100, 100);
2526
2527 tableView->setModel(model);
2528 for (auto view : views)
2529 view->setModel(model);
2530
2531 tableView->setContentX(flickToPos);
2532 tableView->setContentY(flickToPos);
2533
2534 WAIT_UNTIL_POLISHED;
2535
2536 // Check that geometry properties are mirrored
2537 QCOMPARE(tableViewH->columnSpacing(), tableView->columnSpacing());
2538 QCOMPARE(tableViewH->rowSpacing(), 0);
2539 QCOMPARE(tableViewH->contentWidth(), tableView->contentWidth());
2540 QCOMPARE(tableViewV->columnSpacing(), 0);
2541 QCOMPARE(tableViewV->rowSpacing(), tableView->rowSpacing());
2542 QCOMPARE(tableViewV->contentHeight(), tableView->contentHeight());
2543
2544 // Check that viewport is in sync after the flick
2545 QCOMPARE(tableView->contentX(), flickToPos);
2546 QCOMPARE(tableView->contentY(), flickToPos);
2547 QCOMPARE(tableViewH->contentX(), tableView->contentX());
2548 QCOMPARE(tableViewH->contentY(), 0);
2549 QCOMPARE(tableViewV->contentX(), 0);
2550 QCOMPARE(tableViewV->contentY(), tableView->contentY());
2551 QCOMPARE(tableViewHV->contentX(), tableView->contentX());
2552 QCOMPARE(tableViewHV->contentY(), tableView->contentY());
2553
2554 // Check that topLeft cell is in sync after the flick
2555 QCOMPARE(tableViewHPrivate->leftColumn(), tableViewPrivate->leftColumn());
2556 QCOMPARE(tableViewHPrivate->rightColumn(), tableViewPrivate->rightColumn());
2557 QCOMPARE(tableViewHPrivate->topRow(), 0);
2558 QCOMPARE(tableViewVPrivate->leftColumn(), 0);
2559 QCOMPARE(tableViewVPrivate->topRow(), tableViewPrivate->topRow());
2560 QCOMPARE(tableViewHVPrivate->leftColumn(), tableViewPrivate->leftColumn());
2561 QCOMPARE(tableViewHVPrivate->topRow(), tableViewPrivate->topRow());
2562
2563 // Check that the geometry of the tables are in sync after the flick
2564 QCOMPARE(tableViewHPrivate->loadedTableOuterRect.left(), tableViewPrivate->loadedTableOuterRect.left());
2565 QCOMPARE(tableViewHPrivate->loadedTableOuterRect.right(), tableViewPrivate->loadedTableOuterRect.right());
2566 QCOMPARE(tableViewHPrivate->loadedTableOuterRect.top(), 0);
2567
2568 QCOMPARE(tableViewVPrivate->loadedTableOuterRect.top(), tableViewPrivate->loadedTableOuterRect.top());
2569 QCOMPARE(tableViewVPrivate->loadedTableOuterRect.bottom(), tableViewPrivate->loadedTableOuterRect.bottom());
2570 QCOMPARE(tableViewVPrivate->loadedTableOuterRect.left(), 0);
2571
2572 QCOMPARE(tableViewHVPrivate->loadedTableOuterRect, tableViewPrivate->loadedTableOuterRect);
2573}
2574
2575void tst_QQuickTableView::checkSyncView_childViews_data()
2576{
2577 QTest::addColumn<int>(name: "viewIndexToFlick");
2578 QTest::addColumn<qreal>(name: "flickToPos");
2579
2580 QTest::newRow(dataTag: "tableViewH, pos:100") << 0 << 100.;
2581 QTest::newRow(dataTag: "tableViewV, pos:100") << 1 << 100.;
2582 QTest::newRow(dataTag: "tableViewHV, pos:100") << 2 << 100.;
2583 QTest::newRow(dataTag: "tableViewH, pos:2000") << 0 << 2000.;
2584 QTest::newRow(dataTag: "tableViewV, pos:2000") << 1 << 2000.;
2585 QTest::newRow(dataTag: "tableViewHV, pos:2000") << 2 << 2000.;
2586}
2587
2588void tst_QQuickTableView::checkSyncView_childViews()
2589{
2590 // Check that if you flick on a tableview that has a syncView, the
2591 // syncView will move to the new position as well, which will also
2592 // recursivly move all other connected child views of the syncView.
2593 QFETCH(int, viewIndexToFlick);
2594 QFETCH(qreal, flickToPos);
2595 LOAD_TABLEVIEW("syncviewsimple.qml");
2596 GET_QML_TABLEVIEW(tableViewH);
2597 GET_QML_TABLEVIEW(tableViewV);
2598 GET_QML_TABLEVIEW(tableViewHV);
2599 QQuickTableView *views[] = {tableViewH, tableViewV, tableViewHV};
2600 QQuickTableView *viewToFlick = views[viewIndexToFlick];
2601 QQuickTableViewPrivate *viewToFlickPrivate = QQuickTableViewPrivate::get(q: viewToFlick);
2602
2603 auto model = TestModelAsVariant(100, 100);
2604
2605 tableView->setModel(model);
2606 for (auto view : views)
2607 view->setModel(model);
2608
2609 viewToFlick->setContentX(flickToPos);
2610 viewToFlick->setContentY(flickToPos);
2611
2612 WAIT_UNTIL_POLISHED;
2613
2614 // The view the user flicks on can always be flicked in both directions
2615 // (unless is has a flickingDirection set, which is not the case here).
2616 QCOMPARE(viewToFlick->contentX(), flickToPos);
2617 QCOMPARE(viewToFlick->contentY(), flickToPos);
2618
2619 // The root view (tableView) will move in sync according
2620 // to the syncDirection of the view being flicked.
2621 if (viewToFlick->syncDirection() & Qt::Horizontal) {
2622 QCOMPARE(tableView->contentX(), flickToPos);
2623 QCOMPARE(tableViewPrivate->leftColumn(), viewToFlickPrivate->leftColumn());
2624 QCOMPARE(tableViewPrivate->rightColumn(), viewToFlickPrivate->rightColumn());
2625 QCOMPARE(tableViewPrivate->loadedTableOuterRect.left(), viewToFlickPrivate->loadedTableOuterRect.left());
2626 QCOMPARE(tableViewPrivate->loadedTableOuterRect.right(), viewToFlickPrivate->loadedTableOuterRect.right());
2627 } else {
2628 QCOMPARE(tableView->contentX(), 0);
2629 QCOMPARE(tableViewPrivate->leftColumn(), 0);
2630 QCOMPARE(tableViewPrivate->loadedTableOuterRect.left(), 0);
2631 }
2632
2633 if (viewToFlick->syncDirection() & Qt::Vertical) {
2634 QCOMPARE(tableView->contentY(), flickToPos);
2635 QCOMPARE(tableViewPrivate->topRow(), viewToFlickPrivate->topRow());
2636 QCOMPARE(tableViewPrivate->bottomRow(), viewToFlickPrivate->bottomRow());
2637 QCOMPARE(tableViewPrivate->loadedTableOuterRect.top(), viewToFlickPrivate->loadedTableOuterRect.top());
2638 QCOMPARE(tableViewPrivate->loadedTableOuterRect.bottom(), viewToFlickPrivate->loadedTableOuterRect.bottom());
2639 } else {
2640 QCOMPARE(tableView->contentY(), 0);
2641 QCOMPARE(tableViewPrivate->topRow(), 0);
2642 QCOMPARE(tableViewPrivate->loadedTableOuterRect.top(), 0);
2643 }
2644
2645 // The other views should continue to stay in sync with
2646 // the root view, unless it was the view being flicked.
2647 if (viewToFlick != tableViewH) {
2648 QCOMPARE(tableViewH->contentX(), tableView->contentX());
2649 QCOMPARE(tableViewH->contentY(), 0);
2650 QCOMPARE(tableViewHPrivate->leftColumn(), tableViewPrivate->leftColumn());
2651 QCOMPARE(tableViewHPrivate->rightColumn(), tableViewPrivate->rightColumn());
2652 QCOMPARE(tableViewHPrivate->loadedTableOuterRect.left(), tableViewPrivate->loadedTableOuterRect.left());
2653 QCOMPARE(tableViewHPrivate->loadedTableOuterRect.right(), tableViewPrivate->loadedTableOuterRect.right());
2654 QCOMPARE(tableViewHPrivate->topRow(), 0);
2655 QCOMPARE(tableViewHPrivate->loadedTableOuterRect.top(), 0);
2656 }
2657
2658 if (viewToFlick != tableViewV) {
2659 QCOMPARE(tableViewV->contentX(), 0);
2660 QCOMPARE(tableViewV->contentY(), tableView->contentY());
2661 QCOMPARE(tableViewVPrivate->topRow(), tableViewPrivate->topRow());
2662 QCOMPARE(tableViewVPrivate->bottomRow(), tableViewPrivate->bottomRow());
2663 QCOMPARE(tableViewVPrivate->loadedTableOuterRect.top(), tableViewPrivate->loadedTableOuterRect.top());
2664 QCOMPARE(tableViewVPrivate->loadedTableOuterRect.bottom(), tableViewPrivate->loadedTableOuterRect.bottom());
2665 QCOMPARE(tableViewVPrivate->leftColumn(), 0);
2666 QCOMPARE(tableViewVPrivate->loadedTableOuterRect.left(), 0);
2667 }
2668
2669 if (viewToFlick != tableViewHV) {
2670 QCOMPARE(tableViewHV->contentX(), tableView->contentX());
2671 QCOMPARE(tableViewHV->contentY(), tableView->contentY());
2672 QCOMPARE(tableViewHVPrivate->leftColumn(), tableViewPrivate->leftColumn());
2673 QCOMPARE(tableViewHVPrivate->rightColumn(), tableViewPrivate->rightColumn());
2674 QCOMPARE(tableViewHVPrivate->topRow(), tableViewPrivate->topRow());
2675 QCOMPARE(tableViewHVPrivate->bottomRow(), tableViewPrivate->bottomRow());
2676 QCOMPARE(tableViewHVPrivate->loadedTableOuterRect, tableViewPrivate->loadedTableOuterRect);
2677 }
2678}
2679
2680void tst_QQuickTableView::checkSyncView_differentSizedModels()
2681{
2682 // Check that you can have two tables in a syncView relation, where
2683 // the sync "child" has fewer rows/columns than the syncView. In that
2684 // case, it will be possible to flick the syncView further out than
2685 // the child have rows/columns to follow. This causes some extra
2686 // challenges for TableView to ensure that they are still kept in
2687 // sync once you later flick the syncView back to a point where both
2688 // tables ends up visible. This test will check this sitiation.
2689 LOAD_TABLEVIEW("syncviewsimple.qml");
2690 GET_QML_TABLEVIEW(tableViewH);
2691 GET_QML_TABLEVIEW(tableViewV);
2692 GET_QML_TABLEVIEW(tableViewHV);
2693
2694 auto tableViewModel = TestModelAsVariant(100, 100);
2695 auto tableViewHModel = TestModelAsVariant(100, 50);
2696 auto tableViewVModel = TestModelAsVariant(50, 100);
2697 auto tableViewHVModel = TestModelAsVariant(5, 5);
2698
2699 tableView->setModel(tableViewModel);
2700 tableViewH->setModel(tableViewHModel);
2701 tableViewV->setModel(tableViewVModel);
2702 tableViewHV->setModel(tableViewHVModel);
2703
2704 WAIT_UNTIL_POLISHED;
2705
2706 // Flick far out, beyond the smaller tables, which will
2707 // also force a rebuild (and as such, cause layout properties
2708 // like average cell width to be temporarily out of sync).
2709 tableView->setContentX(5000);
2710 QVERIFY(tableViewPrivate->scheduledRebuildOptions);
2711
2712 WAIT_UNTIL_POLISHED;
2713
2714 // Check that the smaller tables are now flicked out of view
2715 qreal leftEdge = tableViewPrivate->loadedTableOuterRect.left();
2716 QVERIFY(tableViewHPrivate->loadedTableOuterRect.right() < leftEdge);
2717 QVERIFY(tableViewHVPrivate->loadedTableOuterRect.right() < leftEdge);
2718
2719 // Flick slowly back so that we don't trigger a rebuild (since
2720 // we want to check that we stay in sync also when not rebuilding).
2721 while (tableView->contentX() > 200) {
2722 tableView->setContentX(tableView->contentX() - 200);
2723 QVERIFY(!tableViewPrivate->rebuildOptions);
2724 QVERIFY(!tableViewPrivate->polishScheduled);
2725 }
2726
2727 leftEdge = tableViewPrivate->loadedTableOuterRect.left();
2728 const int leftColumn = tableViewPrivate->leftColumn();
2729 QCOMPARE(tableViewHPrivate->loadedTableOuterRect.left(), leftEdge);
2730 QCOMPARE(tableViewHPrivate->leftColumn(), leftColumn);
2731
2732 // Because the tableView was fast flicked and then slowly flicked back, the
2733 // left column is now 49, which is actually far too high, since we're almost
2734 // at the beginning of the content view. But this "miscalculation" is expected
2735 // when the column widths are increasing for each column, like they do in this
2736 // test. In that case, the algorithm that predicts where each column should end
2737 // up gets slightly confused. Anyway, check that tableViewHV, that has only
2738 // 5 columns, is not showing any columns, since it should always stay in sync with
2739 // syncView regardless of the content view position.
2740 QVERIFY(tableViewHVPrivate->loadedColumns.isEmpty());
2741}
2742
2743void tst_QQuickTableView::checkSyncView_connect_late_data()
2744{
2745 QTest::addColumn<qreal>(name: "flickToPos");
2746
2747 QTest::newRow(dataTag: "pos:110") << 110.;
2748 QTest::newRow(dataTag: "pos:2010") << 2010.;
2749}
2750
2751void tst_QQuickTableView::checkSyncView_connect_late()
2752{
2753 // Check that if you assign a syncView to a TableView late, and
2754 // after the views have been flicked around, they will still end up in sync.
2755 QFETCH(qreal, flickToPos);
2756 LOAD_TABLEVIEW("syncviewsimple.qml");
2757 GET_QML_TABLEVIEW(tableViewH);
2758 GET_QML_TABLEVIEW(tableViewV);
2759 GET_QML_TABLEVIEW(tableViewHV);
2760 QQuickTableView *views[] = {tableViewH, tableViewV, tableViewHV};
2761
2762 auto model = TestModelAsVariant(100, 100);
2763
2764 tableView->setModel(model);
2765
2766 // Start with no syncView connections
2767 for (auto view : views) {
2768 view->setSyncView(nullptr);
2769 view->setModel(model);
2770 }
2771
2772 WAIT_UNTIL_POLISHED;
2773
2774 tableView->setContentX(flickToPos);
2775 tableView->setContentY(flickToPos);
2776
2777 WAIT_UNTIL_POLISHED;
2778
2779 // Check that viewport is not in sync after the flick
2780 QCOMPARE(tableView->contentX(), flickToPos);
2781 QCOMPARE(tableView->contentY(), flickToPos);
2782 QCOMPARE(tableViewH->contentX(), 0);
2783 QCOMPARE(tableViewH->contentY(), 0);
2784 QCOMPARE(tableViewV->contentX(), 0);
2785 QCOMPARE(tableViewV->contentY(), 0);
2786 QCOMPARE(tableViewHV->contentX(), 0);
2787 QCOMPARE(tableViewHV->contentY(), 0);
2788
2789 // Assign the syncView late. This should make
2790 // all the views end up in sync.
2791 for (auto view : views) {
2792 view->setSyncView(tableView);
2793 WAIT_UNTIL_POLISHED_ARG(view);
2794 }
2795
2796 // Check that geometry properties are mirrored
2797 QCOMPARE(tableViewH->columnSpacing(), tableView->columnSpacing());
2798 QCOMPARE(tableViewH->rowSpacing(), 0);
2799 QCOMPARE(tableViewH->contentWidth(), tableView->contentWidth());
2800 QCOMPARE(tableViewV->columnSpacing(), 0);
2801 QCOMPARE(tableViewV->rowSpacing(), tableView->rowSpacing());
2802 QCOMPARE(tableViewV->contentHeight(), tableView->contentHeight());
2803
2804 // Check that viewport is in sync after the flick
2805 QCOMPARE(tableView->contentX(), flickToPos);
2806 QCOMPARE(tableView->contentY(), flickToPos);
2807 QCOMPARE(tableViewH->contentX(), tableView->contentX());
2808 QCOMPARE(tableViewH->contentY(), 0);
2809 QCOMPARE(tableViewV->contentX(), 0);
2810 QCOMPARE(tableViewV->contentY(), tableView->contentY());
2811 QCOMPARE(tableViewHV->contentX(), tableView->contentX());
2812 QCOMPARE(tableViewHV->contentY(), tableView->contentY());
2813
2814 // Check that topLeft cell is in sync after the flick
2815 QCOMPARE(tableViewHPrivate->leftColumn(), tableViewPrivate->leftColumn());
2816 QCOMPARE(tableViewHPrivate->rightColumn(), tableViewPrivate->rightColumn());
2817 QCOMPARE(tableViewHPrivate->topRow(), 0);
2818 QCOMPARE(tableViewVPrivate->leftColumn(), 0);
2819 QCOMPARE(tableViewVPrivate->topRow(), tableViewPrivate->topRow());
2820 QCOMPARE(tableViewHVPrivate->leftColumn(), tableViewPrivate->leftColumn());
2821 QCOMPARE(tableViewHVPrivate->topRow(), tableViewPrivate->topRow());
2822
2823 // Check that the geometry of the tables are in sync after the flick
2824 QCOMPARE(tableViewHPrivate->loadedTableOuterRect.left(), tableViewPrivate->loadedTableOuterRect.left());
2825 QCOMPARE(tableViewHPrivate->loadedTableOuterRect.right(), tableViewPrivate->loadedTableOuterRect.right());
2826 QCOMPARE(tableViewHPrivate->loadedTableOuterRect.top(), 0);
2827
2828 QCOMPARE(tableViewVPrivate->loadedTableOuterRect.top(), tableViewPrivate->loadedTableOuterRect.top());
2829 QCOMPARE(tableViewVPrivate->loadedTableOuterRect.bottom(), tableViewPrivate->loadedTableOuterRect.bottom());
2830 QCOMPARE(tableViewVPrivate->loadedTableOuterRect.left(), 0);
2831
2832 QCOMPARE(tableViewHVPrivate->loadedTableOuterRect, tableViewPrivate->loadedTableOuterRect);
2833}
2834
2835void tst_QQuickTableView::checkSyncView_pageFlicking()
2836{
2837 // Check that we rebuild the syncView (instead of refilling
2838 // edges), if the sync child moves more than a page (the size of TableView).
2839 // The point is that it shouldn't matter if you fast-flick the
2840 // sync view itself, or a sync child. Either way, the sync view
2841 // needs to rebuild. This, in turn, will eventually rebuild the
2842 // sync children as well when they sync up later.
2843 LOAD_TABLEVIEW("syncviewsimple.qml");
2844 GET_QML_TABLEVIEW(tableViewH);
2845 GET_QML_TABLEVIEW(tableViewV);
2846 GET_QML_TABLEVIEW(tableViewHV);
2847 QQuickTableView *views[] = {tableViewH, tableViewV, tableViewHV};
2848
2849 auto model = TestModelAsVariant(100, 100);
2850
2851 tableView->setModel(model);
2852
2853 WAIT_UNTIL_POLISHED;
2854
2855 // Move the viewport more than a "page"
2856 tableViewHV->setContentX(tableViewHV->width() * 2);
2857
2858 QVERIFY(tableViewPrivate->scheduledRebuildOptions & QQuickTableViewPrivate::RebuildOption::ViewportOnly);
2859 QVERIFY(tableViewPrivate->scheduledRebuildOptions & QQuickTableViewPrivate::RebuildOption::CalculateNewTopLeftColumn);
2860 QVERIFY(!(tableViewPrivate->scheduledRebuildOptions & QQuickTableViewPrivate::RebuildOption::CalculateNewTopLeftRow));
2861
2862 WAIT_UNTIL_POLISHED;
2863
2864 tableViewHV->setContentY(tableViewHV->height() * 2);
2865
2866 QVERIFY(tableViewPrivate->scheduledRebuildOptions & QQuickTableViewPrivate::RebuildOption::ViewportOnly);
2867 QVERIFY(!(tableViewPrivate->scheduledRebuildOptions & QQuickTableViewPrivate::RebuildOption::CalculateNewTopLeftColumn));
2868 QVERIFY(tableViewPrivate->scheduledRebuildOptions & QQuickTableViewPrivate::RebuildOption::CalculateNewTopLeftRow);
2869}
2870
2871void tst_QQuickTableView::checkSyncView_emptyModel()
2872{
2873 // When a tableview has a syncview with an empty model then it should still be
2874 // showing the tableview without depending on the syncview. This is particularly
2875 // important for headerviews for example
2876 LOAD_TABLEVIEW("syncviewsimple.qml");
2877 GET_QML_TABLEVIEW(tableViewH);
2878 GET_QML_TABLEVIEW(tableViewV);
2879 GET_QML_TABLEVIEW(tableViewHV);
2880 QQuickTableView *views[] = {tableViewH, tableViewV, tableViewHV};
2881
2882 auto model = TestModelAsVariant(100, 100);
2883
2884 for (auto view : views)
2885 view->setModel(model);
2886
2887 WAIT_UNTIL_POLISHED_ARG(tableViewHV);
2888
2889 // Check that geometry properties are mirrored
2890 QCOMPARE(tableViewH->columnSpacing(), tableView->columnSpacing());
2891 QCOMPARE(tableViewH->rowSpacing(), 0);
2892 QCOMPARE(tableViewH->contentWidth(), tableView->contentWidth());
2893 QVERIFY(tableViewH->contentHeight() > 0);
2894 QCOMPARE(tableViewV->columnSpacing(), 0);
2895 QCOMPARE(tableViewV->rowSpacing(), tableView->rowSpacing());
2896 QCOMPARE(tableViewV->contentHeight(), tableView->contentHeight());
2897 QVERIFY(tableViewV->contentWidth() > 0);
2898
2899 QCOMPARE(tableViewH->contentX(), tableView->contentX());
2900 QCOMPARE(tableViewH->contentY(), 0);
2901 QCOMPARE(tableViewV->contentX(), 0);
2902 QCOMPARE(tableViewV->contentY(), tableView->contentY());
2903 QCOMPARE(tableViewHV->contentX(), tableView->contentX());
2904 QCOMPARE(tableViewHV->contentY(), tableView->contentY());
2905
2906 QCOMPARE(tableViewHPrivate->loadedTableOuterRect.left(), tableViewPrivate->loadedTableOuterRect.left());
2907 QCOMPARE(tableViewHPrivate->loadedTableOuterRect.top(), 0);
2908
2909 QCOMPARE(tableViewVPrivate->loadedTableOuterRect.top(), tableViewPrivate->loadedTableOuterRect.top());
2910 QCOMPARE(tableViewVPrivate->loadedTableOuterRect.left(), 0);
2911}
2912
2913void tst_QQuickTableView::checkThatFetchMoreIsCalledWhenScrolledToTheEndOfTable()
2914{
2915 LOAD_TABLEVIEW("plaintableview.qml");
2916
2917 auto model = TestModelAsVariant(5, 5, true);
2918 tableView->setModel(model);
2919 WAIT_UNTIL_POLISHED;
2920
2921 QCOMPARE(tableView->rows(), 5);
2922 QCOMPARE(tableView->columns(), 5);
2923
2924 // Flick table out of view on top
2925 tableView->setContentX(0);
2926 tableView->setContentY(-tableView->height() - 10);
2927 tableView->polish();
2928 WAIT_UNTIL_POLISHED;
2929
2930 QCOMPARE(tableView->rows(), 6);
2931 QCOMPARE(tableView->columns(), 5);
2932}
2933
2934void tst_QQuickTableView::delegateWithRequiredProperties()
2935{
2936 constexpr static int PositionRole = Qt::UserRole+1;
2937 struct MyTable : QAbstractTableModel {
2938
2939
2940 using QAbstractTableModel::QAbstractTableModel;
2941
2942 int rowCount(const QModelIndex& = QModelIndex()) const override {
2943 return 3;
2944 }
2945
2946 int columnCount(const QModelIndex& = QModelIndex()) const override {
2947 return 3;
2948 }
2949
2950 QVariant data(const QModelIndex &index, int = Qt::DisplayRole) const override {
2951 return QVariant::fromValue(value: QString::asprintf(format: "R%d:C%d", index.row(), index.column()));
2952 }
2953
2954 QHash<int, QByteArray> roleNames() const override {
2955 return QHash<int, QByteArray> { {PositionRole, "position"} };
2956 }
2957 };
2958
2959 auto model = QVariant::fromValue(value: QSharedPointer<MyTable>(new MyTable));
2960 {
2961 QTest::ignoreMessage(type: QtMsgType::QtInfoMsg, message: "success");
2962 LOAD_TABLEVIEW("delegateWithRequired.qml");
2963 QVERIFY(tableView);
2964 tableView->setModel(model);
2965 WAIT_UNTIL_POLISHED;
2966 QVERIFY(view->errors().empty());
2967 }
2968 {
2969 QTest::ignoreMessage(type: QtMsgType::QtWarningMsg, messagePattern: QRegularExpression(R"|(TableView: failed loading index: \d)|"));
2970 LOAD_TABLEVIEW("delegatewithRequiredUnset.qml");
2971 QVERIFY(tableView);
2972 tableView->setModel(model);
2973 WAIT_UNTIL_POLISHED;
2974 QTRY_VERIFY(view->errors().empty());
2975 }
2976}
2977
2978void tst_QQuickTableView::replaceModel()
2979{
2980 LOAD_TABLEVIEW("replaceModelTableView.qml");
2981
2982 const auto objectModel = view->rootObject()->property(name: "objectModel");
2983 const auto listModel = view->rootObject()->property(name: "listModel");
2984 const auto delegateModel = view->rootObject()->property(name: "delegateModel");
2985
2986 tableView->setModel(listModel);
2987 QTRY_COMPARE(tableView->rows(), 2);
2988 tableView->setModel(objectModel);
2989 QTRY_COMPARE(tableView->rows(), 3);
2990 tableView->setModel(delegateModel);
2991 QTRY_COMPARE(tableView->rows(), 2);
2992 tableView->setModel(listModel);
2993 QTRY_COMPARE(tableView->rows(), 2);
2994 tableView->setModel(QVariant());
2995 QTRY_COMPARE(tableView->rows(), 0);
2996 QCOMPARE(tableView->contentWidth(), 0);
2997 QCOMPARE(tableView->contentHeight(), 0);
2998}
2999
3000void tst_QQuickTableView::checkContentSize_data()
3001{
3002 QTest::addColumn<int>(name: "rowCount");
3003 QTest::addColumn<int>(name: "colCount");
3004
3005 QTest::newRow(dataTag: "4x4") << 4 << 4;
3006 QTest::newRow(dataTag: "100x100") << 100 << 100;
3007 QTest::newRow(dataTag: "0x0") << 0 << 0;
3008}
3009
3010void tst_QQuickTableView::checkContentSize()
3011{
3012 QFETCH(int, rowCount);
3013 QFETCH(int, colCount);
3014
3015 // Check that the content size is initially correct, and that
3016 // it updates when we change e.g the model or spacing (QTBUG-87680)
3017 LOAD_TABLEVIEW("plaintableview.qml");
3018
3019 TestModel model(rowCount, colCount);
3020 tableView->setModel(QVariant::fromValue(value: &model));
3021 tableView->setRowSpacing(1);
3022 tableView->setColumnSpacing(2);
3023
3024 WAIT_UNTIL_POLISHED;
3025
3026 const qreal delegateWidth = 100;
3027 const qreal delegateHeight = 50;
3028 qreal colSpacing = tableView->columnSpacing();
3029 qreal rowSpacing = tableView->rowSpacing();
3030
3031 // Check that content size has the exepected initial values
3032 QCOMPARE(tableView->contentWidth(), colCount == 0 ? 0 : (colCount * (delegateWidth + colSpacing)) - colSpacing);
3033 QCOMPARE(tableView->contentHeight(), rowCount == 0 ? 0 : (rowCount * (delegateHeight + rowSpacing)) - rowSpacing);
3034
3035 // Set no spacing
3036 rowSpacing = 0;
3037 colSpacing = 0;
3038 tableView->setRowSpacing(rowSpacing);
3039 tableView->setColumnSpacing(colSpacing);
3040 WAIT_UNTIL_POLISHED;
3041 QCOMPARE(tableView->contentWidth(), colCount * delegateWidth);
3042 QCOMPARE(tableView->contentHeight(), rowCount * delegateHeight);
3043
3044 // Set typical spacing values
3045 rowSpacing = 2;
3046 colSpacing = 3;
3047 tableView->setRowSpacing(rowSpacing);
3048 tableView->setColumnSpacing(colSpacing);
3049 WAIT_UNTIL_POLISHED;
3050 QCOMPARE(tableView->contentWidth(), colCount == 0 ? 0 : (colCount * (delegateWidth + colSpacing)) - colSpacing);
3051 QCOMPARE(tableView->contentHeight(), rowCount == 0 ? 0 : (rowCount * (delegateHeight + rowSpacing)) - rowSpacing);
3052
3053 // Add a row and a column
3054 model.insertRow(arow: 0);
3055 model.insertColumn(acolumn: 0);
3056 rowCount = model.rowCount();
3057 colCount = model.columnCount();
3058 WAIT_UNTIL_POLISHED;
3059 QCOMPARE(tableView->contentWidth(), (colCount * (delegateWidth + colSpacing)) - colSpacing);
3060 QCOMPARE(tableView->contentHeight(), (rowCount * (delegateHeight + rowSpacing)) - rowSpacing);
3061
3062 // Remove a row (this should also make affect contentWidth if rowCount becomes 0)
3063 model.removeRow(arow: 0);
3064 rowCount = model.rowCount();
3065 WAIT_UNTIL_POLISHED;
3066 QCOMPARE(tableView->contentWidth(), rowCount == 0 ? 0 : (colCount * (delegateWidth + colSpacing)) - colSpacing);
3067 QCOMPARE(tableView->contentHeight(), rowCount == 0 ? 0 : (rowCount * (delegateHeight + rowSpacing)) - rowSpacing);
3068}
3069
3070QTEST_MAIN(tst_QQuickTableView)
3071
3072#include "tst_qquicktableview.moc"
3073

source code of qtdeclarative/tests/auto/quick/qquicktableview/tst_qquicktableview.cpp