aboutsummaryrefslogtreecommitdiffstats
path: root/tests/auto/quick/qquicktableview/tst_qquicktableview.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'tests/auto/quick/qquicktableview/tst_qquicktableview.cpp')
-rw-r--r--tests/auto/quick/qquicktableview/tst_qquicktableview.cpp4666
1 files changed, 4474 insertions, 192 deletions
diff --git a/tests/auto/quick/qquicktableview/tst_qquicktableview.cpp b/tests/auto/quick/qquicktableview/tst_qquicktableview.cpp
index 2bd7c38a11..bb425b5a6f 100644
--- a/tests/auto/quick/qquicktableview/tst_qquicktableview.cpp
+++ b/tests/auto/quick/qquicktableview/tst_qquicktableview.cpp
@@ -1,30 +1,5 @@
-/****************************************************************************
-**
-** Copyright (C) 2018 The Qt Company Ltd.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the test suite of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:GPL-EXCEPT$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and The Qt Company. For licensing terms
-** and conditions see https://www.qt.io/terms-conditions. For further
-** information use the contact form at https://www.qt.io/contact-us.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 3 as published by the Free Software
-** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
+// Copyright (C) 2018 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include <QtTest/QtTest>
#include <QtQuickTest/quicktest.h>
@@ -33,22 +8,25 @@
#include <QtQuick/private/qquicktableview_p.h>
#include <QtQuick/private/qquicktableview_p_p.h>
#include <QtQuick/private/qquickloader_p.h>
+#include <QtQuick/private/qquickdraghandler_p.h>
+#include <QtQuick/private/qquicktextinput_p.h>
#include <QtQml/qqmlengine.h>
#include <QtQml/qqmlcontext.h>
#include <QtQml/qqmlexpression.h>
#include <QtQml/qqmlincubator.h>
+#include <QtQml/qqmlcomponent.h>
#include <QtQmlModels/private/qqmlobjectmodel_p.h>
#include <QtQmlModels/private/qqmllistmodel_p.h>
#include "testmodel.h"
-#include "../../shared/util.h"
-#include "../shared/viewtestutil.h"
-#include "../shared/visualtestutil.h"
+#include <QtQuickTestUtils/private/qmlutils_p.h>
+#include <QtQuickTestUtils/private/viewtestutils_p.h>
+#include <QtQuickTestUtils/private/visualtestutils_p.h>
-using namespace QQuickViewTestUtil;
-using namespace QQuickVisualTestUtil;
+using namespace QQuickViewTestUtils;
+using namespace QQuickVisualTestUtils;
static const char* kDelegateObjectName = "tableViewDelegate";
static const char *kDelegatesCreatedCountProp = "delegatesCreatedCount";
@@ -65,6 +43,7 @@ Q_DECLARE_METATYPE(QMarginsF);
#define LOAD_TABLEVIEW(fileName) \
view->setSource(testFileUrl(fileName)); \
view->show(); \
+ view->requestActivate(); \
QVERIFY(QTest::qWaitForWindowActive(view)); \
GET_QML_TABLEVIEW(tableView)
@@ -73,14 +52,14 @@ Q_DECLARE_METATYPE(QMarginsF);
view->show(); \
QVERIFY(QTest::qWaitForWindowActive(view)); \
auto loader = view->rootObject()->property("loader").value<QQuickLoader *>(); \
- loader->setSource(QUrl::fromLocalFile("data/" fileName)); \
+ loader->setSourceWithoutResolve(testFileUrl(fileName)); \
QTRY_VERIFY(loader->item()); \
QCOMPARE(loader->status(), QQuickLoader::Status::Ready); \
GET_QML_TABLEVIEW(tableView)
#define WAIT_UNTIL_POLISHED_ARG(item) \
QVERIFY(QQuickTest::qIsPolishScheduled(item)); \
- QVERIFY(QQuickTest::qWaitForItemPolished(item))
+ QVERIFY(QQuickTest::qWaitForPolish(item))
#define WAIT_UNTIL_POLISHED WAIT_UNTIL_POLISHED_ARG(tableView)
class tst_QQuickTableView : public QQmlDataTest
@@ -107,21 +86,29 @@ private slots:
void checkPreload();
void checkZeroSizedDelegate();
void checkImplicitSizeDelegate();
+ void checkZeroSizedTableView();
+ void checkZeroSizedViewPort();
void checkColumnWidthWithoutProvider();
+ void checkColumnWidthAndRowHeightFunctions();
void checkDelegateWithAnchors();
void checkColumnWidthProvider();
void checkColumnWidthProviderInvalidReturnValues();
void checkColumnWidthProviderNegativeReturnValue();
void checkColumnWidthProviderNotCallable();
+ void checkColumnWidthBoundToViewWidth();
void checkRowHeightWithoutProvider();
void checkRowHeightProvider();
void checkRowHeightProviderInvalidReturnValues();
void checkRowHeightProviderNegativeReturnValue();
void checkRowHeightProviderNotCallable();
+ void isColumnLoadedAndIsRowLoaded();
void checkForceLayoutFunction();
void checkForceLayoutEndUpDoingALayout();
- void checkForceLayoutDuringModelChange();
+ void checkForceLayoutInbetweenAddingRowsToModel();
+ void checkForceLayoutWhenAllItemsAreHidden();
+ void checkLayoutChangedSignal();
void checkContentWidthAndHeight();
+ void checkContentWidthAndHeightForSmallTables();
void checkPageFlicking();
void checkExplicitContentWidthAndHeight();
void checkExtents_origin();
@@ -167,16 +154,21 @@ private slots:
void checkTableviewInsideAsyncLoader();
void hideRowsAndColumns_data();
void hideRowsAndColumns();
+ void hideAndShowFirstColumn();
+ void hideAndShowFirstRow();
void checkThatRevisionedPropertiesCannotBeUsedInOldImports();
void checkSyncView_rootView_data();
void checkSyncView_rootView();
void checkSyncView_childViews_data();
void checkSyncView_childViews();
void checkSyncView_differentSizedModels();
+ void checkSyncView_differentGeometry();
void checkSyncView_connect_late_data();
void checkSyncView_connect_late();
void checkSyncView_pageFlicking();
void checkSyncView_emptyModel();
+ void checkSyncView_topLeftChanged();
+ void checkSyncView_unloadHeader();
void delegateWithRequiredProperties();
void checkThatFetchMoreIsCalledWhenScrolledToTheEndOfTable();
void replaceModel();
@@ -186,15 +178,111 @@ private slots:
void positionViewAtRow();
void positionViewAtColumn_data();
void positionViewAtColumn();
+ void positionViewAtRowClamped_data();
+ void positionViewAtRowClamped();
+ void positionViewAtColumnClamped_data();
+ void positionViewAtColumnClamped();
+ void positionViewAtCellWithAnimation();
+ void positionViewAtCell_VisibleAndContain_data();
+ void positionViewAtCell_VisibleAndContain();
+ void positionViewAtCell_VisibleAndContain_SubRect_data();
+ void positionViewAtCell_VisibleAndContain_SubRect();
+ void positionViewAtCellForLargeCells_data();
+ void positionViewAtCellForLargeCells();
+ void positionViewAtCellForLargeCellsUsingSubrect();
+ void positionViewAtLastRow_data();
+ void positionViewAtLastRow();
+ void positionViewAtLastColumn_data();
+ void positionViewAtLastColumn();
void itemAtCell_data();
void itemAtCell();
void leftRightTopBottomProperties_data();
void leftRightTopBottomProperties();
+ void leftRightTopBottomUpdatedBeforeSignalEmission();
void checkContentSize_data();
void checkContentSize();
+ void checkSelectionModelWithRequiredSelectedProperty_data();
+ void checkSelectionModelWithRequiredSelectedProperty();
+ void checkSelectionModelWithUnrequiredSelectedProperty();
+ void removeAndAddSelectionModel();
+ void warnOnWrongModelInSelectionModel();
+ void selectionBehaviorCells_data();
+ void selectionBehaviorCells();
+ void selectionBehaviorRows();
+ void selectionBehaviorColumns();
+ void selectionBehaviorDisabled();
+ void testSelectableStartPosEndPosOutsideView();
+ void testSelectableScrollTowardsPos();
+ void setCurrentIndexFromSelectionModel();
+ void clearSelectionOnTap_data();
+ void clearSelectionOnTap();
+ void moveCurrentIndexUsingArrowKeys();
+ void moveCurrentIndexUsingHomeAndEndKeys();
+ void moveCurrentIndexUsingPageUpDownKeys();
+ void moveCurrentIndexUsingTabKey_data();
+ void moveCurrentIndexUsingTabKey();
+ void respectActiveFocusOnTabDisabled();
+ void setCurrentIndexOnFirstKeyPress_data();
+ void setCurrentIndexOnFirstKeyPress();
+ void setCurrentIndexFromMouse();
+ void showMarginsWhenNavigatingToEnd();
+ void disableKeyNavigation();
+ void disablePointerNavigation();
+ void selectUsingArrowKeys();
+ void selectUsingHomeAndEndKeys();
+ void selectUsingPageUpDownKeys();
+ void testDeprecatedApi();
+ void alternatingRows();
+ void boundDelegateComponent();
+ void setColumnWidth_data();
+ void setColumnWidth();
+ void setColumnWidthWhenProviderIsSet();
+ void setColumnWidthForInvalidColumn();
+ void setColumnWidthWhenUsingSyncView();
+ void resetColumnWidth();
+ void clearColumnWidths();
+ void setRowHeight_data();
+ void setRowHeight();
+ void setRowHeightWhenProviderIsSet();
+ void setRowHeightForInvalidRow();
+ void setRowHeightWhenUsingSyncView();
+ void resetRowHeight();
+ void clearRowHeights();
+ void deletedDelegate();
+ void columnResizing_data();
+ void columnResizing();
+ void tableViewInteractive();
+ void rowResizing_data();
+ void rowResizing();
+ void rowAndColumnResizing_data();
+ void rowAndColumnResizing();
+ void columnResizingDisabled();
+ void rowResizingDisabled();
+ void dragFromCellCenter();
+ void tapOnResizeArea_data();
+ void tapOnResizeArea();
+ void editUsingEditTriggers_data();
+ void editUsingEditTriggers();
+ void editUsingTab();
+ void editDelegateComboBox();
+ void editOnNonEditableCell_data();
+ void editOnNonEditableCell();
+ void noEditDelegate_data();
+ void noEditDelegate();
+ void editAndCloseEditor();
+ void editWarning_noEditDelegate();
+ void editWarning_invalidIndex();
+ void editWarning_nonEditableModelItem();
+ void attachedPropertiesOnEditDelegate();
+ void requiredPropertiesOnEditDelegate();
+ void resettingRolesRespected();
+ void checkScroll_data();
+ void checkScroll();
+ void checkRebuildJsModel();
};
tst_QQuickTableView::tst_QQuickTableView()
+ : QQmlDataTest(QT_QMLTEST_DATADIR)
{
}
@@ -224,6 +312,18 @@ QPoint tst_QQuickTableView::getContextRowAndColumn(const QQuickItem *item) const
return QPoint(column, row);
}
+static void sendWheelEvent(QQuickWindow *window, QPoint pos, QPoint angleDelta,
+ QPoint pixelDelta,
+ Qt::KeyboardModifiers modifiers = Qt::NoModifier,
+ Qt::ScrollPhase phase = Qt::NoScrollPhase,
+ bool inverted = false) {
+ QWheelEvent wheelEvent(pos, window->mapToGlobal(pos), pixelDelta,
+ angleDelta, Qt::NoButton, modifiers, phase,
+ inverted);
+ QGuiApplication::sendEvent(window, &wheelEvent);
+ qApp->processEvents();
+}
+
void tst_QQuickTableView::setAndGetModel_data()
{
QTest::addColumn<QVariant>("model");
@@ -260,8 +360,9 @@ void tst_QQuickTableView::emptyModel()
LOAD_TABLEVIEW("plaintableview.qml");
tableView->setModel(model);
- WAIT_UNTIL_POLISHED;
- QCOMPARE(tableViewPrivate->loadedItems.count(), 0);
+ if (QQuickTest::qIsPolishScheduled(tableView))
+ WAIT_UNTIL_POLISHED;
+ QCOMPARE(tableViewPrivate->loadedItems.size(), 0);
}
void tst_QQuickTableView::checkPreload_data()
@@ -344,6 +445,54 @@ void tst_QQuickTableView::checkImplicitSizeDelegate()
}
}
+void tst_QQuickTableView::checkZeroSizedTableView()
+{
+ // Check that we don't load any delegates if TableView
+ // itself has zero size.
+ LOAD_TABLEVIEW("zerosizedtableview.qml");
+
+ auto model = TestModelAsVariant(100, 100);
+ tableView->setModel(model);
+
+ WAIT_UNTIL_POLISHED;
+
+ QVERIFY(tableViewPrivate->loadedItems.isEmpty());
+
+ // Resize TableView. This should load delegate. Since
+ // the delegate's implicitWidth is bound to TableView.width,
+ // we expect the delegates to now get the same width.
+ tableView->setWidth(200);
+ tableView->setHeight(100);
+
+ WAIT_UNTIL_POLISHED;
+
+ QCOMPARE(tableViewPrivate->loadedItems.size(), 2);
+ const auto item = tableView->itemAtIndex(tableView->index(0, 0));
+ QVERIFY(item);
+ QCOMPARE(item->width(), 200);
+
+ // Hide TableView again, and check that all items are
+ // unloaded, except the topLeft corner item.
+ tableView->setWidth(0);
+ tableView->setHeight(0);
+
+ WAIT_UNTIL_POLISHED;
+
+ QCOMPARE(tableViewPrivate->loadedItems.size(), 1);
+}
+
+void tst_QQuickTableView::checkZeroSizedViewPort()
+{
+ LOAD_TABLEVIEW("zerosizedviewport.qml");
+
+ auto model = TestModelAsVariant(20, 20);
+ tableView->setModel(model);
+
+ WAIT_UNTIL_POLISHED;
+
+ QVERIFY(!tableViewPrivate->loadedItems.isEmpty());
+}
+
void tst_QQuickTableView::checkColumnWidthWithoutProvider()
{
// Checks that a function isn't assigned to the columnWidthProvider property
@@ -357,15 +506,42 @@ void tst_QQuickTableView::checkColumnWidthWithoutProvider()
WAIT_UNTIL_POLISHED;
- for (const int column : tableViewPrivate->loadedColumns.keys()) {
+ for (const int column : tableViewPrivate->loadedColumns) {
const qreal expectedColumnWidth = tableViewPrivate->sizeHintForColumn(column);
- for (const int row : tableViewPrivate->loadedRows.keys()) {
+ for (const int row : tableViewPrivate->loadedRows) {
const auto item = tableViewPrivate->loadedTableItem(QPoint(column, row))->item;
QCOMPARE(item->width(), expectedColumnWidth);
}
}
}
+void tst_QQuickTableView::checkColumnWidthAndRowHeightFunctions()
+{
+ // Checks that the column width and row height functions return
+ // the correct sizes. When we have row-, or columnWidthProviders
+ // the actual row and column sizes will normally differ from the
+ // minimum row and column sizes (which is the maximum implicit
+ // size found among the delegates).
+ LOAD_TABLEVIEW("userowcolumnprovider.qml");
+
+ const int count = 4;
+ auto model = TestModelAsVariant(count, count);
+
+ tableView->setModel(model);
+
+ WAIT_UNTIL_POLISHED;
+
+ const qreal expectedimplicitSize = 20;
+
+ for (int i = 0; i < count; ++i) {
+ const qreal expectedSize = i + 10;
+ QCOMPARE(tableView->columnWidth(i), expectedSize);
+ QCOMPARE(tableView->rowHeight(i), expectedSize);
+ QCOMPARE(tableView->implicitColumnWidth(i), expectedimplicitSize);
+ QCOMPARE(tableView->implicitRowHeight(i), expectedimplicitSize);
+ }
+}
+
void tst_QQuickTableView::checkDelegateWithAnchors()
{
// Checks that we issue a warning if the delegate has anchors
@@ -453,6 +629,27 @@ void tst_QQuickTableView::checkColumnWidthProviderNotCallable()
QCOMPARE(fxItem->item->width(), kDefaultColumnWidth);
}
+void tst_QQuickTableView::checkColumnWidthBoundToViewWidth()
+{
+ // Check that you can bind the width of a delegate to the
+ // width of TableView, and that it updates when TableView is resized.
+ LOAD_TABLEVIEW("columnwidthboundtoviewwidth.qml");
+
+ auto model = TestModelAsVariant(10, 1);
+ tableView->setModel(model);
+
+ WAIT_UNTIL_POLISHED;
+
+ for (auto fxItem : tableViewPrivate->loadedItems)
+ QCOMPARE(fxItem->item->width(), tableView->width());
+
+ tableView->setWidth(200);
+ WAIT_UNTIL_POLISHED;
+
+ for (auto fxItem : tableViewPrivate->loadedItems)
+ QCOMPARE(fxItem->item->width(), 200);
+}
+
void tst_QQuickTableView::checkRowHeightWithoutProvider()
{
// Checks that a function isn't assigned to the rowHeightProvider property
@@ -466,9 +663,9 @@ void tst_QQuickTableView::checkRowHeightWithoutProvider()
WAIT_UNTIL_POLISHED;
- for (const int row : tableViewPrivate->loadedRows.keys()) {
+ for (const int row : tableViewPrivate->loadedRows) {
const qreal expectedRowHeight = tableViewPrivate->sizeHintForRow(row);
- for (const int column : tableViewPrivate->loadedColumns.keys()) {
+ for (const int column : tableViewPrivate->loadedColumns) {
const auto item = tableViewPrivate->loadedTableItem(QPoint(column, row))->item;
QCOMPARE(item->height(), expectedRowHeight);
}
@@ -551,6 +748,26 @@ void tst_QQuickTableView::checkRowHeightProviderNotCallable()
QCOMPARE(fxItem->item->height(), kDefaultRowHeight);
}
+void tst_QQuickTableView::isColumnLoadedAndIsRowLoaded()
+{
+ // Check that all the delegate items are loaded and available from
+ // the columnWidthProvider/rowHeightProvider when 'isColumnLoaded()'
+ // and 'isRowLoaded()' returns true.
+ LOAD_TABLEVIEW("iscolumnloaded.qml");
+
+ auto model = TestModelAsVariant(4, 5);
+
+ tableView->setModel(model);
+
+ WAIT_UNTIL_POLISHED;
+
+ const int itemsInColumnAfterLoaded = view->rootObject()->property("itemsInColumnAfterLoaded").toInt();
+ const int itemsInRowAfterLoaded = view->rootObject()->property("itemsInRowAfterLoaded").toInt();
+
+ QCOMPARE(itemsInColumnAfterLoaded, tableView->rows());
+ QCOMPARE(itemsInRowAfterLoaded, tableView->columns());
+}
+
void tst_QQuickTableView::checkForceLayoutFunction()
{
// When we set the 'columnWidths' property in the test file, the
@@ -612,10 +829,11 @@ void tst_QQuickTableView::checkForceLayoutEndUpDoingALayout()
QCOMPARE(tableView->contentHeight(), (9 * (newDelegateSize + rowSpacing)) - rowSpacing);
}
-void tst_QQuickTableView::checkForceLayoutDuringModelChange()
+void tst_QQuickTableView::checkForceLayoutInbetweenAddingRowsToModel()
{
- // Check that TableView doesn't assert if we call
- // forceLayout() in the middle of a model change.
+ // Check that TableView doesn't assert if we call forceLayout() while waiting
+ // for a callback from the model that the row count has changed. Also make sure
+ // that we don't move the contentItem while doing so.
LOAD_TABLEVIEW("plaintableview.qml");
const int initialRowCount = 10;
@@ -630,9 +848,91 @@ void tst_QQuickTableView::checkForceLayoutDuringModelChange()
WAIT_UNTIL_POLISHED;
+ const int contentY = 10;
+ tableView->setContentY(contentY);
QCOMPARE(tableView->rows(), initialRowCount);
+ QCOMPARE(tableView->contentY(), contentY);
model.addRow(0);
QCOMPARE(tableView->rows(), initialRowCount + 1);
+ QCOMPARE(tableView->contentY(), contentY);
+}
+
+void tst_QQuickTableView::checkForceLayoutWhenAllItemsAreHidden()
+{
+ // Check that you can have a TableView where all columns are
+ // initially hidden, and then show some columns and call
+ // forceLayout(). This should make the columns become visible.
+ LOAD_TABLEVIEW("forcelayout.qml");
+
+ // Tell all columns to be hidden
+ const char *propertyName = "columnWidths";
+ view->rootObject()->setProperty(propertyName, 0);
+
+ const int rows = 3;
+ const int columns = 3;
+ auto model = TestModelAsVariant(rows, columns);
+ tableView->setModel(model);
+
+ WAIT_UNTIL_POLISHED;
+
+ // Check that the we have no items loaded
+ QCOMPARE(tableViewPrivate->loadedColumns.count(), 0);
+ QCOMPARE(tableViewPrivate->loadedRows.count(), 0);
+ QCOMPARE(tableViewPrivate->loadedItems.size(), 0);
+
+ // Tell all columns to be visible
+ view->rootObject()->setProperty(propertyName, 10);
+ tableView->forceLayout();
+
+ QCOMPARE(tableViewPrivate->loadedRows.count(), rows);
+ QCOMPARE(tableViewPrivate->loadedColumns.count(), columns);
+ QCOMPARE(tableViewPrivate->loadedItems.size(), rows * columns);
+}
+
+void tst_QQuickTableView::checkLayoutChangedSignal()
+{
+ // Check that the layoutChanged signal is emitted
+ // when the layout has changed.
+ LOAD_TABLEVIEW("plaintableview.qml");
+
+ const QSignalSpy layoutChanges(tableView, &QQuickTableView::layoutChanged);
+ TestModel model(100, 100);
+ tableView->setModel(QVariant::fromValue(&model));
+
+ WAIT_UNTIL_POLISHED;
+
+ QCOMPARE(layoutChanges.size(), 1);
+
+ tableView->forceLayout();
+ QCOMPARE(layoutChanges.size(), 2);
+
+ tableView->setRowHeight(1, 10);
+ WAIT_UNTIL_POLISHED;
+ QCOMPARE(layoutChanges.size(), 3);
+
+ tableView->setColumnWidth(1, 10);
+ WAIT_UNTIL_POLISHED;
+ QCOMPARE(layoutChanges.size(), 4);
+
+ tableView->setContentX(30);
+ QCOMPARE(layoutChanges.size(), 5);
+
+ tableView->setContentY(30);
+ QCOMPARE(layoutChanges.size(), 6);
+
+ tableView->setContentX(0);
+ QCOMPARE(layoutChanges.size(), 7);
+
+ tableView->setContentY(0);
+ QCOMPARE(layoutChanges.size(), 8);
+
+ model.addRow(1);
+ WAIT_UNTIL_POLISHED;
+ QCOMPARE(layoutChanges.size(), 9);
+
+ model.removeRow(1);
+ WAIT_UNTIL_POLISHED;
+ QCOMPARE(layoutChanges.size(), 10);
}
void tst_QQuickTableView::checkContentWidthAndHeight()
@@ -683,6 +983,30 @@ void tst_QQuickTableView::checkContentWidthAndHeight()
QCOMPARE(tableView->contentHeight(), expectedSizeInit);
}
+void tst_QQuickTableView::checkContentWidthAndHeightForSmallTables()
+{
+ // For tables where all the columns in the model are loaded, we know
+ // the exact table width, and can therefore update the content width
+ // if e.g new rows are added or removed. The same is true for rows.
+ // This test will check that we do so.
+ LOAD_TABLEVIEW("sizefromdelegate.qml");
+
+ TestModel model(3, 3);
+ tableView->setModel(QVariant::fromValue(&model));
+ WAIT_UNTIL_POLISHED;
+
+ const qreal initialContentWidth = tableView->contentWidth();
+ const qreal initialContentHeight = tableView->contentHeight();
+ const QString longText = QStringLiteral("Adding a row with a very long text");
+ model.insertRow(0);
+ model.setModelData(QPoint(0, 0), QSize(1, 1), longText);
+
+ WAIT_UNTIL_POLISHED;
+
+ QVERIFY(tableView->contentWidth() > initialContentWidth);
+ QVERIFY(tableView->contentHeight() > initialContentHeight);
+}
+
void tst_QQuickTableView::checkPageFlicking()
{
// Check that we rebuild the table instead of refilling edges, if the viewport moves
@@ -992,7 +1316,7 @@ void tst_QQuickTableView::noDelegate()
WAIT_UNTIL_POLISHED;
items = tableViewPrivate->loadedItems;
- QCOMPARE(items.count(), rows * columns);
+ QCOMPARE(items.size(), rows * columns);
// And then unset the delegate again, and check
// that we end up with no items.
@@ -1081,11 +1405,11 @@ void tst_QQuickTableView::countDelegateItems()
// Check that tableview internals contain the expected number of items
auto const items = tableViewPrivate->loadedItems;
- QCOMPARE(items.count(), count);
+ QCOMPARE(items.size(), count);
// Check that this also matches the items found in the view
auto foundItems = findItems<QQuickItem>(tableView, kDelegateObjectName);
- QCOMPARE(foundItems.count(), count);
+ QCOMPARE(foundItems.size(), count);
}
void tst_QQuickTableView::checkLayoutOfEqualSizedDelegateItems_data()
@@ -2167,7 +2491,7 @@ void tst_QQuickTableView::checkChangingModelFromDelegate()
// And since the QML code tried to add another row as well, we
// expect rebuildScheduled to be true, and a polish event to be pending.
QVERIFY(tableViewPrivate->scheduledRebuildOptions);
- QCOMPARE(tableViewPrivate->polishScheduled, true);
+ QVERIFY(tableViewPrivate->polishScheduled);
WAIT_UNTIL_POLISHED;
// After handling the polish event, we expect also the third row to now be added
@@ -2198,7 +2522,7 @@ void tst_QQuickTableView::checkRebuildViewportOnly()
// Set reuse items to false, just to make it easier to
// check the number of items created during a rebuild.
tableView->setReuseItems(false);
- const int itemCountBeforeRebuild = tableViewPrivate->loadedItems.count();
+ const int itemCountBeforeRebuild = tableViewPrivate->loadedItems.size();
// Since all cells have the same size, we expect that we end up creating
// the same amount of items that were already showing before, even after
@@ -2353,18 +2677,94 @@ void tst_QQuickTableView::hideRowsAndColumns()
WAIT_UNTIL_POLISHED;
- const int expectedRowCount = modelSize - rowsToHideList.count();
- const int expectedColumnCount = modelSize - columnsToHideList.count();
+ const int expectedRowCount = modelSize - rowsToHideList.size();
+ const int expectedColumnCount = modelSize - columnsToHideList.size();
QCOMPARE(tableViewPrivate->loadedRows.count(), expectedRowCount);
QCOMPARE(tableViewPrivate->loadedColumns.count(), expectedColumnCount);
- for (const int row : tableViewPrivate->loadedRows.keys())
+ for (const int row : tableViewPrivate->loadedRows)
QVERIFY(!rowsToHideList.contains(row));
- for (const int column : tableViewPrivate->loadedColumns.keys())
+ for (const int column : tableViewPrivate->loadedColumns)
QVERIFY(!columnsToHideList.contains(column));
}
+void tst_QQuickTableView::hideAndShowFirstColumn()
+{
+ // Check that if we hide the first column, it will move
+ // the second column to the origin of the viewport. Then check
+ // that if we show the first column again, it will reappear at
+ // the origin of the viewport, and as such, pushing the second
+ // column to the right of it.
+ LOAD_TABLEVIEW("hiderowsandcolumns.qml");
+
+ const int modelSize = 5;
+ auto model = TestModelAsVariant(modelSize, modelSize);
+ tableView->setModel(model);
+
+ // Start by making the first column hidden
+ const auto columnsToHideList = QList<int>() << 0;
+ view->rootObject()->setProperty("columnsToHide", QVariant::fromValue(columnsToHideList));
+
+ WAIT_UNTIL_POLISHED;
+
+ const int expectedColumnCount = modelSize - columnsToHideList.size();
+ QCOMPARE(tableViewPrivate->loadedColumns.count(), expectedColumnCount);
+ QCOMPARE(tableViewPrivate->leftColumn(), 1);
+ QCOMPARE(tableView->contentX(), 0);
+ QCOMPARE(tableViewPrivate->loadedTableOuterRect.x(), 0);
+
+ // Make the first column in the model visible again
+ const auto emptyList = QList<int>();
+ view->rootObject()->setProperty("columnsToHide", QVariant::fromValue(emptyList));
+ tableView->forceLayout();
+
+ WAIT_UNTIL_POLISHED;
+
+ QCOMPARE(tableViewPrivate->loadedColumns.count(), modelSize);
+ QCOMPARE(tableViewPrivate->leftColumn(), 0);
+ QCOMPARE(tableView->contentX(), 0);
+ QCOMPARE(tableViewPrivate->loadedTableOuterRect.x(), 0);
+}
+
+void tst_QQuickTableView::hideAndShowFirstRow()
+{
+ // Check that if we hide the first row, it will move
+ // the second row to the origin of the viewport. Then check
+ // that if we show the first row again, it will reappear at
+ // the origin of the viewport, and as such, pushing the second
+ // row below it.
+ LOAD_TABLEVIEW("hiderowsandcolumns.qml");
+
+ const int modelSize = 5;
+ auto model = TestModelAsVariant(modelSize, modelSize);
+ tableView->setModel(model);
+
+ // Start by making the first row hidden
+ const auto rowsToHideList = QList<int>() << 0;
+ view->rootObject()->setProperty("rowsToHide", QVariant::fromValue(rowsToHideList));
+
+ WAIT_UNTIL_POLISHED;
+
+ const int expectedRowsCount = modelSize - rowsToHideList.size();
+ QCOMPARE(tableViewPrivate->loadedRows.count(), expectedRowsCount);
+ QCOMPARE(tableViewPrivate->topRow(), 1);
+ QCOMPARE(tableView->contentY(), 0);
+ QCOMPARE(tableViewPrivate->loadedTableOuterRect.y(), 0);
+
+ // Make the first row in the model visible again
+ const auto emptyList = QList<int>();
+ view->rootObject()->setProperty("rowsToHide", QVariant::fromValue(emptyList));
+ tableView->forceLayout();
+
+ WAIT_UNTIL_POLISHED;
+
+ QCOMPARE(tableViewPrivate->loadedRows.count(), modelSize);
+ QCOMPARE(tableViewPrivate->topRow(), 0);
+ QCOMPARE(tableView->contentY(), 0);
+ QCOMPARE(tableViewPrivate->loadedTableOuterRect.y(), 0);
+}
+
void tst_QQuickTableView::checkThatRevisionedPropertiesCannotBeUsedInOldImports()
{
// Check that if you use a QQmlAdaptorModel together with a Repeater, the
@@ -2379,9 +2779,21 @@ void tst_QQuickTableView::checkThatRevisionedPropertiesCannotBeUsedInOldImports(
void tst_QQuickTableView::checkSyncView_rootView_data()
{
QTest::addColumn<qreal>("flickToPos");
+ QTest::addColumn<qreal>("rowSpacing");
+ QTest::addColumn<qreal>("columnSpacing");
+ QTest::addColumn<qreal>("leftMargin");
+ QTest::addColumn<qreal>("rightMargin");
+ QTest::addColumn<qreal>("topMargin");
+ QTest::addColumn<qreal>("bottomMargin");
- QTest::newRow("pos:110") << 110.;
- QTest::newRow("pos:2010") << 2010.;
+ QTest::newRow("pos:110") << 110. << 0. << 0. << 0. << 0. << 0. << 0.;
+ QTest::newRow("pos:2010") << 2010. << 0. << 0. << 0. << 0. << 0. << 0.;
+
+ QTest::newRow("pos:110, spacing") << 110. << 10. << 20. << 0. << 0. << 0. << 0.;
+ QTest::newRow("pos:2010, spacing") << 2010. << 10. << 20. << 0. << 0. << 0. << 0.;
+
+ QTest::newRow("pos:110, margins") << 110. << 0. << 0. << 10. << 10. << 20. << 20.;
+ QTest::newRow("pos:2010, margins") << 2010. << 0. << 0. << 10. << 10. << 20. << 20.;
}
void tst_QQuickTableView::checkSyncView_rootView()
@@ -2390,6 +2802,13 @@ void tst_QQuickTableView::checkSyncView_rootView()
// no other view as syncView), all the other tableviews will sync
// their content view position according to their syncDirection flag.
QFETCH(qreal, flickToPos);
+ QFETCH(qreal, rowSpacing);
+ QFETCH(qreal, columnSpacing);
+ QFETCH(qreal, leftMargin);
+ QFETCH(qreal, rightMargin);
+ QFETCH(qreal, topMargin);
+ QFETCH(qreal, bottomMargin);
+
LOAD_TABLEVIEW("syncviewsimple.qml");
GET_QML_TABLEVIEW(tableViewH);
GET_QML_TABLEVIEW(tableViewV);
@@ -2402,18 +2821,32 @@ void tst_QQuickTableView::checkSyncView_rootView()
for (auto view : views)
view->setModel(model);
+ tableView->setRowSpacing(rowSpacing);
+ tableView->setColumnSpacing(columnSpacing);
+ tableView->setLeftMargin(leftMargin);
+ tableView->setRightMargin(rightMargin);
+ tableView->setTopMargin(topMargin);
+ tableView->setBottomMargin(bottomMargin);
tableView->setContentX(flickToPos);
tableView->setContentY(flickToPos);
WAIT_UNTIL_POLISHED;
- // Check that geometry properties are mirrored
+ // Check that geometry properties are mirrored accoring to sync direction
QCOMPARE(tableViewH->columnSpacing(), tableView->columnSpacing());
QCOMPARE(tableViewH->rowSpacing(), 0);
QCOMPARE(tableViewH->contentWidth(), tableView->contentWidth());
+ QCOMPARE(tableViewH->leftMargin(), tableView->leftMargin());
+ QCOMPARE(tableViewH->rightMargin(), tableView->rightMargin());
+ QCOMPARE(tableViewH->topMargin(), 0);
+ QCOMPARE(tableViewH->bottomMargin(), 0);
QCOMPARE(tableViewV->columnSpacing(), 0);
QCOMPARE(tableViewV->rowSpacing(), tableView->rowSpacing());
QCOMPARE(tableViewV->contentHeight(), tableView->contentHeight());
+ QCOMPARE(tableViewV->topMargin(), tableView->topMargin());
+ QCOMPARE(tableViewV->bottomMargin(), tableView->bottomMargin());
+ QCOMPARE(tableViewV->leftMargin(), 0);
+ QCOMPARE(tableViewV->rightMargin(), 0);
// Check that viewport is in sync after the flick
QCOMPARE(tableView->contentX(), flickToPos);
@@ -2444,6 +2877,18 @@ void tst_QQuickTableView::checkSyncView_rootView()
QCOMPARE(tableViewVPrivate->loadedTableOuterRect.left(), 0);
QCOMPARE(tableViewHVPrivate->loadedTableOuterRect, tableViewPrivate->loadedTableOuterRect);
+
+ // Check that the column widths are in sync
+ for (int column = tableView->leftColumn(); column < tableView->rightColumn(); ++column) {
+ QCOMPARE(tableViewH->columnWidth(column), tableView->columnWidth(column));
+ QCOMPARE(tableViewHV->columnWidth(column), tableView->columnWidth(column));
+ }
+
+ // Check that the row heights are in sync
+ for (int row = tableView->topRow(); row < tableView->bottomRow(); ++row) {
+ QCOMPARE(tableViewV->rowHeight(row), tableView->rowHeight(row));
+ QCOMPARE(tableViewHV->rowHeight(row), tableView->rowHeight(row));
+ }
}
void tst_QQuickTableView::checkSyncView_childViews_data()
@@ -2549,6 +2994,18 @@ void tst_QQuickTableView::checkSyncView_childViews()
QCOMPARE(tableViewHVPrivate->bottomRow(), tableViewPrivate->bottomRow());
QCOMPARE(tableViewHVPrivate->loadedTableOuterRect, tableViewPrivate->loadedTableOuterRect);
}
+
+ // Check that the column widths are in sync
+ for (int column = tableView->leftColumn(); column < tableView->rightColumn(); ++column) {
+ QCOMPARE(tableViewH->columnWidth(column), tableView->columnWidth(column));
+ QCOMPARE(tableViewHV->columnWidth(column), tableView->columnWidth(column));
+ }
+
+ // Check that the row heights are in sync
+ for (int row = tableView->topRow(); row < tableView->bottomRow(); ++row) {
+ QCOMPARE(tableViewV->rowHeight(row), tableView->rowHeight(row));
+ QCOMPARE(tableViewHV->rowHeight(row), tableView->rowHeight(row));
+ }
}
void tst_QQuickTableView::checkSyncView_differentSizedModels()
@@ -2614,6 +3071,61 @@ void tst_QQuickTableView::checkSyncView_differentSizedModels()
QVERIFY(tableViewHVPrivate->loadedColumns.isEmpty());
}
+void tst_QQuickTableView::checkSyncView_differentGeometry()
+{
+ // Check that you can have two tables in a syncView relation, where
+ // the sync "child" is larger than the sync view. This means that the
+ // child will display more rows and columns than the parent.
+ // In that case, the sync view will anyway need to load the same rows
+ // and columns as the child, otherwise the column and row sizes
+ // cannot be determined for the child.
+ LOAD_TABLEVIEW("syncviewsimple.qml");
+ GET_QML_TABLEVIEW(tableViewH);
+ GET_QML_TABLEVIEW(tableViewV);
+ GET_QML_TABLEVIEW(tableViewHV);
+
+ tableView->setWidth(40);
+ tableView->setHeight(40);
+
+ auto tableViewModel = TestModelAsVariant(100, 100);
+
+ tableView->setModel(tableViewModel);
+ tableViewH->setModel(tableViewModel);
+ tableViewV->setModel(tableViewModel);
+ tableViewHV->setModel(tableViewModel);
+
+ WAIT_UNTIL_POLISHED;
+
+ // Check that the column widths are in sync
+ for (int column = tableViewH->leftColumn(); column < tableViewH->rightColumn(); ++column) {
+ QCOMPARE(tableViewH->columnWidth(column), tableView->columnWidth(column));
+ QCOMPARE(tableViewHV->columnWidth(column), tableView->columnWidth(column));
+ }
+
+ // Check that the row heights are in sync
+ for (int row = tableViewV->topRow(); row < tableViewV->bottomRow(); ++row) {
+ QCOMPARE(tableViewV->rowHeight(row), tableView->rowHeight(row));
+ QCOMPARE(tableViewHV->rowHeight(row), tableView->rowHeight(row));
+ }
+
+ // Flick a bit, and do the same test again
+ tableView->setContentX(200);
+ tableView->setContentY(200);
+ WAIT_UNTIL_POLISHED;
+
+ // Check that the column widths are in sync
+ for (int column = tableViewH->leftColumn(); column < tableViewH->rightColumn(); ++column) {
+ QCOMPARE(tableViewH->columnWidth(column), tableView->columnWidth(column));
+ QCOMPARE(tableViewHV->columnWidth(column), tableView->columnWidth(column));
+ }
+
+ // Check that the row heights are in sync
+ for (int row = tableViewV->topRow(); row < tableViewV->bottomRow(); ++row) {
+ QCOMPARE(tableViewV->rowHeight(row), tableView->rowHeight(row));
+ QCOMPARE(tableViewHV->rowHeight(row), tableView->rowHeight(row));
+ }
+}
+
void tst_QQuickTableView::checkSyncView_connect_late_data()
{
QTest::addColumn<qreal>("flickToPos");
@@ -2781,6 +3293,57 @@ void tst_QQuickTableView::checkSyncView_emptyModel()
QCOMPARE(tableViewVPrivate->loadedTableOuterRect.left(), 0);
}
+void tst_QQuickTableView::checkSyncView_unloadHeader()
+{
+ // Check that we don't get a crash in TableView if one
+ // of the sync children is suddenly deleted (from e.g a Loader).
+ LOAD_TABLEVIEW("unloadheader.qml");
+
+ const auto loader = view->rootObject()->property("loader").value<QQuickLoader *>();
+ QVERIFY(loader);
+ QVERIFY(loader->item());
+ loader->setActive(false);
+ QVERIFY(!loader->item());
+ gc(*qmlEngine(tableView));
+ tableView->forceLayout();
+}
+
+void tst_QQuickTableView::checkSyncView_topLeftChanged()
+{
+ LOAD_TABLEVIEW("syncviewsimple.qml");
+ GET_QML_TABLEVIEW(tableViewH);
+ GET_QML_TABLEVIEW(tableViewV);
+ GET_QML_TABLEVIEW(tableViewHV);
+ QQuickTableView *views[] = {tableViewH, tableViewV, tableViewHV};
+
+ auto model = TestModelAsVariant(100, 100);
+ tableView->setModel(model);
+
+ for (auto view : views)
+ view->setModel(model);
+
+ tableView->setColumnWidthProvider(QJSValue());
+ tableView->setRowHeightProvider(QJSValue());
+ view->rootObject()->setProperty("delegateWidth", 300);
+ view->rootObject()->setProperty("delegateHeight", 300);
+ tableView->forceLayout();
+
+ tableViewHV->setContentX(350);
+ tableViewHV->setContentY(350);
+
+ WAIT_UNTIL_POLISHED;
+
+ QCOMPARE(tableViewH->leftColumn(), tableView->leftColumn());
+ QCOMPARE(tableViewV->topRow(), tableView->topRow());
+
+ view->rootObject()->setProperty("delegateWidth", 50);
+ view->rootObject()->setProperty("delegateHeight", 50);
+ tableView->forceLayout();
+
+ QCOMPARE(tableViewH->leftColumn(), tableView->leftColumn());
+ QCOMPARE(tableViewV->topRow(), tableView->topRow());
+}
+
void tst_QQuickTableView::checkThatFetchMoreIsCalledWhenScrolledToTheEndOfTable()
{
LOAD_TABLEVIEW("plaintableview.qml");
@@ -2871,9 +3434,10 @@ void tst_QQuickTableView::replaceModel()
void tst_QQuickTableView::cellAtPos_data()
{
QTest::addColumn<QPointF>("contentStartPos");
- QTest::addColumn<QPointF>("position");
+ QTest::addColumn<QPointF>("localPos");
QTest::addColumn<bool>("includeSpacing");
QTest::addColumn<QPoint>("expectedCell");
+ QTest::addColumn<QSizeF>("margins");
const int spacing = 10;
const QPointF cellSize(100, 50);
@@ -2886,109 +3450,109 @@ void tst_QQuickTableView::cellAtPos_data()
return QPointF(x, y);
};
- QTest::newRow("1") << QPointF(0, 0) << cellStart(0, 0) << false << QPoint(0, 0);
- QTest::newRow("2") << QPointF(0, 0) << cellStart(1, 0) << false << QPoint(1, 0);
- QTest::newRow("3") << QPointF(0, 0) << cellStart(0, 1) << false << QPoint(0, 1);
- QTest::newRow("4") << QPointF(0, 0) << cellStart(1, 1) << false << QPoint(1, 1);
-
- QTest::newRow("5") << QPointF(0, 0) << cellStart(1, 1) - quadSpace << false << QPoint(-1, -1);
- QTest::newRow("6") << QPointF(0, 0) << cellStart(0, 0) + cellSize + quadSpace << false << QPoint(-1, -1);
- QTest::newRow("7") << QPointF(0, 0) << cellStart(0, 1) + cellSize + quadSpace << false << QPoint(-1, -1);
-
- QTest::newRow("8") << QPointF(0, 0) << cellStart(1, 1) - quadSpace << true << QPoint(1, 1);
- QTest::newRow("9") << QPointF(0, 0) << cellStart(0, 0) + cellSize + quadSpace << true << QPoint(0, 0);
- QTest::newRow("10") << QPointF(0, 0) << cellStart(0, 1) + cellSize + quadSpace << true << QPoint(0, 1);
-
- QTest::newRow("11") << cellStart(50, 50) << cellStart(0, 0) << false << QPoint(50, 50);
- QTest::newRow("12") << cellStart(50, 50) << cellStart(4, 4) << false << QPoint(54, 54);
- QTest::newRow("13") << cellStart(50, 50) << cellStart(4, 4) - quadSpace << false << QPoint(-1, -1);
- QTest::newRow("14") << cellStart(50, 50) << cellStart(4, 4) + cellSize + quadSpace << false << QPoint(-1, -1);
- QTest::newRow("15") << cellStart(50, 50) << cellStart(4, 4) - quadSpace << true << QPoint(54, 54);
- QTest::newRow("16") << cellStart(50, 50) << cellStart(4, 4) + cellSize + quadSpace << true << QPoint(54, 54);
-
- QTest::newRow("17") << cellStart(50, 50) + halfCell << cellStart(0, 0) << false << QPoint(50, 50);
- QTest::newRow("18") << cellStart(50, 50) + halfCell << cellStart(1, 1) << false << QPoint(51, 51);
- QTest::newRow("19") << cellStart(50, 50) + halfCell << cellStart(4, 4) << false << QPoint(54, 54);
+ QTest::newRow("1") << QPointF(0, 0) << cellStart(0, 0) << false << QPoint(0, 0) << QSizeF(0, 0);
+ QTest::newRow("2") << QPointF(0, 0) << cellStart(1, 0) << false << QPoint(1, 0) << QSizeF(0, 0);
+ QTest::newRow("3") << QPointF(0, 0) << cellStart(0, 1) << false << QPoint(0, 1) << QSizeF(0, 0);
+ QTest::newRow("4") << QPointF(0, 0) << cellStart(1, 1) << false << QPoint(1, 1) << QSizeF(0, 0);
+
+ QTest::newRow("5") << QPointF(0, 0) << cellStart(1, 1) - quadSpace << false << QPoint(-1, -1) << QSizeF(0, 0);
+ QTest::newRow("6") << QPointF(0, 0) << cellStart(0, 0) + cellSize + quadSpace << false << QPoint(-1, -1) << QSizeF(0, 0);
+ QTest::newRow("7") << QPointF(0, 0) << cellStart(0, 1) + cellSize + quadSpace << false << QPoint(-1, -1) << QSizeF(0, 0);
+
+ QTest::newRow("8") << QPointF(0, 0) << cellStart(1, 1) - quadSpace << true << QPoint(1, 1) << QSizeF(0, 0);
+ QTest::newRow("9") << QPointF(0, 0) << cellStart(0, 0) + cellSize + quadSpace << true << QPoint(0, 0) << QSizeF(0, 0);
+ QTest::newRow("10") << QPointF(0, 0) << cellStart(0, 1) + cellSize + quadSpace << true << QPoint(0, 1) << QSizeF(0, 0);
+
+ QTest::newRow("11") << cellStart(50, 50) << cellStart(50, 50) << false << QPoint(50, 50) << QSizeF(0, 0);
+ QTest::newRow("12") << cellStart(50, 50) << cellStart(54, 54) << false << QPoint(54, 54) << QSizeF(0, 0);
+ QTest::newRow("13") << cellStart(50, 50) << cellStart(54, 54) - quadSpace << false << QPoint(-1, -1) << QSizeF(0, 0);
+ QTest::newRow("14") << cellStart(50, 50) << cellStart(54, 54) + cellSize + quadSpace << false << QPoint(-1, -1) << QSizeF(0, 0);
+ QTest::newRow("15") << cellStart(50, 50) << cellStart(54, 54) - quadSpace << true << QPoint(54, 54) << QSizeF(0, 0);
+ QTest::newRow("16") << cellStart(50, 50) << cellStart(54, 54) + cellSize + quadSpace << true << QPoint(54, 54) << QSizeF(0, 0);
+
+ QTest::newRow("17") << cellStart(50, 50) + halfCell << cellStart(50, 50) << false << QPoint(50, 50) << QSizeF(0, 0);
+ QTest::newRow("18") << cellStart(50, 50) + halfCell << cellStart(51, 51) << false << QPoint(51, 51) << QSizeF(0, 0);
+ QTest::newRow("19") << cellStart(50, 50) + halfCell << cellStart(54, 54) << false << QPoint(54, 54) << QSizeF(0, 0);
+
+ QTest::newRow("20") << QPointF(0, 0) << cellStart(0, 0) << false << QPoint(0, 0) << QSizeF(150, 150);
+ QTest::newRow("20") << QPointF(0, 0) << cellStart(5, 5) << false << QPoint(5, 5) << QSizeF(150, 150);
+
+ QTest::newRow("20") << QPointF(-150, -150) << cellStart(0, 0) << false << QPoint(0, 0) << QSizeF(150, 150);
+ QTest::newRow("21") << QPointF(-150, -150) << cellStart(4, 0) + halfCell << false << QPoint(4, 0) << QSizeF(150, 150);
+ QTest::newRow("22") << QPointF(-150, -150) << cellStart(0, 4) + halfCell << false << QPoint(0, 4) << QSizeF(150, 150);
+ QTest::newRow("23") << QPointF(-150, -150) << cellStart(4, 4) + halfCell << false << QPoint(4, 4) << QSizeF(150, 150);
}
void tst_QQuickTableView::cellAtPos()
{
QFETCH(QPointF, contentStartPos);
- QFETCH(QPointF, position);
+ QFETCH(QPointF, localPos);
QFETCH(bool, includeSpacing);
QFETCH(QPoint, expectedCell);
+ QFETCH(QSizeF, margins);
LOAD_TABLEVIEW("plaintableview.qml");
auto model = TestModelAsVariant(100, 100);
tableView->setModel(model);
tableView->setRowSpacing(10);
tableView->setColumnSpacing(10);
+ tableView->setLeftMargin(margins.width());
+ tableView->setLeftMargin(margins.height());
+ tableView->setTopMargin(margins.height());
tableView->setContentX(contentStartPos.x());
tableView->setContentY(contentStartPos.y());
WAIT_UNTIL_POLISHED;
- QPoint cell = tableView->cellAtPos(position, includeSpacing);
+ QPoint cell = tableView->cellAtPosition(localPos, includeSpacing);
QCOMPARE(cell, expectedCell);
}
void tst_QQuickTableView::positionViewAtRow_data()
{
QTest::addColumn<int>("row");
- QTest::addColumn<Qt::AlignmentFlag>("alignment");
+ QTest::addColumn<QQuickTableView::PositionModeFlag>("alignment");
QTest::addColumn<qreal>("offset");
+ QTest::addColumn<QRectF>("subRect");
QTest::addColumn<qreal>("contentYStartPos");
- QTest::newRow("AlignTop 0") << 0 << Qt::AlignTop << 0. << 0.;
- QTest::newRow("AlignTop 1") << 1 << Qt::AlignTop << 0. << 0.;
- QTest::newRow("AlignTop 1") << 1 << Qt::AlignTop << 0. << 50.;
- QTest::newRow("AlignTop 50") << 50 << Qt::AlignTop << 0. << -1.;
- QTest::newRow("AlignTop 0") << 0 << Qt::AlignTop << 0. << -1.;
- QTest::newRow("AlignTop 99") << 99 << Qt::AlignTop << 0. << -1.;
-
- QTest::newRow("AlignTop 0") << 0 << Qt::AlignTop << -10. << 0.;
- QTest::newRow("AlignTop 1") << 1 << Qt::AlignTop << -10. << 0.;
- QTest::newRow("AlignTop 1") << 1 << Qt::AlignTop << -10. << 50.;
- QTest::newRow("AlignTop 50") << 50 << Qt::AlignTop << -10. << -1.;
- QTest::newRow("AlignTop 0") << 0 << Qt::AlignTop << -10. << -1.;
- QTest::newRow("AlignTop 99") << 99 << Qt::AlignTop << -10. << -1.;
-
- QTest::newRow("AlignBottom 0") << 0 << Qt::AlignBottom << 0. << 0.;
- QTest::newRow("AlignBottom 1") << 1 << Qt::AlignBottom << 0. << 0.;
- QTest::newRow("AlignBottom 1") << 1 << Qt::AlignBottom << 0. << 50.;
- QTest::newRow("AlignBottom 50") << 50 << Qt::AlignBottom << 0. << -1.;
- QTest::newRow("AlignBottom 0") << 0 << Qt::AlignBottom << 0. << -1.;
- QTest::newRow("AlignBottom 99") << 99 << Qt::AlignBottom << 0. << -1.;
-
- QTest::newRow("AlignBottom 0") << 0 << Qt::AlignBottom << 10. << 0.;
- QTest::newRow("AlignBottom 1") << 1 << Qt::AlignBottom << 10. << 0.;
- QTest::newRow("AlignBottom 1") << 1 << Qt::AlignBottom << 10. << 50.;
- QTest::newRow("AlignBottom 50") << 50 << Qt::AlignBottom << 10. << -1.;
- QTest::newRow("AlignBottom 0") << 0 << Qt::AlignBottom << 10. << -1.;
- QTest::newRow("AlignBottom 99") << 99 << Qt::AlignBottom << 10. << -1.;
-
- QTest::newRow("AlignCenter 0") << 0 << Qt::AlignCenter << 0. << 0.;
- QTest::newRow("AlignCenter 1") << 1 << Qt::AlignCenter << 0. << 0.;
- QTest::newRow("AlignCenter 1") << 1 << Qt::AlignCenter << 0. << 50.;
- QTest::newRow("AlignCenter 50") << 50 << Qt::AlignCenter << 0. << -1.;
- QTest::newRow("AlignCenter 0") << 0 << Qt::AlignCenter << 0. << -1.;
- QTest::newRow("AlignCenter 99") << 99 << Qt::AlignCenter << 0. << -1.;
-
- QTest::newRow("AlignCenter 0") << 0 << Qt::AlignCenter << -10. << 0.;
- QTest::newRow("AlignCenter 1") << 1 << Qt::AlignCenter << -10. << 0.;
- QTest::newRow("AlignCenter 1") << 1 << Qt::AlignCenter << -10. << 50.;
- QTest::newRow("AlignCenter 50") << 50 << Qt::AlignCenter << -10. << -1.;
- QTest::newRow("AlignCenter 0") << 0 << Qt::AlignCenter << -10. << -1.;
- QTest::newRow("AlignCenter 99") << 99 << Qt::AlignCenter << -10. << -1.;
+ QRectF subRects[] = { QRectF(), QRectF(11, 12, 13, 14) };
+
+ for (auto subRect : subRects) {
+ QTest::newRow("AlignTop 0") << 0 << QQuickTableView::AlignTop << 0. << subRect << 0.;
+ QTest::newRow("AlignTop 1") << 1 << QQuickTableView::AlignTop << 0. << subRect << 0.;
+ QTest::newRow("AlignTop 1") << 1 << QQuickTableView::AlignTop << 0. << subRect << 50.;
+ QTest::newRow("AlignTop 50") << 50 << QQuickTableView::AlignTop << 0. << subRect << -1.;
+ QTest::newRow("AlignTop 0") << 0 << QQuickTableView::AlignTop << 0. << subRect << -1.;
+ QTest::newRow("AlignTop 1") << 1 << QQuickTableView::AlignTop << -10. << subRect << 0.;
+ QTest::newRow("AlignTop 1") << 1 << QQuickTableView::AlignTop << -10. << subRect << 50.;
+ QTest::newRow("AlignTop 50") << 50 << QQuickTableView::AlignTop << -10. << subRect << -1.;
+
+ QTest::newRow("AlignBottom 50") << 50 << QQuickTableView::AlignBottom << 0. << subRect << -1.;
+ QTest::newRow("AlignBottom 98") << 98 << QQuickTableView::AlignBottom << 0. << subRect << -1.;
+ QTest::newRow("AlignBottom 99") << 99 << QQuickTableView::AlignBottom << 0. << subRect << -1.;
+ QTest::newRow("AlignBottom 50") << 40 << QQuickTableView::AlignBottom << 10. << subRect << -1.;
+ QTest::newRow("AlignBottom 40") << 50 << QQuickTableView::AlignBottom << -10. << subRect << -1.;
+ QTest::newRow("AlignBottom 98") << 98 << QQuickTableView::AlignBottom << 10. << subRect << -1.;
+ QTest::newRow("AlignBottom 99") << 99 << QQuickTableView::AlignBottom << -10. << subRect << -1.;
+
+ QTest::newRow("AlignCenter 40") << 40 << QQuickTableView::AlignCenter << 0. << subRect << -1.;
+ QTest::newRow("AlignCenter 50") << 50 << QQuickTableView::AlignCenter << 0. << subRect << -1.;
+ QTest::newRow("AlignCenter 40") << 40 << QQuickTableView::AlignCenter << 10. << subRect << -1.;
+ QTest::newRow("AlignCenter 50") << 50 << QQuickTableView::AlignCenter << -10. << subRect << -1.;
+ }
}
void tst_QQuickTableView::positionViewAtRow()
{
// Check that positionViewAtRow actually flicks the view
- // to the right position so that the row becomes visible
+ // to the right position so that the row becomes visible.
+ // For this test, we only check cells that can be placed exactly
+ // according to the given alignment.
QFETCH(int, row);
- QFETCH(Qt::AlignmentFlag, alignment);
+ QFETCH(QQuickTableView::PositionModeFlag, alignment);
QFETCH(qreal, offset);
+ QFETCH(QRectF, subRect);
QFETCH(qreal, contentYStartPos);
LOAD_TABLEVIEW("plaintableview.qml");
@@ -2999,24 +3563,26 @@ void tst_QQuickTableView::positionViewAtRow()
WAIT_UNTIL_POLISHED;
- tableView->positionViewAtRow(row, alignment, offset);
+ tableView->positionViewAtRow(row, alignment, offset, subRect);
- WAIT_UNTIL_POLISHED;
+ if (!tableView->isRowLoaded(row))
+ WAIT_UNTIL_POLISHED;
const QPoint cell(0, row);
const int modelIndex = tableViewPrivate->modelIndexAtCell(cell);
QVERIFY(tableViewPrivate->loadedItems.contains(modelIndex));
- const auto geometry = tableViewPrivate->loadedTableItem(cell)->geometry();
+ const QRectF cellRect = tableViewPrivate->loadedTableItem(cell)->geometry();
+ const QRectF alignmentRect = subRect.isValid() ? subRect.translated(cellRect.topLeft()) : cellRect;
switch (alignment) {
- case Qt::AlignTop:
- QCOMPARE(geometry.y(), tableView->contentY() - offset);
+ case QQuickTableView::AlignTop:
+ QCOMPARE(alignmentRect.y(), tableView->contentY() - offset);
break;
- case Qt::AlignBottom:
- QCOMPARE(geometry.bottom(), tableView->contentY() + tableView->height() - offset);
+ case QQuickTableView::AlignBottom:
+ QCOMPARE(alignmentRect.bottom(), tableView->contentY() + tableView->height() - offset);
break;
- case Qt::AlignCenter:
- QCOMPARE(geometry.y(), tableView->contentY() + (tableView->height() / 2) - (geometry.height() / 2) - offset);
+ case QQuickTableView::AlignCenter:
+ QCOMPARE(alignmentRect.y(), tableView->contentY() + (tableView->height() / 2) - (alignmentRect.height() / 2) - offset);
break;
default:
Q_UNREACHABLE();
@@ -3026,60 +3592,45 @@ void tst_QQuickTableView::positionViewAtRow()
void tst_QQuickTableView::positionViewAtColumn_data()
{
QTest::addColumn<int>("column");
- QTest::addColumn<Qt::AlignmentFlag>("alignment");
+ QTest::addColumn<QQuickTableView::PositionModeFlag>("alignment");
QTest::addColumn<qreal>("offset");
+ QTest::addColumn<QRectF>("subRect");
QTest::addColumn<qreal>("contentXStartPos");
- QTest::newRow("AlignLeft 0") << 0 << Qt::AlignLeft << 0. << 0.;
- QTest::newRow("AlignLeft 1") << 1 << Qt::AlignLeft << 0. << 0.;
- QTest::newRow("AlignLeft 1") << 1 << Qt::AlignLeft << 0. << 50.;
- QTest::newRow("AlignLeft 50") << 50 << Qt::AlignLeft << 0. << -1.;
- QTest::newRow("AlignLeft 0") << 0 << Qt::AlignLeft << 0. << -1.;
- QTest::newRow("AlignLeft 99") << 99 << Qt::AlignLeft << 0. << -1.;
-
- QTest::newRow("AlignLeft 0") << 0 << Qt::AlignLeft << -10. << 0.;
- QTest::newRow("AlignLeft 1") << 1 << Qt::AlignLeft << -10. << 0.;
- QTest::newRow("AlignLeft 1") << 1 << Qt::AlignLeft << -10. << 50.;
- QTest::newRow("AlignLeft 50") << 50 << Qt::AlignLeft << -10. << -1.;
- QTest::newRow("AlignLeft 0") << 0 << Qt::AlignLeft << -10. << -1.;
- QTest::newRow("AlignLeft 99") << 99 << Qt::AlignLeft << -10. << -1.;
-
- QTest::newRow("AlignRight 0") << 0 << Qt::AlignRight << 0. << 0.;
- QTest::newRow("AlignRight 1") << 1 << Qt::AlignRight << 0. << 0.;
- QTest::newRow("AlignRight 1") << 1 << Qt::AlignRight << 0. << 50.;
- QTest::newRow("AlignRight 50") << 50 << Qt::AlignRight << 0. << -1.;
- QTest::newRow("AlignRight 0") << 0 << Qt::AlignRight << 0. << -1.;
- QTest::newRow("AlignRight 99") << 99 << Qt::AlignRight << 0. << -1.;
-
- QTest::newRow("AlignRight 0") << 0 << Qt::AlignRight << 10. << 0.;
- QTest::newRow("AlignRight 1") << 1 << Qt::AlignRight << 10. << 0.;
- QTest::newRow("AlignRight 1") << 1 << Qt::AlignRight << 10. << 50.;
- QTest::newRow("AlignRight 50") << 50 << Qt::AlignRight << 10. << -1.;
- QTest::newRow("AlignRight 0") << 0 << Qt::AlignRight << 10. << -1.;
- QTest::newRow("AlignRight 99") << 99 << Qt::AlignRight << 10. << -1.;
-
- QTest::newRow("AlignCenter 0") << 0 << Qt::AlignCenter << 0. << 0.;
- QTest::newRow("AlignCenter 1") << 1 << Qt::AlignCenter << 0. << 0.;
- QTest::newRow("AlignCenter 1") << 1 << Qt::AlignCenter << 0. << 50.;
- QTest::newRow("AlignCenter 50") << 50 << Qt::AlignCenter << 0. << -1.;
- QTest::newRow("AlignCenter 0") << 0 << Qt::AlignCenter << 0. << -1.;
- QTest::newRow("AlignCenter 99") << 99 << Qt::AlignCenter << 0. << -1.;
-
- QTest::newRow("AlignCenter 0") << 0 << Qt::AlignCenter << -10. << 0.;
- QTest::newRow("AlignCenter 1") << 1 << Qt::AlignCenter << -10. << 0.;
- QTest::newRow("AlignCenter 1") << 1 << Qt::AlignCenter << -10. << 50.;
- QTest::newRow("AlignCenter 50") << 50 << Qt::AlignCenter << -10. << -1.;
- QTest::newRow("AlignCenter 0") << 0 << Qt::AlignCenter << -10. << -1.;
- QTest::newRow("AlignCenter 99") << 99 << Qt::AlignCenter << -10. << -1.;
+ QRectF subRects[] = { QRectF(), QRectF(11, 12, 13, 14) };
+
+ for (auto subRect : subRects) {
+ QTest::newRow("AlignLeft 0") << 0 << QQuickTableView::AlignLeft << 0. << subRect << 0.;
+ QTest::newRow("AlignLeft 1") << 1 << QQuickTableView::AlignLeft << 0. << subRect << 0.;
+ QTest::newRow("AlignLeft 1") << 1 << QQuickTableView::AlignLeft << 0. << subRect << 50.;
+ QTest::newRow("AlignLeft 50") << 50 << QQuickTableView::AlignLeft << 0. << subRect << -1.;
+ QTest::newRow("AlignLeft 0") << 0 << QQuickTableView::AlignLeft << 0. << subRect << -1.;
+ QTest::newRow("AlignLeft 1") << 1 << QQuickTableView::AlignLeft << -10. << subRect << 0.;
+ QTest::newRow("AlignLeft 1") << 1 << QQuickTableView::AlignLeft << -10. << subRect << 50.;
+ QTest::newRow("AlignLeft 50") << 50 << QQuickTableView::AlignLeft << -10. << subRect << -1.;
+
+ QTest::newRow("AlignRight 50") << 50 << QQuickTableView::AlignRight << 0. << subRect << -1.;
+ QTest::newRow("AlignRight 99") << 99 << QQuickTableView::AlignRight << 0. << subRect << -1.;
+ QTest::newRow("AlignRight 50") << 50 << QQuickTableView::AlignRight << 10. << subRect << -1.;
+ QTest::newRow("AlignRight 99") << 99 << QQuickTableView::AlignRight << -10. << subRect << -1.;
+
+ QTest::newRow("AlignCenter 40") << 50 << QQuickTableView::AlignCenter << 0. << subRect << -1.;
+ QTest::newRow("AlignCenter 50") << 50 << QQuickTableView::AlignCenter << 0. << subRect << -1.;
+ QTest::newRow("AlignCenter 40") << 50 << QQuickTableView::AlignCenter << 10. << subRect << -1.;
+ QTest::newRow("AlignCenter 50") << 50 << QQuickTableView::AlignCenter << -10. << subRect << -1.;
+ }
}
void tst_QQuickTableView::positionViewAtColumn()
{
// Check that positionViewAtColumn actually flicks the view
- // to the right position so that the row becomes visible
+ // to the right position so that the row becomes visible.
+ // For this test, we only check cells that can be placed exactly
+ // according to the given alignment.
QFETCH(int, column);
- QFETCH(Qt::AlignmentFlag, alignment);
+ QFETCH(QQuickTableView::PositionModeFlag, alignment);
QFETCH(qreal, offset);
+ QFETCH(QRectF, subRect);
QFETCH(qreal, contentXStartPos);
LOAD_TABLEVIEW("plaintableview.qml");
@@ -3090,30 +3641,607 @@ void tst_QQuickTableView::positionViewAtColumn()
WAIT_UNTIL_POLISHED;
- tableView->positionViewAtColumn(column, alignment, offset);
+ tableView->positionViewAtColumn(column, alignment, offset, subRect);
- WAIT_UNTIL_POLISHED;
+ if (!tableView->isColumnLoaded(column))
+ WAIT_UNTIL_POLISHED;
const QPoint cell(column, 0);
const int modelIndex = tableViewPrivate->modelIndexAtCell(cell);
QVERIFY(tableViewPrivate->loadedItems.contains(modelIndex));
- const auto geometry = tableViewPrivate->loadedTableItem(cell)->geometry();
+ const QRectF cellRect = tableViewPrivate->loadedTableItem(cell)->geometry();
+ const QRectF alignmentRect = subRect.isValid() ? subRect.translated(cellRect.topLeft()) : cellRect;
switch (alignment) {
- case Qt::AlignLeft:
- QCOMPARE(geometry.x(), tableView->contentX() - offset);
+ case QQuickTableView::AlignLeft:
+ QCOMPARE(alignmentRect.x(), tableView->contentX() - offset);
break;
- case Qt::AlignRight:
- QCOMPARE(geometry.right(), tableView->contentX() + tableView->width() - offset);
+ case QQuickTableView::AlignRight:
+ QCOMPARE(alignmentRect.right(), tableView->contentX() + tableView->width() - offset);
break;
- case Qt::AlignCenter:
- QCOMPARE(geometry.x(), tableView->contentX() + (tableView->width() / 2) - (geometry.width() / 2) - offset);
+ case QQuickTableView::AlignCenter:
+ QCOMPARE(alignmentRect.x(), tableView->contentX() + (tableView->width() / 2) - (alignmentRect.width() / 2) - offset);
break;
default:
Q_UNREACHABLE();
}
}
+void tst_QQuickTableView::positionViewAtRowClamped_data()
+{
+ QTest::addColumn<int>("row");
+ QTest::addColumn<QQuickTableView::PositionModeFlag>("alignment");
+ QTest::addColumn<qreal>("offset");
+ QTest::addColumn<QRectF>("subRect");
+ QTest::addColumn<qreal>("contentYStartPos");
+
+ QRectF subRects[] = { QRectF(), QRectF(1, 2, 3, 4) };
+
+ for (auto subRect : subRects) {
+ QTest::newRow("AlignTop 0") << 0 << QQuickTableView::AlignTop << -10. << subRect << 0.;
+ QTest::newRow("AlignTop 0") << 0 << QQuickTableView::AlignTop << -10. << subRect << -1.;
+ QTest::newRow("AlignTop 99") << 99 << QQuickTableView::AlignTop << 0. << subRect << -1.;
+ QTest::newRow("AlignTop 99") << 99 << QQuickTableView::AlignTop << -10. << subRect << -1.;
+
+ QTest::newRow("AlignBottom 0") << 0 << QQuickTableView::AlignBottom << 0. << subRect << 0.;
+ QTest::newRow("AlignBottom 1") << 1 << QQuickTableView::AlignBottom << 0. << subRect << 0.;
+ QTest::newRow("AlignBottom 1") << 1 << QQuickTableView::AlignBottom << 0. << subRect << 50.;
+ QTest::newRow("AlignBottom 0") << 0 << QQuickTableView::AlignBottom << 0. << subRect << -1.;
+
+ QTest::newRow("AlignBottom 0") << 0 << QQuickTableView::AlignBottom << 10. << subRect << 0.;
+ QTest::newRow("AlignBottom 1") << 1 << QQuickTableView::AlignBottom << 10. << subRect << 0.;
+ QTest::newRow("AlignBottom 1") << 1 << QQuickTableView::AlignBottom << 10. << subRect << 50.;
+ QTest::newRow("AlignBottom 0") << 0 << QQuickTableView::AlignBottom << 10. << subRect << -1.;
+ QTest::newRow("AlignBottom 99") << 99 << QQuickTableView::AlignBottom << 50. << subRect << -1.;
+
+ QTest::newRow("AlignCenter 0") << 0 << QQuickTableView::AlignCenter << 0. << subRect << 0.;
+ QTest::newRow("AlignCenter 1") << 1 << QQuickTableView::AlignCenter << 0. << subRect << 0.;
+ QTest::newRow("AlignCenter 1") << 1 << QQuickTableView::AlignCenter << 0. << subRect << 50.;
+ QTest::newRow("AlignCenter 0") << 0 << QQuickTableView::AlignCenter << 0. << subRect << -1.;
+ QTest::newRow("AlignCenter 99") << 99 << QQuickTableView::AlignCenter << 0. << subRect << -1.;
+
+ QTest::newRow("AlignCenter 0") << 0 << QQuickTableView::AlignCenter << -10. << subRect << 0.;
+ QTest::newRow("AlignCenter 1") << 1 << QQuickTableView::AlignCenter << -10. << subRect << 0.;
+ QTest::newRow("AlignCenter 1") << 1 << QQuickTableView::AlignCenter << -10. << subRect << 50.;
+ QTest::newRow("AlignCenter 0") << 0 << QQuickTableView::AlignCenter << -10. << subRect << -1.;
+ QTest::newRow("AlignCenter 99") << 99 << QQuickTableView::AlignCenter << -10. << subRect << -1.;
+ }
+}
+
+void tst_QQuickTableView::positionViewAtRowClamped()
+{
+ // Check that positionViewAtRow actually flicks the table to the
+ // right position so that the row becomes visible. For this test, we
+ // only test cells that cannot be placed exactly at the given alignment,
+ // because it would cause the table to overshoot. Instead the
+ // table should be flicked to the edge of the viewport, close to the
+ // requested alignment.
+ QFETCH(int, row);
+ QFETCH(QQuickTableView::PositionModeFlag, alignment);
+ QFETCH(qreal, offset);
+ QFETCH(QRectF, subRect);
+ QFETCH(qreal, contentYStartPos);
+
+ LOAD_TABLEVIEW("plaintableview.qml");
+ auto model = TestModelAsVariant(100, 100);
+ tableView->setModel(model);
+ if (contentYStartPos >= 0)
+ tableView->setContentY(contentYStartPos);
+
+ if (!tableView->isRowLoaded(row))
+ WAIT_UNTIL_POLISHED;
+
+ tableView->positionViewAtRow(row, alignment, offset, subRect);
+
+ if (!tableView->isRowLoaded(row))
+ WAIT_UNTIL_POLISHED;
+
+ QCOMPARE(tableView->contentY(), row < 50 ? 0 : tableView->contentHeight() - tableView->height());
+}
+
+void tst_QQuickTableView::positionViewAtColumnClamped_data()
+{
+ QTest::addColumn<int>("column");
+ QTest::addColumn<QQuickTableView::PositionModeFlag>("alignment");
+ QTest::addColumn<qreal>("offset");
+ QTest::addColumn<QRectF>("subRect");
+ QTest::addColumn<qreal>("contentXStartPos");
+
+ QRectF subRects[] = { QRectF(), QRectF(1, 2, 3, 4) };
+
+ for (auto subRect : subRects) {
+ QTest::newRow("AlignLeft 0") << 0 << QQuickTableView::AlignLeft << -10. << subRect << 0.;
+ QTest::newRow("AlignLeft 0") << 0 << QQuickTableView::AlignLeft << -10. << subRect << -1.;
+ QTest::newRow("AlignLeft 99") << 99 << QQuickTableView::AlignLeft << 0. << subRect << -1.;
+ QTest::newRow("AlignLeft 99") << 99 << QQuickTableView::AlignLeft << -10. << subRect << -1.;
+
+ QTest::newRow("AlignRight 0") << 0 << QQuickTableView::AlignRight << 0. << subRect << 0.;
+ QTest::newRow("AlignRight 1") << 1 << QQuickTableView::AlignRight << 0. << subRect << 0.;
+ QTest::newRow("AlignRight 1") << 1 << QQuickTableView::AlignRight << 0. << subRect << 50.;
+ QTest::newRow("AlignRight 0") << 0 << QQuickTableView::AlignRight << 0. << subRect << -1.;
+
+ QTest::newRow("AlignRight 0") << 0 << QQuickTableView::AlignRight << 10. << subRect << 0.;
+ QTest::newRow("AlignRight 1") << 1 << QQuickTableView::AlignRight << 10. << subRect << 0.;
+ QTest::newRow("AlignRight 1") << 1 << QQuickTableView::AlignRight << 10. << subRect << 50.;
+ QTest::newRow("AlignRight 0") << 0 << QQuickTableView::AlignRight << 10. << subRect << -1.;
+ QTest::newRow("AlignRight 99") << 99 << QQuickTableView::AlignRight << 100. << subRect << -1.;
+
+ QTest::newRow("AlignCenter 0") << 0 << QQuickTableView::AlignCenter << 0. << subRect << 0.;
+ QTest::newRow("AlignCenter 1") << 1 << QQuickTableView::AlignCenter << 0. << subRect << 0.;
+ QTest::newRow("AlignCenter 1") << 1 << QQuickTableView::AlignCenter << 0. << subRect << 50.;
+ QTest::newRow("AlignCenter 0") << 0 << QQuickTableView::AlignCenter << 0. << subRect << -1.;
+ QTest::newRow("AlignCenter 99") << 99 << QQuickTableView::AlignCenter << 0. << subRect << -1.;
+
+ QTest::newRow("AlignCenter 0") << 0 << QQuickTableView::AlignCenter << -10. << subRect << 0.;
+ QTest::newRow("AlignCenter 1") << 1 << QQuickTableView::AlignCenter << -10. << subRect << 0.;
+ QTest::newRow("AlignCenter 1") << 1 << QQuickTableView::AlignCenter << -10. << subRect << 50.;
+ QTest::newRow("AlignCenter 0") << 0 << QQuickTableView::AlignCenter << -10. << subRect << -1.;
+ QTest::newRow("AlignCenter 99") << 99 << QQuickTableView::AlignCenter << -10. << subRect << -1.;
+ }
+}
+
+void tst_QQuickTableView::positionViewAtColumnClamped()
+{
+ // Check that positionViewAtColumn actually flicks the table to the
+ // right position so that the column becomes visible. For this test, we
+ // only test cells that cannot be placed exactly at the given alignment,
+ // because it would cause the table to overshoot. Instead the
+ // table should be flicked to the edge of the viewport, close to the
+ // requested alignment.
+ QFETCH(int, column);
+ QFETCH(QQuickTableView::PositionModeFlag, alignment);
+ QFETCH(qreal, offset);
+ QFETCH(QRectF, subRect);
+ QFETCH(qreal, contentXStartPos);
+
+ LOAD_TABLEVIEW("plaintableview.qml");
+ auto model = TestModelAsVariant(100, 100);
+ tableView->setModel(model);
+ if (contentXStartPos >= 0)
+ tableView->setContentX(contentXStartPos);
+
+ WAIT_UNTIL_POLISHED;
+
+ tableView->positionViewAtColumn(column, alignment, offset, subRect);
+
+ if (!tableView->isColumnLoaded(column))
+ WAIT_UNTIL_POLISHED;
+
+ QCOMPARE(tableView->contentX(), column < 50 ? 0 : tableView->contentWidth() - tableView->width());
+}
+
+void tst_QQuickTableView::positionViewAtCellWithAnimation()
+{
+ // Check that when we flick to already loaded cell in the
+ // table, this will start the animation, and the view will
+ // be position correctly after the expected duration.
+
+ LOAD_TABLEVIEW("plaintableview.qml");
+ auto model = TestModelAsVariant(100, 100);
+ tableView->setModel(model);
+ tableView->setAnimate(true);
+
+ WAIT_UNTIL_POLISHED;
+
+ QPoint cell(tableView->rightColumn(), tableView->bottomRow());
+ const QRectF cellGeometry = tableViewPrivate->loadedTableItem(cell)->geometry();
+ const int serializedIndex = tableViewPrivate->modelIndexAtCell(cell);
+ const QModelIndex index = tableView->index(cell.y(), cell.x());
+
+ QVERIFY(tableViewPrivate->loadedItems.contains(serializedIndex));
+ QVERIFY(!tableViewPrivate->positionXAnimation.isRunning());
+ QVERIFY(!tableViewPrivate->positionYAnimation.isRunning());
+
+ // Animate the cell to the top left location in the view
+ tableView->positionViewAtIndex(index, QQuickTableView::AlignTop | QQuickTableView::AlignLeft);
+
+ // Wait for animation to finish
+ QVERIFY(tableViewPrivate->positionXAnimation.isRunning());
+ QVERIFY(tableViewPrivate->positionYAnimation.isRunning());
+ QTRY_COMPARE(tableViewPrivate->positionXAnimation.isRunning(), false);
+ QTRY_COMPARE(tableViewPrivate->positionYAnimation.isRunning(), false);
+
+ // Check that the cell is now placed in the top left corner
+ QVERIFY(tableViewPrivate->loadedItems.contains(serializedIndex));
+ QPointF expectedPos = tableView->mapToItem(tableView->contentItem(), QPointF(0, 0));
+ QCOMPARE(cellGeometry.x(), expectedPos.x());
+ QCOMPARE(cellGeometry.y(), expectedPos.y());
+
+ // Animate the cell to the top right location in the view
+ tableView->positionViewAtIndex(index, QQuickTableView::AlignTop | QQuickTableView::AlignRight);
+
+ // Wait for animation to finish
+ QVERIFY(tableViewPrivate->positionXAnimation.isRunning());
+ QVERIFY(!tableViewPrivate->positionYAnimation.isRunning());
+ QTRY_COMPARE(tableViewPrivate->positionXAnimation.isRunning(), false);
+
+ // Check that the cell is now placed in the top right corner
+ QVERIFY(tableViewPrivate->loadedItems.contains(serializedIndex));
+ expectedPos = tableView->mapToItem(tableView->contentItem(), QPointF(tableView->width(), 0));
+ QCOMPARE(cellGeometry.right(), expectedPos.x());
+ QCOMPARE(cellGeometry.y(), expectedPos.y());
+
+ // Animate the cell to the bottom left location in the view
+ tableView->positionViewAtIndex(index, QQuickTableView::AlignBottom | QQuickTableView::AlignLeft);
+
+ // Wait for animation to finish
+ QVERIFY(tableViewPrivate->positionXAnimation.isRunning());
+ QVERIFY(tableViewPrivate->positionYAnimation.isRunning());
+ QTRY_COMPARE(tableViewPrivate->positionXAnimation.isRunning(), false);
+ QTRY_COMPARE(tableViewPrivate->positionYAnimation.isRunning(), false);
+
+ // Check that the cell is now placed in the bottom left corner
+ QVERIFY(tableViewPrivate->loadedItems.contains(serializedIndex));
+ expectedPos = tableView->mapToItem(tableView->contentItem(), QPointF(0, tableView->height()));
+ QCOMPARE(cellGeometry.x(), expectedPos.x());
+ QCOMPARE(cellGeometry.bottom(), expectedPos.y());
+
+ // Animate the cell to the bottom right location in the view
+ tableView->positionViewAtCell(cell, QQuickTableView::AlignBottom | QQuickTableView::AlignRight);
+
+ // Wait for animation to finish
+ QVERIFY(tableViewPrivate->positionXAnimation.isRunning());
+ QVERIFY(!tableViewPrivate->positionYAnimation.isRunning());
+ QTRY_COMPARE(tableViewPrivate->positionXAnimation.isRunning(), false);
+
+ // Check that the cell is now placed in the bottom right corner
+ QVERIFY(tableViewPrivate->loadedItems.contains(serializedIndex));
+ expectedPos = tableView->mapToItem(tableView->contentItem(), QPointF(tableView->width(), tableView->height()));
+ QCOMPARE(cellGeometry.right(), expectedPos.x());
+ QCOMPARE(cellGeometry.bottom(), expectedPos.y());
+}
+
+void tst_QQuickTableView::positionViewAtCell_VisibleAndContain_data()
+{
+ QTest::addColumn<QPoint>("cell");
+ QTest::addColumn<QQuickTableView::PositionModeFlag>("mode");
+ QTest::addColumn<QPointF>("offset");
+
+ QTest::newRow("99, 99, Contain") << QPoint{99, 99} << QQuickTableView::Contain << QPointF{0, 0};
+ QTest::newRow("0, 0, Contain") << QPoint{0, 0} << QQuickTableView::Contain << QPointF{0, 0};
+ QTest::newRow("5, 0, Contain") << QPoint{5, 0} << QQuickTableView::Contain << QPointF{0, 0};
+ QTest::newRow("0, 7, Contain") << QPoint{0, 7} << QQuickTableView::Contain << QPointF{0, 0};
+ QTest::newRow("1, 1, Contain") << QPoint{1, 1} << QQuickTableView::Contain << QPointF{0, 0};
+ QTest::newRow("10, 10, Contain") << QPoint{10, 10} << QQuickTableView::Contain << QPointF{0, 0};
+
+ QTest::newRow("99, 99, Visible") << QPoint{99, 99} << QQuickTableView::Visible << QPointF{0, 0};
+ QTest::newRow("0, 0, Visible") << QPoint{0, 0} << QQuickTableView::Visible << QPointF{0, 0};
+ QTest::newRow("5, 1, Visible") << QPoint{5, 1} << QQuickTableView::Visible << QPointF{0, 0};
+ QTest::newRow("1, 7, Visible") << QPoint{1, 7} << QQuickTableView::Visible << QPointF{0, 0};
+ QTest::newRow("1, 1, Visible") << QPoint{1, 1} << QQuickTableView::Visible << QPointF{0, 0};
+ QTest::newRow("10, 10, Visible") << QPoint{10, 10} << QQuickTableView::Visible << QPointF{0, 0};
+
+ QTest::newRow("99, 99, Contain, margins") << QPoint{99, 99} << QQuickTableView::Contain << QPointF{10, 10};
+ QTest::newRow("0, 0, Contain, margins") << QPoint{0, 0} << QQuickTableView::Contain << QPointF{10, 10};
+ QTest::newRow("5, 0, Contain, margins") << QPoint{5, 1} << QQuickTableView::Contain << QPointF{10, 10};
+ QTest::newRow("1, 7, Contain, margins") << QPoint{1, 7} << QQuickTableView::Contain << QPointF{10, 10};
+ QTest::newRow("1, 1, Contain, margins") << QPoint{1, 1} << QQuickTableView::Contain << QPointF{10, 10};
+ QTest::newRow("10, 10, Contain, margins") << QPoint{10, 10} << QQuickTableView::Contain << QPointF{10, 10};
+}
+
+void tst_QQuickTableView::positionViewAtCell_VisibleAndContain()
+{
+ // Check that the PositionModes "Visible" and "Contain" works according
+ // to the documentation.
+ QFETCH(QPoint, cell);
+ QFETCH(QQuickTableView::PositionModeFlag, mode);
+ QFETCH(QPointF, offset);
+
+ LOAD_TABLEVIEW("plaintableview.qml");
+ auto model = TestModelAsVariant(100, 100);
+ tableView->setModel(model);
+ tableView->setAnimate(true);
+
+ WAIT_UNTIL_POLISHED;
+
+ const bool cellIsVisible = tableView->itemAtCell(cell) != nullptr;
+ bool cellIsCompletelyVisible = false;
+ if (cellIsVisible) {
+ const QRectF cellRect = tableViewPrivate->loadedTableItem(cell)->geometry();
+ QRectF viewportRect = tableViewPrivate->viewportRect;
+ viewportRect.adjust(offset.x(), offset.y(), -offset.x(), -offset.y());
+ cellIsCompletelyVisible = viewportRect.contains(cellRect);
+ }
+
+ tableView->positionViewAtCell(cell, mode, offset);
+
+ if (cellIsCompletelyVisible || (cellIsVisible && mode == QQuickTableView::Visible)) {
+ // Nothing to do!
+ QVERIFY(!tableViewPrivate->positionXAnimation.isRunning());
+ QVERIFY(!tableViewPrivate->positionYAnimation.isRunning());
+ QVERIFY(!QQuickTest::qIsPolishScheduled(tableView));
+ } else if (cellIsVisible) {
+ // TableView will scroll towards the cell, unless it'a already at the correct place
+ QTRY_COMPARE(tableViewPrivate->positionXAnimation.isRunning(), false);
+ QTRY_COMPARE(tableViewPrivate->positionYAnimation.isRunning(), false);
+ } else {
+ // TableView will rebuild on top of the cell
+ QVERIFY(!tableViewPrivate->positionXAnimation.isRunning());
+ QVERIFY(!tableViewPrivate->positionYAnimation.isRunning());
+ WAIT_UNTIL_POLISHED;
+ }
+
+ QVERIFY(tableView->itemAtCell(cell));
+}
+
+void tst_QQuickTableView::positionViewAtCell_VisibleAndContain_SubRect_data()
+{
+ QTest::addColumn<QPoint>("cell");
+ QTest::addColumn<QQuickTableView::PositionModeFlag>("mode");
+ QTest::addColumn<QRectF>("subRect");
+ QTest::addColumn<QPointF>("contentStartPos");
+
+ QRectF subRects[] = { QRectF(0, 0, 10, 10),
+ QRectF(10, 10, 10, 10),
+ QRectF(80, 30, 10, 10),
+ QRectF(90, 40, 10, 10),
+ QRectF(0, 0, 100, 50) };
+
+ for (auto subRect : subRects) {
+ QTest::newRow("99, 99, Contain") << QPoint{99, 99} << QQuickTableView::Contain << subRect << QPointF(0, 0);
+ QTest::newRow("0, 0, Contain") << QPoint{0, 0} << QQuickTableView::Contain << subRect << QPointF(0, 0);
+ QTest::newRow("0, 0, Contain, start: 50, 25") << QPoint{0, 0} << QQuickTableView::Contain << subRect << QPointF(50, 25);
+ QTest::newRow("5, 0, Contain") << QPoint{5, 0} << QQuickTableView::Contain << subRect << QPointF(0, 0);
+ QTest::newRow("0, 7, Contain") << QPoint{0, 7} << QQuickTableView::Contain << subRect << QPointF(0, 0);
+ QTest::newRow("5, 7, Contain, start: -50, -25") << QPoint{5, 7} << QQuickTableView::Contain << subRect << QPointF(-50, -25);
+
+ QTest::newRow("99, 99, Visible") << QPoint{99, 99} << QQuickTableView::Visible << subRect << QPointF(0, 0);
+ QTest::newRow("0, 0, Visible") << QPoint{0, 0} << QQuickTableView::Visible << subRect << QPointF(0, 0);
+ QTest::newRow("0, 0, Visible, start: 50, 25") << QPoint{0, 0} << QQuickTableView::Visible << subRect << QPointF(50, 25);
+ QTest::newRow("5, 1, Visible") << QPoint{5, 1} << QQuickTableView::Visible << subRect << QPointF(0, 0);
+ QTest::newRow("1, 7, Visible") << QPoint{1, 7} << QQuickTableView::Visible << subRect << QPointF(0, 0);
+ QTest::newRow("5, 7, Visible, start: -50, -25") << QPoint{5, 7} << QQuickTableView::Visible << subRect << QPointF(-50, -25);
+ }
+}
+
+void tst_QQuickTableView::positionViewAtCell_VisibleAndContain_SubRect()
+{
+ // Check that the PositionModes "Visible" and "Contain" works when using subrects
+ QFETCH(QPoint, cell);
+ QFETCH(QQuickTableView::PositionModeFlag, mode);
+ QFETCH(QRectF, subRect);
+ QFETCH(QPointF, contentStartPos);
+
+ LOAD_TABLEVIEW("plaintableview.qml");
+ auto model = TestModelAsVariant(100, 100);
+ tableView->setModel(model);
+ tableView->setAnimate(true);
+ tableView->setContentX(contentStartPos.x());
+ tableView->setContentY(contentStartPos.y());
+
+ WAIT_UNTIL_POLISHED;
+
+ const bool cellIsVisible = tableView->itemAtCell(cell) != nullptr;
+ bool subRectIsVisible = false;
+ bool subRectIsContained = false;
+
+ if (cellIsVisible) {
+ const QRectF cellRect = tableViewPrivate->loadedTableItem(cell)->geometry();
+ const QRectF alignmentRect = subRect.translated(cellRect.topLeft());
+ const QRectF viewportRect = tableViewPrivate->viewportRect;
+ subRectIsVisible = viewportRect.intersects(alignmentRect);
+ subRectIsContained = viewportRect.contains(alignmentRect);
+ }
+
+ tableView->positionViewAtCell(cell, mode, QPointF(), subRect);
+
+ if (cellIsVisible) {
+ if ((mode == QQuickTableView::Visible && subRectIsVisible) ||
+ (mode == QQuickTableView::Contain && subRectIsContained)) {
+ // The mode is already fulfilled, so verify that no animation (or rebuild) runs
+ QVERIFY(!tableViewPrivate->positionXAnimation.isRunning());
+ QVERIFY(!tableViewPrivate->positionYAnimation.isRunning());
+ QVERIFY(!QQuickTest::qIsPolishScheduled(tableView));
+ } else {
+ // TableView will scroll towards the cell
+ QVERIFY(tableViewPrivate->positionXAnimation.isRunning()
+ || tableViewPrivate->positionYAnimation.isRunning());
+ QTRY_VERIFY(!tableViewPrivate->positionXAnimation.isRunning()
+ && !tableViewPrivate->positionYAnimation.isRunning());
+ }
+ } else {
+ // The cell is not loaded, so TableView will rebuild
+ WAIT_UNTIL_POLISHED;
+ }
+
+ // Check that the subRect is now visible inside the viewport, according to the mode
+ QVERIFY(tableView->itemAtCell(cell));
+ const QRectF cellRectAfterPositioning = tableViewPrivate->loadedTableItem(cell)->geometry();
+ const QRectF alignmentRectAfterPositioning = subRect.translated(cellRectAfterPositioning.topLeft());
+ const QRectF viewportRect = tableViewPrivate->viewportRect;
+
+ if (mode == QQuickTableView::Visible)
+ QVERIFY(viewportRect.intersects(alignmentRectAfterPositioning));
+ else // QQuickTableView::Contain
+ QVERIFY(viewportRect.contains(alignmentRectAfterPositioning));
+}
+
+void tst_QQuickTableView::positionViewAtCellForLargeCells_data()
+{
+ QTest::addColumn<qreal>("cellSize");
+
+ QTest::newRow("200") << 200.;
+ QTest::newRow("800") << 800.;
+}
+
+void tst_QQuickTableView::positionViewAtCellForLargeCells()
+{
+ // Position the view on a cell outside the viewport. When the cells are larger
+ // than the viewport, check that TableView.Contain will place the cell top-left.
+ // When the cells are smaller than the viewport, it should be placed bottom-right.
+ QFETCH(qreal, cellSize);
+
+ LOAD_TABLEVIEW("plaintableview.qml");
+
+ auto model = TestModelAsVariant(10, 10);
+ tableView->setModel(model);
+
+ view->rootObject()->setProperty("delegateWidth", cellSize);
+ view->rootObject()->setProperty("delegateHeight", cellSize);
+
+ WAIT_UNTIL_POLISHED;
+
+ const QPoint cell(5, 5);
+ tableView->positionViewAtCell(cell, QQuickTableView::Contain);
+
+ WAIT_UNTIL_POLISHED;
+
+ const QQuickItem *item = tableView->itemAtCell(cell);
+ QVERIFY(item);
+
+ QPointF expectedPos;
+ if (cellSize > tableView->width()) {
+ expectedPos = tableView->mapToItem(tableView->contentItem(), QPointF(0, 0));
+ } else {
+ expectedPos = tableView->mapToItem(tableView->contentItem(), QPointF(tableView->width(), tableView->height()));
+ expectedPos -= QPointF(item->width(), item->height());
+ }
+
+ QCOMPARE(item->x(), expectedPos.x());
+ QCOMPARE(item->y(), expectedPos.y());
+}
+
+void tst_QQuickTableView::positionViewAtCellForLargeCellsUsingSubrect()
+{
+ // Position the view on a cell outside the viewport. When a cell is larger
+ // than the viewport, TableView.Contain will normally place it top-left.
+ // But if we specify a subRect that is close to the bottom-right edge (and
+ // the subRect is smaller than the viewport), TableView should align
+ // bottom-right of the subRect instead.
+ LOAD_TABLEVIEW("plaintableview.qml");
+
+ auto model = TestModelAsVariant(10, 10);
+ tableView->setModel(model);
+ tableView->setAnimate(false);
+
+ const qreal cellSize = 800;
+ view->rootObject()->setProperty("delegateWidth", cellSize);
+ view->rootObject()->setProperty("delegateHeight", cellSize);
+
+ WAIT_UNTIL_POLISHED;
+
+ const QRectF subRect(cellSize - 100, cellSize - 100, 10, 10);
+ tableView->positionViewAtCell(QPoint(0, 0), QQuickTableView::Contain, QPointF(), subRect);
+ QCOMPARE(tableView->contentX(), -(tableView->width() - subRect.right()));
+ QCOMPARE(tableView->contentY(), -(tableView->height() - subRect.bottom()));
+}
+
+void tst_QQuickTableView::positionViewAtLastRow_data()
+{
+ QTest::addColumn<QString>("signalToTest");
+ QTest::addColumn<bool>("animate");
+
+ QTest::newRow("positionOnRowsChanged, animate=false") << "positionOnRowsChanged" << false;
+ QTest::newRow("positionOnRowsChanged, animate=true") << "positionOnRowsChanged" << true;
+ QTest::newRow("positionOnContentHeightChanged, animate=false") << "positionOnContentHeightChanged" << false;
+ QTest::newRow("positionOnContentHeightChanged, animate=true") << "positionOnContentHeightChanged" << true;
+}
+
+void tst_QQuickTableView::positionViewAtLastRow()
+{
+ // Check that we can make TableView always scroll to the
+ // last row in the model by positioning the view upon
+ // a rowsChanged callback
+ QFETCH(QString, signalToTest);
+ QFETCH(bool, animate);
+
+ LOAD_TABLEVIEW("positionlast.qml");
+
+ // Use a very large model to indirectly test that we "fast-flick" to
+ // the end at start-up (instead of loading and unloading rows, which
+ // would take forever).
+ TestModel model(2000000, 2000000);
+ tableView->setModel(QVariant::fromValue(&model));
+ tableView->setAnimate(animate);
+
+ view->rootObject()->setProperty(signalToTest.toUtf8().constData(), true);
+
+ WAIT_UNTIL_POLISHED;
+
+ const qreal delegateSize = 100.;
+ const qreal viewportRowCount = tableView->height() / delegateSize;
+
+ // Check that the viewport is positioned at the last row at start-up
+ QCOMPARE(tableView->rows(), model.rowCount());
+ QCOMPARE(tableView->bottomRow(), model.rowCount() - 1);
+ QCOMPARE(tableView->contentY(), (model.rowCount() - viewportRowCount) * delegateSize);
+
+ // Check that the viewport is positioned at the last
+ // row after more rows are added.
+ for (int row = 0; row < 2; ++row) {
+ model.addRow(model.rowCount() - 1);
+
+ WAIT_UNTIL_POLISHED;
+
+ if (tableView->animate()) {
+ QVERIFY(tableViewPrivate->positionYAnimation.isRunning());
+ QTRY_VERIFY(!tableViewPrivate->positionYAnimation.isRunning());
+ }
+
+ QCOMPARE(tableView->rows(), model.rowCount());
+ QCOMPARE(tableView->bottomRow(), model.rowCount() - 1);
+ QCOMPARE(tableView->contentY(), (model.rowCount() - viewportRowCount) * delegateSize);
+ }
+}
+
+void tst_QQuickTableView::positionViewAtLastColumn_data()
+{
+ QTest::addColumn<QString>("signalToTest");
+ QTest::addColumn<bool>("animate");
+
+ QTest::newRow("positionOnColumnsChanged, animate=false") << "positionOnColumnsChanged" << false;
+ QTest::newRow("positionOnColumnsChanged, animate=true") << "positionOnColumnsChanged" << true;
+ QTest::newRow("positionOnContentWidthChanged, animate=false") << "positionOnContentWidthChanged" << false;
+ QTest::newRow("positionOnContentWidthChanged, animate=true") << "positionOnContentWidthChanged" << true;
+}
+
+void tst_QQuickTableView::positionViewAtLastColumn()
+{
+ // Check that we can make TableView always scroll to the
+ // last column in the model by positioning the view upon
+ // a columnsChanged callback
+ QFETCH(QString, signalToTest);
+ QFETCH(bool, animate);
+
+ LOAD_TABLEVIEW("positionlast.qml");
+
+ // Use a very large model to indirectly test that we "fast-flick" to
+ // the end at start-up (instead of loading and unloading columns, which
+ // would take forever).
+ TestModel model(2000000, 2000000);
+ tableView->setModel(QVariant::fromValue(&model));
+ tableView->setAnimate(animate);
+
+ view->rootObject()->setProperty(signalToTest.toUtf8().constData(), true);
+
+ WAIT_UNTIL_POLISHED;
+
+ const qreal delegateSize = 100.;
+ const qreal viewportColumnCount = tableView->width() / delegateSize;
+
+ // Check that the viewport is positioned at the last column at start-up
+ QCOMPARE(tableView->columns(), model.columnCount());
+ QCOMPARE(tableView->rightColumn(), model.columnCount() - 1);
+ QCOMPARE(tableView->contentX(), (model.columnCount() - viewportColumnCount) * delegateSize);
+
+ // Check that the viewport is positioned at the last
+ // column after more columns are added.
+ for (int column = 0; column < 2; ++column) {
+ model.addColumn(model.columnCount() - 1);
+
+ WAIT_UNTIL_POLISHED;
+
+ if (tableView->animate()) {
+ QVERIFY(tableViewPrivate->positionXAnimation.isRunning());
+ QTRY_VERIFY(!tableViewPrivate->positionXAnimation.isRunning());
+ }
+
+ QCOMPARE(tableView->columns(), model.columnCount());
+ QCOMPARE(tableView->rightColumn(), model.columnCount() - 1);
+ QCOMPARE(tableView->contentX(), (model.columnCount() - viewportColumnCount) * delegateSize);
+ }
+}
+
void tst_QQuickTableView::itemAtCell_data()
{
QTest::addColumn<QPoint>("cell");
@@ -3190,10 +4318,37 @@ void tst_QQuickTableView::leftRightTopBottomProperties()
QCOMPARE(tableView->rightColumn(), expectedTable.right());
QCOMPARE(tableView->bottomRow(), expectedTable.bottom());
- QCOMPARE(leftSpy.count(), expectedSignalCount.left());
- QCOMPARE(rightSpy.count(), expectedSignalCount.right());
- QCOMPARE(topSpy.count(), expectedSignalCount.top());
- QCOMPARE(bottomSpy.count(), expectedSignalCount.bottom());
+ QCOMPARE(leftSpy.size(), expectedSignalCount.left());
+ QCOMPARE(rightSpy.size(), expectedSignalCount.right());
+ QCOMPARE(topSpy.size(), expectedSignalCount.top());
+ QCOMPARE(bottomSpy.size(), expectedSignalCount.bottom());
+}
+
+void tst_QQuickTableView::leftRightTopBottomUpdatedBeforeSignalEmission()
+{
+ // Check that leftColumn, rightColumn, topRow and bottomRow are
+ // actually updated before the changed signals are emitted.
+ LOAD_TABLEVIEW("plaintableview.qml");
+ auto model = TestModelAsVariant(100, 100);
+ tableView->setModel(model);
+
+ WAIT_UNTIL_POLISHED;
+
+ connect(tableView, &QQuickTableView::leftColumnChanged, [=]{
+ QCOMPARE(tableView->leftColumn(), 1);
+ });
+ connect(tableView, &QQuickTableView::rightColumnChanged, [=]{
+ QCOMPARE(tableView->rightColumn(), 6);
+ });
+ connect(tableView, &QQuickTableView::topRowChanged, [=]{
+ QCOMPARE(tableView->topRow(), 1);
+ });
+ connect(tableView, &QQuickTableView::bottomRowChanged, [=]{
+ QCOMPARE(tableView->bottomRow(), 8);
+ });
+
+ tableView->setContentX(100);
+ tableView->setContentY(50);
}
void tst_QQuickTableView::checkContentSize_data()
@@ -3266,6 +4421,3133 @@ void tst_QQuickTableView::checkContentSize()
QCOMPARE(tableView->contentHeight(), rowCount == 0 ? 0 : (rowCount * (delegateHeight + rowSpacing)) - rowSpacing);
}
+void tst_QQuickTableView::checkSelectionModelWithRequiredSelectedProperty_data()
+{
+ QTest::addColumn<QVector<QPoint>>("selected");
+ QTest::addColumn<QPoint>("toggle");
+
+ QTest::newRow("nothing selected") << QVector<QPoint>() << QPoint(0,0);
+ QTest::newRow("one item selected") << (QVector<QPoint>() << QPoint(0, 0)) << QPoint(1, 1);
+ QTest::newRow("two items selected") << (QVector<QPoint>() << QPoint(1, 1) << QPoint(2, 2)) << QPoint(1, 1);
+}
+
+void tst_QQuickTableView::checkSelectionModelWithRequiredSelectedProperty()
+{
+ // Check that if you add a "required property selected" to the delegate,
+ // TableView will give it a value upon creation that matches the state
+ // in the selection model.
+ QFETCH(QVector<QPoint>, selected);
+ QFETCH(QPoint, toggle);
+
+ LOAD_TABLEVIEW("tableviewwithselected1.qml");
+
+ TestModel model(10, 10);
+ QItemSelectionModel selectionModel(&model);
+
+ // Set initially selected cells
+ for (auto it = selected.constBegin(); it != selected.constEnd(); ++it) {
+ const QPoint &cell = *it;
+ selectionModel.select(model.index(cell.y(), cell.x()), QItemSelectionModel::Select);
+ }
+
+ tableView->setModel(QVariant::fromValue(&model));
+ tableView->setSelectionModel(&selectionModel);
+
+ WAIT_UNTIL_POLISHED;
+
+ // Check that all delegates have "selected" set with the initial value
+ for (auto fxItem : tableViewPrivate->loadedItems) {
+ const auto context = qmlContext(fxItem->item.data());
+ const int row = context->contextProperty("row").toInt();
+ const int column = context->contextProperty("column").toInt();
+ const bool selected = fxItem->item->property("selected").toBool();
+ const auto modelIndex = model.index(row, column);
+ QCOMPARE(selected, selectionModel.isSelected(modelIndex));
+ }
+
+ // Toggle selected on one of the model indices, and check
+ // that the "selected" property got updated as well
+ const QModelIndex toggleIndex = model.index(toggle.y(), toggle.x());
+ const bool wasSelected = selectionModel.isSelected(toggleIndex);
+ selectionModel.select(toggleIndex, QItemSelectionModel::Toggle);
+ const auto fxItem = tableViewPrivate->loadedTableItem(toggle);
+ const bool isSelected = fxItem->item->property("selected").toBool();
+ QCOMPARE(isSelected, !wasSelected);
+}
+
+void tst_QQuickTableView::checkSelectionModelWithUnrequiredSelectedProperty()
+{
+ // Check that if there is a property "selected" in the delegate, but it's
+ // not required, then TableView will not touch it. This is for legacy reasons, to
+ // not break applications written before Qt 6.2 that has such a property
+ // added for application logic.
+ LOAD_TABLEVIEW("tableviewwithselected2.qml");
+
+ TestModel model(10, 10);
+ tableView->setModel(QVariant::fromValue(&model));
+ QItemSelectionModel *selectionModel = tableView->selectionModel();
+ QVERIFY(selectionModel);
+
+ // Select a cell
+ selectionModel->select(model.index(1, 1), QItemSelectionModel::Select);
+
+ WAIT_UNTIL_POLISHED;
+
+ const auto fxItem = tableViewPrivate->loadedTableItem(QPoint(1, 1));
+ const bool selected = fxItem->item->property("selected").toBool();
+ QCOMPARE(selected, false);
+}
+
+void tst_QQuickTableView::removeAndAddSelectionModel()
+{
+ // Check that if we remove the selection model from TableView, all delegates
+ // will be unselected. And opposite, if we add the selection model back, the
+ // delegates will be updated.
+ LOAD_TABLEVIEW("tableviewwithselected1.qml");
+
+ TestModel model(10, 10);
+ QItemSelectionModel selectionModel(&model);
+
+ // Select a cell in the selection model
+ selectionModel.select(model.index(1, 1), QItemSelectionModel::Select);
+
+ tableView->setModel(QVariant::fromValue(&model));
+ tableView->setSelectionModel(&selectionModel);
+
+ WAIT_UNTIL_POLISHED;
+
+ // Check that the delegate item is selected
+ const auto fxItem = tableViewPrivate->loadedTableItem(QPoint(1, 1));
+ bool selected = fxItem->item->property("selected").toBool();
+ QCOMPARE(selected, true);
+
+ // Remove the selection model, and check that the delegate item is now unselected
+ tableView->setSelectionModel(nullptr);
+ selected = fxItem->item->property("selected").toBool();
+ QCOMPARE(selected, false);
+
+ // Add the selection model back, and check that the delegate item is selected again
+ tableView->setSelectionModel(&selectionModel);
+ selected = fxItem->item->property("selected").toBool();
+ QCOMPARE(selected, true);
+}
+
+void tst_QQuickTableView::warnOnWrongModelInSelectionModel()
+{
+ // The model set on the SelectionModel should always match the model
+ // set on TableView. This is normally handled automatically, but it's
+ // possible to circumvent. This test will check that we warn if that happens.
+ LOAD_TABLEVIEW("tableviewwithselected1.qml");
+
+ TestModel model1(10, 10);
+ TestModel model2(10, 10);
+
+ tableView->setModel(QVariant::fromValue(&model1));
+ QItemSelectionModel selectionModel;
+ tableView->setSelectionModel(&selectionModel);
+
+ // Set a different model
+ selectionModel.setModel(&model2);
+
+ // And change currentIndex. This will produce a warning.
+ QTest::ignoreMessage(QtWarningMsg, QRegularExpression(".*TableView.selectionModel.model.*"));
+ selectionModel.setCurrentIndex(model2.index(0, 0), QItemSelectionModel::NoUpdate);
+}
+
+void tst_QQuickTableView::selectionBehaviorCells_data()
+{
+ QTest::addColumn<QPoint>("endCellDist");
+
+ QTest::newRow("single cell") << QPoint(0, 0);
+
+ QTest::newRow("left to right") << QPoint(1, 0);
+ QTest::newRow("left to right") << QPoint(2, 0);
+ QTest::newRow("right to left") << QPoint(-1, 0);
+ QTest::newRow("right to left") << QPoint(-2, 0);
+
+ QTest::newRow("top to bottom") << QPoint(0, 1);
+ QTest::newRow("top to bottom") << QPoint(0, 2);
+ QTest::newRow("bottom to top") << QPoint(0, -1);
+ QTest::newRow("bottom to top") << QPoint(0, -2);
+
+ QTest::newRow("diagonal top left to bottom right") << QPoint(1, 1);
+ QTest::newRow("diagonal top left to bottom right") << QPoint(2, 2);
+ QTest::newRow("diagonal bottom left to top right") << QPoint(-1, -1);
+ QTest::newRow("diagonal bottom left to top right") << QPoint(-2, -2);
+ QTest::newRow("diagonal top right to bottom left") << QPoint(-1, 1);
+ QTest::newRow("diagonal top right to bottom left") << QPoint(-2, 2);
+ QTest::newRow("diagonal bottom right to top left") << QPoint(1, -1);
+ QTest::newRow("diagonal bottom right to top left") << QPoint(2, -2);
+}
+
+void tst_QQuickTableView::selectionBehaviorCells()
+{
+ // Check that the TableView implement QQuickSelectableInterface startSelection,
+ // setSelectionStartPos, setSelectionEndPos and clearSelection correctly. Do this by
+ // calling setSelectionStartPos/setSelectionEndPos on top of different cells, and see
+ // that we end up with the expected selections.
+ QFETCH(QPoint, endCellDist);
+ LOAD_TABLEVIEW("tableviewwithselected1.qml");
+
+ TestModel model(10, 10);
+ QItemSelectionModel selectionModel(&model);
+
+ tableView->setModel(QVariant::fromValue(&model));
+ tableView->setSelectionModel(&selectionModel);
+
+ WAIT_UNTIL_POLISHED;
+
+ QCOMPARE(selectionModel.hasSelection(), false);
+ QCOMPARE(tableView->selectionBehavior(), QQuickTableView::SelectCells);
+
+ const QPoint startCell(5, 5);
+ const QPoint endCell = startCell + endCellDist;
+ const QPoint endCellWrapped = startCell - endCellDist;
+
+ const QQuickItem *startItem = tableView->itemAtCell(startCell);
+ const QQuickItem *endItem = tableView->itemAtCell(endCell);
+ const QQuickItem *endItemWrapped = tableView->itemAtCell(endCellWrapped);
+ QVERIFY(startItem);
+ QVERIFY(endItem);
+ QVERIFY(endItemWrapped);
+
+ const QPointF startPos(startItem->x(), startItem->y());
+ const QPointF endPos(endItem->x(), endItem->y());
+ const QPointF endPosWrapped(endItemWrapped->x(), endItemWrapped->y());
+
+ QVERIFY(tableViewPrivate->startSelection(startPos, Qt::NoModifier));
+ tableViewPrivate->setSelectionStartPos(startPos);
+ tableViewPrivate->setSelectionEndPos(endPos);
+
+ QCOMPARE(selectionModel.hasSelection(), true);
+
+ const int x1 = qMin(startCell.x(), endCell.x());
+ const int x2 = qMax(startCell.x(), endCell.x());
+ const int y1 = qMin(startCell.y(), endCell.y());
+ const int y2 = qMax(startCell.y(), endCell.y());
+
+ for (int x = x1; x < x2; ++x) {
+ for (int y = y1; y < y2; ++y) {
+ const auto index = model.index(y, x);
+ QVERIFY(selectionModel.isSelected(index));
+ }
+ }
+
+ const int expectedCount = (x2 - x1 + 1) * (y2 - y1 + 1);
+ const int actualCount = selectionModel.selectedIndexes().size();
+ QCOMPARE(actualCount, expectedCount);
+
+ // Wrap the selection
+ tableViewPrivate->setSelectionEndPos(endPosWrapped);
+
+ for (int x = x2; x < x1; ++x) {
+ for (int y = y2; y < y1; ++y) {
+ const auto index = model.index(y, x);
+ QVERIFY(selectionModel.isSelected(index));
+ }
+ }
+
+ const int actualCountAfterWrap = selectionModel.selectedIndexes().size();
+ QCOMPARE(actualCountAfterWrap, expectedCount);
+
+ tableViewPrivate->clearSelection();
+ QCOMPARE(selectionModel.hasSelection(), false);
+}
+
+void tst_QQuickTableView::selectionBehaviorRows()
+{
+ // Check that the TableView implement QQuickSelectableInterface startSelection,
+ // setSelectionStartPos, setSelectionEndPos and clearSelection correctly for
+ // QQuickTableView::SelectRows.
+ LOAD_TABLEVIEW("tableviewwithselected1.qml");
+
+ TestModel model(10, 10);
+ QItemSelectionModel selectionModel(&model);
+
+ tableView->setModel(QVariant::fromValue(&model));
+ tableView->setSelectionModel(&selectionModel);
+ tableView->setSelectionBehavior(QQuickTableView::SelectRows);
+
+ WAIT_UNTIL_POLISHED;
+
+ QCOMPARE(selectionModel.hasSelection(), false);
+
+ // Drag from row 0 to row 3
+ QVERIFY(tableViewPrivate->startSelection(QPointF(0, 0), Qt::NoModifier));
+ tableViewPrivate->setSelectionStartPos(QPointF(0, 0));
+ tableViewPrivate->setSelectionEndPos(QPointF(60, 60));
+
+ QCOMPARE(selectionModel.hasSelection(), true);
+
+ const int expectedCount = 10 * 3; // all columns * three rows
+ int actualCount = selectionModel.selectedIndexes().size();
+ QCOMPARE(actualCount, expectedCount);
+
+ for (int x = 0; x < tableView->columns(); ++x) {
+ for (int y = 0; y < 3; ++y) {
+ const auto index = model.index(y, x);
+ QVERIFY(selectionModel.isSelected(index));
+ }
+ }
+
+ selectionModel.clear();
+ QCOMPARE(selectionModel.hasSelection(), false);
+
+ // Drag from row 3 to row 0 (and overshoot mouse)
+ QVERIFY(tableViewPrivate->startSelection(QPointF(60, 60), Qt::NoModifier));
+ tableViewPrivate->setSelectionStartPos(QPointF(60, 60));
+ tableViewPrivate->setSelectionEndPos(QPointF(-10, -10));
+
+ QCOMPARE(selectionModel.hasSelection(), true);
+
+ actualCount = selectionModel.selectedIndexes().size();
+ QCOMPARE(actualCount, expectedCount);
+
+ for (int x = 0; x < tableView->columns(); ++x) {
+ for (int y = 0; y < 3; ++y) {
+ const auto index = model.index(y, x);
+ QVERIFY(selectionModel.isSelected(index));
+ }
+ }
+}
+
+void tst_QQuickTableView::selectionBehaviorColumns()
+{
+ // Check that the TableView implement QQuickSelectableInterface startSelection,
+ // setSelectionStartPos, setSelectionEndPos and clearSelection correctly for
+ // QQuickTableView::SelectColumns.
+ LOAD_TABLEVIEW("tableviewwithselected1.qml");
+
+ TestModel model(10, 10);
+ QItemSelectionModel selectionModel(&model);
+
+ tableView->setModel(QVariant::fromValue(&model));
+ tableView->setSelectionModel(&selectionModel);
+ tableView->setSelectionBehavior(QQuickTableView::SelectColumns);
+
+ WAIT_UNTIL_POLISHED;
+
+ QCOMPARE(selectionModel.hasSelection(), false);
+
+ // Drag from column 0 to column 3
+ QVERIFY(tableViewPrivate->startSelection(QPointF(60, 60), Qt::NoModifier));
+ tableViewPrivate->setSelectionStartPos(QPointF(0, 0));
+ tableViewPrivate->setSelectionEndPos(QPointF(60, 60));
+
+ QCOMPARE(selectionModel.hasSelection(), true);
+
+ const int expectedCount = 10 * 3; // all rows * three columns
+ int actualCount = selectionModel.selectedIndexes().size();
+ QCOMPARE(actualCount, expectedCount);
+
+ for (int x = 0; x < 3; ++x) {
+ for (int y = 0; y < tableView->rows(); ++y) {
+ const auto index = model.index(y, x);
+ QVERIFY(selectionModel.isSelected(index));
+ }
+ }
+
+ selectionModel.clear();
+ QCOMPARE(selectionModel.hasSelection(), false);
+
+ // Drag from column 3 to column 0 (and overshoot mouse)
+ QVERIFY(tableViewPrivate->startSelection(QPointF(60, 60), Qt::NoModifier));
+ tableViewPrivate->setSelectionStartPos(QPointF(60, 60));
+ tableViewPrivate->setSelectionEndPos(QPointF(-10, -10));
+
+ QCOMPARE(selectionModel.hasSelection(), true);
+
+ actualCount = selectionModel.selectedIndexes().size();
+ QCOMPARE(actualCount, expectedCount);
+
+ for (int x = 0; x < 3; ++x) {
+ for (int y = 0; y < tableView->rows(); ++y) {
+ const auto index = model.index(y, x);
+ QVERIFY(selectionModel.isSelected(index));
+ }
+ }
+}
+
+void tst_QQuickTableView::selectionBehaviorDisabled()
+{
+ // Check that the TableView implement QQuickSelectableInterface startSelection
+ // correctly for QQuickTableView::SelectionDisabled.
+ LOAD_TABLEVIEW("tableviewwithselected1.qml");
+
+ TestModel model(10, 10);
+ QItemSelectionModel selectionModel(&model);
+
+ tableView->setModel(QVariant::fromValue(&model));
+ tableView->setSelectionModel(&selectionModel);
+ tableView->setSelectionBehavior(QQuickTableView::SelectionDisabled);
+
+ WAIT_UNTIL_POLISHED;
+
+ QCOMPARE(selectionModel.hasSelection(), false);
+
+ // Try to start a selection
+ QTest::ignoreMessage(QtWarningMsg, QRegularExpression(".*TableView.SelectionDisabled"));
+ QVERIFY(!tableViewPrivate->startSelection(QPointF(0, 0), Qt::NoModifier));
+
+ QCOMPARE(selectionModel.hasSelection(), false);
+}
+
+void tst_QQuickTableView::testSelectableStartPosEndPosOutsideView()
+{
+ // Call startSelection, setSelectionStartPos and setSelectionEndPos with positions
+ // outside the view. This should first of all not crash, but instead just clamp the
+ // selection to the cells that are visible inside the view.
+ LOAD_TABLEVIEW("tableviewwithselected1.qml");
+
+ TestModel model(10, 10);
+ QItemSelectionModel selectionModel(&model);
+
+ tableView->setModel(QVariant::fromValue(&model));
+ tableView->setSelectionModel(&selectionModel);
+
+ WAIT_UNTIL_POLISHED;
+
+ const QPoint centerCell(5, 5);
+ const QQuickItem *centerItem = tableView->itemAtCell(centerCell);
+ QVERIFY(centerItem);
+
+ const QPointF centerPos(centerItem->x(), centerItem->y());
+ const QPointF outsideLeft(-100, centerPos.y());
+ const QPointF outsideRight(tableView->width() + 100, centerPos.y());
+ const QPointF outsideTop(centerPos.x(), -100);
+ const QPointF outsideBottom(centerPos.x(), tableView->height() + 100);
+
+ QVERIFY(tableViewPrivate->startSelection(centerPos, Qt::NoModifier));
+ tableViewPrivate->setSelectionStartPos(centerPos);
+
+ tableViewPrivate->setSelectionEndPos(outsideLeft);
+ for (int x = 0; x <= centerCell.x(); ++x) {
+ const auto index = model.index(centerCell.y(), x);
+ QVERIFY(selectionModel.isSelected(index));
+ }
+
+ tableViewPrivate->setSelectionEndPos(outsideRight);
+ for (int x = centerCell.x(); x < model.columnCount(); ++x) {
+ const auto index = model.index(centerCell.y(), x);
+ QVERIFY(selectionModel.isSelected(index));
+ }
+
+ tableViewPrivate->setSelectionEndPos(outsideTop);
+ for (int y = 0; y <= centerCell.y(); ++y) {
+ const auto index = model.index(y, centerCell.x());
+ QVERIFY(selectionModel.isSelected(index));
+ }
+
+ tableViewPrivate->setSelectionEndPos(outsideBottom);
+ for (int y = centerCell.y(); y < model.rowCount(); ++y) {
+ const auto index = model.index(y, centerCell.x());
+ QVERIFY(selectionModel.isSelected(index));
+ }
+}
+
+void tst_QQuickTableView::testSelectableScrollTowardsPos()
+{
+ // Check that TableView will implement the scrollTowardsSelectionPoint function
+ // correctly, and move the content item towards the given position
+ LOAD_TABLEVIEW("tableviewwithselected1.qml");
+
+ TestModel model(200, 200);
+ QItemSelectionModel selectionModel(&model);
+
+ tableView->setModel(QVariant::fromValue(&model));
+ tableView->setSelectionModel(&selectionModel);
+
+ WAIT_UNTIL_POLISHED;
+
+ QCOMPARE(tableView->contentX(), 0);
+ QCOMPARE(tableView->contentY(), 0);
+
+ const QSizeF step(1, 1);
+ const QPointF topLeft(-100, -100);
+ const QPointF topRight(tableView->width() + 100, -100);
+ const QPointF bottomLeft(-100, tableView->height() + 100);
+ const QPointF bottomRight(tableView->width() + 100, tableView->height() + 100);
+
+ tableViewPrivate->scrollTowardsSelectionPoint(topRight, step);
+ QCOMPARE(tableView->contentX(), step.width());
+ QCOMPARE(tableView->contentY(), 0);
+
+ tableViewPrivate->scrollTowardsSelectionPoint(bottomRight, step);
+ QCOMPARE(tableView->contentX(), step.width() * 2);
+ QCOMPARE(tableView->contentY(), step.height());
+
+ tableViewPrivate->scrollTowardsSelectionPoint(bottomLeft, step);
+ QCOMPARE(tableView->contentX(), step.width());
+ QCOMPARE(tableView->contentY(), step.height() * 2);
+
+ tableViewPrivate->scrollTowardsSelectionPoint(topLeft, step);
+ QCOMPARE(tableView->contentX(), 0);
+ QCOMPARE(tableView->contentY(), step.height());
+
+ tableViewPrivate->scrollTowardsSelectionPoint(topLeft, step);
+ QCOMPARE(tableView->contentX(), 0);
+ QCOMPARE(tableView->contentY(), 0);
+}
+
+void tst_QQuickTableView::setCurrentIndexFromSelectionModel()
+{
+ LOAD_TABLEVIEW("tableviewwithselected1.qml");
+
+ TestModel model(40, 40);
+ QItemSelectionModel selectionModel(&model);
+
+ tableView->setModel(QVariant::fromValue(&model));
+ tableView->setSelectionModel(&selectionModel);
+ tableView->setFocus(true);
+ const char kCurrent[] = "current";
+
+ WAIT_UNTIL_POLISHED;
+
+ // Check that all delegates have current set to false upon start
+ for (auto fxItem : tableViewPrivate->loadedItems)
+ QVERIFY(!fxItem->item->property(kCurrent).toBool());
+
+ // Start by making cell 0, 0 current
+ const QPoint cell0_0(0, 0);
+ selectionModel.setCurrentIndex(tableView->modelIndex(cell0_0), QItemSelectionModel::NoUpdate);
+ QVERIFY(tableView->itemAtCell(cell0_0)->property(kCurrent).toBool());
+
+ // Move currentIndex to a cell outside the viewport by accessing the selection
+ // model directly, scroll to it, and check current status.
+ const QPoint cellAtEnd(tableView->columns() - 1, tableView->rows() - 1);
+ selectionModel.setCurrentIndex(tableView->modelIndex(cellAtEnd), QItemSelectionModel::NoUpdate);
+ QVERIFY(!tableView->itemAtCell(cell0_0)->property(kCurrent).toBool());
+
+ tableView->positionViewAtCell(cellAtEnd, QQuickTableView::AlignBottom | QQuickTableView::AlignRight);
+ WAIT_UNTIL_POLISHED;
+ QVERIFY(tableView->itemAtCell(cellAtEnd));
+ QVERIFY(tableView->itemAtCell(cellAtEnd)->property(kCurrent).toBool());
+}
+
+void tst_QQuickTableView::clearSelectionOnTap_data()
+{
+ QTest::addColumn<bool>("selectionEnabled");
+ QTest::newRow("selections enabled") << true;
+ QTest::newRow("selections disabled") << false;
+}
+
+void tst_QQuickTableView::clearSelectionOnTap()
+{
+ // Check that we clear the current selection when tapping
+ // inside TableView. But only if TableView has selections
+ // enabled. Otherwise, TableView should not touch the selection model.
+ QFETCH(bool, selectionEnabled);
+ LOAD_TABLEVIEW("tableviewwithselected2.qml");
+
+ TestModel model(40, 40);
+ tableView->setModel(QVariant::fromValue(&model));
+ if (!selectionEnabled)
+ tableView->setSelectionBehavior(QQuickTableView::SelectionDisabled);
+
+ WAIT_UNTIL_POLISHED;
+
+ // Select root item
+ const auto index = tableView->selectionModel()->model()->index(0, 0);
+ tableView->selectionModel()->select(index, QItemSelectionModel::Select);
+ QCOMPARE(tableView->selectionModel()->selectedIndexes().size(), 1);
+
+ // Click on a cell
+ const auto item = tableView->itemAtIndex(tableView->index(0, 0));
+ QVERIFY(item);
+ QPoint localPos = QPoint(item->width() / 2, item->height() / 2);
+ QPoint pos = item->window()->contentItem()->mapFromItem(item, localPos).toPoint();
+ QTest::mouseClick(item->window(), Qt::LeftButton, Qt::NoModifier, pos);
+
+ QCOMPARE(tableView->selectionModel()->hasSelection(), !selectionEnabled);
+}
+
+void tst_QQuickTableView::moveCurrentIndexUsingArrowKeys()
+{
+ LOAD_TABLEVIEW("tableviewwithselected1.qml");
+
+ TestModel model(40, 40);
+ QItemSelectionModel selectionModel(&model);
+
+ tableView->setModel(QVariant::fromValue(&model));
+ tableView->setSelectionModel(&selectionModel);
+ tableView->setFocus(true);
+ QQuickWindow *window = tableView->window();
+ const char kCurrent[] = "current";
+
+ WAIT_UNTIL_POLISHED;
+
+ // Check that all delegates have current set to false upon start
+ for (auto fxItem : tableViewPrivate->loadedItems)
+ QVERIFY(!fxItem->item->property(kCurrent).toBool());
+
+ QCOMPARE(tableView->currentColumn(), -1);
+ QCOMPARE(tableView->currentRow(), -1);
+
+ // Start by making cell 0, 0 current
+ const QPoint cell0_0(0, 0);
+ selectionModel.setCurrentIndex(tableView->modelIndex(cell0_0), QItemSelectionModel::NoUpdate);
+ QVERIFY(tableView->itemAtCell(cell0_0)->property(kCurrent).toBool());
+
+ // Trying to move the index out of the table with the keys should be a no-op:
+ QTest::keyPress(window, Qt::Key_Left);
+ QVERIFY(tableView->itemAtCell(cell0_0)->property(kCurrent).toBool());
+ QCOMPARE(tableView->currentColumn(), cell0_0.x());
+ QCOMPARE(tableView->currentRow(), cell0_0.y());
+ QCOMPARE(selectionModel.currentIndex(), tableView->modelIndex(cell0_0));
+ QTest::keyPress(window, Qt::Key_Up);
+ QVERIFY(tableView->itemAtCell(cell0_0)->property(kCurrent).toBool());
+ QCOMPARE(tableView->currentColumn(), cell0_0.x());
+ QCOMPARE(tableView->currentRow(), cell0_0.y());
+
+ // Move currentIndex right
+ const QPoint cell1_0(1, 0);
+ QTest::keyPress(window, Qt::Key_Right);
+ QCOMPARE(selectionModel.currentIndex(), tableView->modelIndex(cell1_0));
+ QVERIFY(!tableView->itemAtCell(cell0_0)->property(kCurrent).toBool());
+ QVERIFY(tableView->itemAtCell(cell1_0)->property(kCurrent).toBool());
+ QCOMPARE(tableView->currentColumn(), cell1_0.x());
+ QCOMPARE(tableView->currentRow(), cell1_0.y());
+
+ // Move currentIndex left
+ QTest::keyPress(window, Qt::Key_Left);
+ QCOMPARE(selectionModel.currentIndex(), tableView->modelIndex(cell0_0));
+ QVERIFY(tableView->itemAtCell(cell0_0)->property(kCurrent).toBool());
+ QVERIFY(!tableView->itemAtCell(cell1_0)->property(kCurrent).toBool());
+ QCOMPARE(tableView->currentColumn(), cell0_0.x());
+ QCOMPARE(tableView->currentRow(), cell0_0.y());
+
+ // Move currentIndex down
+ const QPoint cell0_1(0, 1);
+ QTest::keyPress(window, Qt::Key_Down);
+ QCOMPARE(selectionModel.currentIndex(), tableView->modelIndex(cell0_1));
+ QVERIFY(!tableView->itemAtCell(cell0_0)->property(kCurrent).toBool());
+ QVERIFY(tableView->itemAtCell(cell0_1)->property(kCurrent).toBool());
+ QCOMPARE(tableView->currentColumn(), cell0_1.x());
+ QCOMPARE(tableView->currentRow(), cell0_1.y());
+
+ // Move currentIndex up
+ QTest::keyPress(window, Qt::Key_Up);
+ QCOMPARE(selectionModel.currentIndex(), tableView->modelIndex(cell0_0));
+ QVERIFY(tableView->itemAtCell(cell0_0)->property(kCurrent).toBool());
+ QVERIFY(!tableView->itemAtCell(cell0_1)->property(kCurrent).toBool());
+ QCOMPARE(tableView->currentColumn(), cell0_0.x());
+ QCOMPARE(tableView->currentRow(), cell0_0.y());
+}
+
+void tst_QQuickTableView::moveCurrentIndexUsingHomeAndEndKeys()
+{
+ LOAD_TABLEVIEW("tableviewwithselected1.qml");
+
+ TestModel model(40, 40);
+ QItemSelectionModel selectionModel(&model);
+
+ tableView->setModel(QVariant::fromValue(&model));
+ tableView->setSelectionModel(&selectionModel);
+ tableView->setFocus(true);
+ QQuickWindow *window = tableView->window();
+ const char kCurrent[] = "current";
+
+ WAIT_UNTIL_POLISHED;
+
+ // Check that all delegates have current set to false upon start
+ for (auto fxItem : tableViewPrivate->loadedItems)
+ QVERIFY(!fxItem->item->property(kCurrent).toBool());
+
+ QCOMPARE(tableView->currentColumn(), -1);
+ QCOMPARE(tableView->currentRow(), -1);
+
+ const QPoint cell0_0(0, 0);
+ const QPoint cellHorEnd(tableView->columns() - 1, 0);
+
+ // Start by making cell 0, 0 current
+ selectionModel.setCurrentIndex(tableView->modelIndex(cell0_0), QItemSelectionModel::NoUpdate);
+ QVERIFY(tableView->itemAtCell(cell0_0)->property(kCurrent).toBool());
+ QCOMPARE(tableView->currentColumn(), cell0_0.x());
+ QCOMPARE(tableView->currentRow(), cell0_0.y());
+
+ // Move currentIndex to end
+ QTest::keyPress(window, Qt::Key_End);
+ QCOMPARE(selectionModel.currentIndex(), tableView->modelIndex(cellHorEnd));
+ QTRY_VERIFY(tableView->itemAtCell(cellHorEnd));
+ QVERIFY(tableView->itemAtCell(cellHorEnd)->property(kCurrent).toBool());
+ QCOMPARE(tableView->currentColumn(), cellHorEnd.x());
+ QCOMPARE(tableView->currentRow(), cellHorEnd.y());
+
+ // Move currentIndex to end once more is a no-op
+ QTest::keyPress(window, Qt::Key_End);
+ QCOMPARE(selectionModel.currentIndex(), tableView->modelIndex(cellHorEnd));
+ QVERIFY(tableView->itemAtCell(cellHorEnd)->property(kCurrent).toBool());
+ QCOMPARE(tableView->currentColumn(), cellHorEnd.x());
+ QCOMPARE(tableView->currentRow(), cellHorEnd.y());
+
+ // Move currentIndex to home
+ QTest::keyPress(window, Qt::Key_Home);
+ QCOMPARE(selectionModel.currentIndex(), tableView->modelIndex(cell0_0));
+ QTRY_VERIFY(tableView->itemAtCell(cell0_0));
+ QVERIFY(tableView->itemAtCell(cell0_0)->property(kCurrent).toBool());
+ QCOMPARE(tableView->currentColumn(), cell0_0.x());
+ QCOMPARE(tableView->currentRow(), cell0_0.y());
+
+ // Move currentIndex to home once more is a no-op
+ QTest::keyPress(window, Qt::Key_Home);
+ QCOMPARE(selectionModel.currentIndex(), tableView->modelIndex(cell0_0));
+ QVERIFY(tableView->itemAtCell(cell0_0)->property(kCurrent).toBool());
+ QCOMPARE(tableView->currentColumn(), cell0_0.x());
+ QCOMPARE(tableView->currentRow(), cell0_0.y());
+}
+
+void tst_QQuickTableView::moveCurrentIndexUsingPageUpDownKeys()
+{
+ LOAD_TABLEVIEW("tableviewwithselected1.qml");
+
+ TestModel model(40, 40);
+ QItemSelectionModel selectionModel(&model);
+
+ tableView->setModel(QVariant::fromValue(&model));
+ tableView->setSelectionModel(&selectionModel);
+ tableView->setFocus(true);
+ QQuickWindow *window = tableView->window();
+ const char kCurrent[] = "current";
+
+ WAIT_UNTIL_POLISHED;
+
+ // Check that all delegates have current set to false upon start
+ for (auto fxItem : tableViewPrivate->loadedItems)
+ QVERIFY(!fxItem->item->property(kCurrent).toBool());
+
+ QCOMPARE(tableView->currentColumn(), -1);
+ QCOMPARE(tableView->currentRow(), -1);
+
+ // Start by making cell 0, 0 current
+ const QPoint cell0_0(0, 0);
+ selectionModel.setCurrentIndex(tableView->modelIndex(cell0_0), QItemSelectionModel::NoUpdate);
+ QVERIFY(tableView->itemAtCell(cell0_0)->property(kCurrent).toBool());
+ QCOMPARE(tableView->currentColumn(), cell0_0.x());
+ QCOMPARE(tableView->currentRow(), cell0_0.y());
+
+ // Move currentIndex page down
+ const QPoint bottomCell(0, tableView->bottomRow());
+ QTest::keyPress(window, Qt::Key_PageDown);
+ QCOMPARE(selectionModel.currentIndex(), tableView->modelIndex(bottomCell));
+ QVERIFY(tableView->itemAtCell(cell0_0));
+ QVERIFY(tableView->itemAtCell(bottomCell));
+ QVERIFY(!tableView->itemAtCell(cell0_0)->property(kCurrent).toBool());
+ QVERIFY(tableView->itemAtCell(bottomCell)->property(kCurrent).toBool());
+ QCOMPARE(tableView->currentColumn(), bottomCell.x());
+ QCOMPARE(tableView->currentRow(), bottomCell.y());
+
+ // Move currentIndex page up
+ QTest::keyPress(window, Qt::Key_PageUp);
+ QCOMPARE(selectionModel.currentIndex(), tableView->modelIndex(cell0_0));
+ QVERIFY(tableView->itemAtCell(cell0_0));
+ QVERIFY(tableView->itemAtCell(bottomCell));
+ QVERIFY(tableView->itemAtCell(cell0_0)->property(kCurrent).toBool());
+ QVERIFY(!tableView->itemAtCell(bottomCell)->property(kCurrent).toBool());
+ QCOMPARE(tableView->currentColumn(), cell0_0.x());
+ QCOMPARE(tableView->currentRow(), cell0_0.y());
+
+ // Move currentIndex page down a second. The second time will cause a fast-flick.
+ const QPoint bottomCellPageTwo(0, 38);
+ QTest::keyPress(window, Qt::Key_PageDown);
+ QTest::keyPress(window, Qt::Key_PageDown);
+ QCOMPARE(selectionModel.currentIndex(), tableView->modelIndex(bottomCellPageTwo));
+ QTRY_VERIFY(tableView->itemAtCell(bottomCellPageTwo));
+ QVERIFY(tableView->itemAtCell(bottomCellPageTwo)->property(kCurrent).toBool());
+ QCOMPARE(tableView->currentColumn(), bottomCellPageTwo.x());
+ QCOMPARE(tableView->currentRow(), bottomCellPageTwo.y());
+
+ // Move currentIndex page down a third time. This will hit the end of the table
+ // before a whole page can be reached.
+ const QPoint cellVerEnd(0, tableView->rows() - 1);
+ QTest::keyPress(window, Qt::Key_PageDown);
+ QCOMPARE(selectionModel.currentIndex(), tableView->modelIndex(cellVerEnd));
+ QTRY_VERIFY(tableView->itemAtCell(cellVerEnd));
+ QVERIFY(tableView->itemAtCell(cellVerEnd)->property(kCurrent).toBool());
+ QCOMPARE(tableView->currentColumn(), cellVerEnd.x());
+ QCOMPARE(tableView->currentRow(), cellVerEnd.y());
+
+ // Move currentIndex page down once more is a no-op
+ QTest::keyPress(window, Qt::Key_PageDown);
+ QVERIFY(tableView->itemAtCell(cellVerEnd)->property(kCurrent).toBool());
+ QCOMPARE(tableView->currentColumn(), cellVerEnd.x());
+ QCOMPARE(tableView->currentRow(), cellVerEnd.y());
+
+ // Move currentIndex page up
+ const QPoint cellTop1(0, tableView->topRow());
+ QTest::keyPress(window, Qt::Key_PageUp);
+ QCOMPARE(selectionModel.currentIndex(), tableView->modelIndex(cellTop1));
+ QVERIFY(tableView->itemAtCell(cellTop1));
+ QVERIFY(tableView->itemAtCell(cellTop1)->property(kCurrent).toBool());
+ QCOMPARE(tableView->currentColumn(), cellTop1.x());
+ QCOMPARE(tableView->currentRow(), cellTop1.y());
+
+ // Move currentIndex page up a second time. This will cause a fast-flick, which
+ // happens to end up on row 1.
+ const QPoint cell0_1(0, 1);
+ QTest::keyPress(window, Qt::Key_PageUp);
+ QCOMPARE(selectionModel.currentIndex(), tableView->modelIndex(cell0_1));
+ QTRY_VERIFY(tableView->itemAtCell(cell0_1));
+ QVERIFY(tableView->itemAtCell(cell0_1)->property(kCurrent).toBool());
+ QCOMPARE(tableView->currentColumn(), cell0_1.x());
+ QCOMPARE(tableView->currentRow(), cell0_1.y());
+
+ // Move currentIndex page up a third time. This will bring the table
+ // all the way to the top.
+ QTest::keyPress(window, Qt::Key_PageUp);
+ QCOMPARE(selectionModel.currentIndex(), tableView->modelIndex(cell0_0));
+ QTRY_VERIFY(tableView->itemAtCell(cell0_0));
+ QVERIFY(tableView->itemAtCell(cell0_0)->property(kCurrent).toBool());
+ QCOMPARE(tableView->currentColumn(), cell0_0.x());
+ QCOMPARE(tableView->currentRow(), cell0_0.y());
+
+ // Move currentIndex page up once more. This will be a no-op.
+ QTest::keyPress(window, Qt::Key_PageUp);
+ QCOMPARE(selectionModel.currentIndex(), tableView->modelIndex(cell0_0));
+ QVERIFY(tableView->itemAtCell(cell0_0)->property(kCurrent).toBool());
+ QCOMPARE(tableView->currentColumn(), cell0_0.x());
+ QCOMPARE(tableView->currentRow(), cell0_0.y());
+
+ // Move currentIndex to a cell outside the viewport by accessing the selection
+ // model directly, scroll to it, and check current status.
+ const QPoint cellAtEnd(tableView->columns() - 1, tableView->rows() - 1);
+ selectionModel.setCurrentIndex(tableView->modelIndex(cellAtEnd), QItemSelectionModel::NoUpdate);
+ tableView->positionViewAtCell(cellAtEnd, QQuickTableView::AlignBottom | QQuickTableView::AlignRight);
+ QTRY_VERIFY(tableView->itemAtCell(cellAtEnd));
+ QVERIFY(tableView->itemAtCell(cellAtEnd)->property(kCurrent).toBool());
+ QCOMPARE(tableView->currentColumn(), cellAtEnd.x());
+ QCOMPARE(tableView->currentRow(), cellAtEnd.y());
+}
+
+void tst_QQuickTableView::moveCurrentIndexUsingTabKey_data()
+{
+ QTest::addColumn<bool>("hide");
+
+ QTest::newRow("all visible") << false;
+ QTest::newRow("some hidden") << true;
+}
+
+void tst_QQuickTableView::moveCurrentIndexUsingTabKey()
+{
+ QFETCH(bool, hide);
+ LOAD_TABLEVIEW("tableviewwithselected1.qml");
+
+ TestModel model(5, 6);
+ QItemSelectionModel selectionModel(&model);
+
+ tableView->setModel(QVariant::fromValue(&model));
+ tableView->setSelectionModel(&selectionModel);
+ tableView->setFocus(true);
+ QQuickWindow *window = tableView->window();
+ const char kCurrent[] = "current";
+
+ int lastRow = 4;
+ int lastColumn = 5;
+
+ if (hide) {
+ // Hide last row and column. Those sections should
+ // no longer be taking into account when tabbing.
+ tableView->setRowHeight(lastRow, 0);
+ tableView->setColumnWidth(lastColumn, 0);
+ lastRow--;
+ lastColumn--;
+ }
+
+ WAIT_UNTIL_POLISHED;
+
+ QVERIFY(tableView->activeFocusOnTab());
+
+ QCOMPARE(tableView->currentColumn(), -1);
+ QCOMPARE(tableView->currentRow(), -1);
+
+ // Start by making cell 0, 0 current
+ const QPoint cell0_0(0, 0);
+ selectionModel.setCurrentIndex(tableView->modelIndex(cell0_0), QItemSelectionModel::NoUpdate);
+ QVERIFY(tableView->itemAtCell(cell0_0)->property(kCurrent).toBool());
+ QCOMPARE(tableView->currentColumn(), cell0_0.x());
+ QCOMPARE(tableView->currentRow(), cell0_0.y());
+
+ // Press Tab
+ const QPoint cell1_0(1, 0);
+ QTest::keyPress(window, Qt::Key_Tab);
+ QCOMPARE(selectionModel.currentIndex(), tableView->modelIndex(cell1_0));
+ QVERIFY(!tableView->itemAtCell(cell0_0)->property(kCurrent).toBool());
+ QVERIFY(tableView->itemAtCell(cell1_0)->property(kCurrent).toBool());
+
+ // Press Backtab
+ QTest::keyPress(window, Qt::Key_Backtab);
+ QCOMPARE(selectionModel.currentIndex(), tableView->modelIndex(cell0_0));
+ QVERIFY(tableView->itemAtCell(cell0_0)->property(kCurrent).toBool());
+ QVERIFY(!tableView->itemAtCell(cell1_0)->property(kCurrent).toBool());
+ QVERIFY(!selectionModel.hasSelection());
+
+ // Press Backtab again. This wraps current index to the
+ // bottom right of the table
+ const QPoint cell_bottomRight(lastColumn, lastRow);
+ QTest::keyPress(window, Qt::Key_Backtab);
+ QCOMPARE(selectionModel.currentIndex(), tableView->modelIndex(cell_bottomRight));
+ QVERIFY(!tableView->itemAtCell(cell0_0)->property(kCurrent).toBool());
+ QVERIFY(tableView->itemAtCell(cell_bottomRight)->property(kCurrent).toBool());
+ QVERIFY(!selectionModel.hasSelection());
+
+ // Press Tab. This wraps current index back to the 0_0
+ QTest::keyPress(window, Qt::Key_Tab);
+ QCOMPARE(selectionModel.currentIndex(), tableView->modelIndex(cell0_0));
+ QVERIFY(tableView->itemAtCell(cell0_0)->property(kCurrent).toBool());
+ QVERIFY(!tableView->itemAtCell(cell_bottomRight)->property(kCurrent).toBool());
+ QVERIFY(!selectionModel.hasSelection());
+
+ // Make 0_1 current, and press Backtab. This should
+ // wrap current index to the last column, but on the row above.
+ const QPoint cell0_1(0, 1);
+ const QPoint cellRightAbove(lastColumn, 0);
+ selectionModel.setCurrentIndex(tableView->modelIndex(cell0_1), QItemSelectionModel::NoUpdate);
+ QVERIFY(!tableView->itemAtCell(cell0_0)->property(kCurrent).toBool());
+ QVERIFY(tableView->itemAtCell(cell0_1)->property(kCurrent).toBool());
+ QTest::keyPress(window, Qt::Key_Backtab);
+ QVERIFY(tableView->itemAtCell(cellRightAbove)->property(kCurrent).toBool());
+ QVERIFY(!tableView->itemAtCell(cell0_1)->property(kCurrent).toBool());
+ QVERIFY(!selectionModel.hasSelection());
+
+ // Press Tab. This wraps current index back to 0_1
+ QTest::keyPress(window, Qt::Key_Tab);
+ QCOMPARE(selectionModel.currentIndex(), tableView->modelIndex(cell0_1));
+ QVERIFY(!tableView->itemAtCell(cellRightAbove)->property(kCurrent).toBool());
+ QVERIFY(tableView->itemAtCell(cell0_1)->property(kCurrent).toBool());
+ QVERIFY(!selectionModel.hasSelection());
+}
+
+void tst_QQuickTableView::respectActiveFocusOnTabDisabled()
+{
+ // Ensure that we don't move focus for tab or backtab
+ // when TableView.setActiveFocusOnTab is false.
+ LOAD_TABLEVIEW("tableviewwithselected1.qml");
+
+ TestModel model(3, 3);
+ QItemSelectionModel selectionModel(&model);
+
+ tableView->setModel(QVariant::fromValue(&model));
+ tableView->setSelectionModel(&selectionModel);
+ tableView->setActiveFocusOnTab(false);
+ tableView->setFocus(true);
+
+ QQuickWindow *window = tableView->window();
+ const char kCurrent[] = "current";
+
+ WAIT_UNTIL_POLISHED;
+
+ QCOMPARE(tableView->currentColumn(), -1);
+ QCOMPARE(tableView->currentRow(), -1);
+ QVERIFY(!tableView->activeFocusOnTab());
+
+ // Start by making cell 0, 0 current
+ const QPoint cell0_0(0, 0);
+ selectionModel.setCurrentIndex(tableView->modelIndex(cell0_0), QItemSelectionModel::NoUpdate);
+ QVERIFY(tableView->itemAtCell(cell0_0)->property(kCurrent).toBool());
+ QCOMPARE(tableView->currentColumn(), cell0_0.x());
+ QCOMPARE(tableView->currentRow(), cell0_0.y());
+
+ // Press Tab
+ const QPoint cell1_0(1, 0);
+ QTest::keyPress(window, Qt::Key_Tab);
+ QCOMPARE(selectionModel.currentIndex(), tableView->modelIndex(cell0_0));
+ QVERIFY(tableView->itemAtCell(cell0_0)->property(kCurrent).toBool());
+ QVERIFY(!tableView->itemAtCell(cell1_0)->property(kCurrent).toBool());
+
+ // Press Backtab
+ QTest::keyPress(window, Qt::Key_Backtab);
+ QCOMPARE(selectionModel.currentIndex(), tableView->modelIndex(cell0_0));
+ QVERIFY(tableView->itemAtCell(cell0_0)->property(kCurrent).toBool());
+}
+
+void tst_QQuickTableView::setCurrentIndexOnFirstKeyPress_data()
+{
+ QTest::addColumn<Qt::Key>("arrowKey");
+
+ QTest::newRow("left") << Qt::Key_Left;
+ QTest::newRow("right") << Qt::Key_Right;
+ QTest::newRow("up") << Qt::Key_Up;
+ QTest::newRow("down") << Qt::Key_Down;
+}
+
+void tst_QQuickTableView::setCurrentIndexOnFirstKeyPress()
+{
+ // Check that TableView has focus, but no cell is current, the
+ // first key press on any of the arrow keys will assign the
+ // top left cell to be current.
+ QFETCH(Qt::Key, arrowKey);
+ LOAD_TABLEVIEW("tableviewwithselected1.qml");
+
+ TestModel model(2, 2);
+ QItemSelectionModel selectionModel(&model);
+
+ tableView->setModel(QVariant::fromValue(&model));
+ tableView->setSelectionModel(&selectionModel);
+ tableView->setFocus(true);
+ QQuickWindow *window = tableView->window();
+ const char kCurrent[] = "current";
+
+ WAIT_UNTIL_POLISHED;
+
+ // Check that all delegates have current set to false upon start
+ for (auto fxItem : tableViewPrivate->loadedItems)
+ QVERIFY(!fxItem->item->property(kCurrent).toBool());
+
+ QCOMPARE(tableView->currentColumn(), -1);
+ QCOMPARE(tableView->currentRow(), -1);
+
+ // Pressing a random key, e.g 'a', should not change current index
+ QTest::keyPress(window, Qt::Key_A);
+ QVERIFY(!selectionModel.currentIndex().isValid());
+ QCOMPARE(tableView->currentColumn(), -1);
+ QCOMPARE(tableView->currentRow(), -1);
+
+ // Press the given arrow key
+ const QPoint topLeftCell(0, 0);
+ QTest::keyPress(window, arrowKey);
+ QCOMPARE(selectionModel.currentIndex(), tableView->modelIndex(topLeftCell));
+ QVERIFY(tableView->itemAtCell(topLeftCell)->property(kCurrent).toBool());
+ QCOMPARE(tableView->currentColumn(), topLeftCell.x());
+ QCOMPARE(tableView->currentRow(), topLeftCell.y());
+}
+
+void tst_QQuickTableView::setCurrentIndexFromMouse()
+{
+ LOAD_TABLEVIEW("tableviewwithselected1.qml");
+
+ TestModel model(40, 40);
+ QItemSelectionModel selectionModel(&model);
+
+ tableView->setModel(QVariant::fromValue(&model));
+ tableView->setSelectionModel(&selectionModel);
+ tableView->setFocus(true);
+ QQuickWindow *window = tableView->window();
+ QQuickItem *contentItem = window->contentItem();
+ const char kCurrent[] = "current";
+
+ WAIT_UNTIL_POLISHED;
+
+ // Check that all delegates have current set to false upon start
+ for (auto fxItem : tableViewPrivate->loadedItems)
+ QVERIFY(!fxItem->item->property(kCurrent).toBool());
+
+ QCOMPARE(tableView->currentColumn(), -1);
+ QCOMPARE(tableView->currentRow(), -1);
+
+ // Click on cell 0, 0
+ const QPoint cell0_0(0, 0);
+ const auto item0_0 = tableView->itemAtCell(cell0_0);
+ QVERIFY(item0_0);
+ QPoint pos = contentItem->mapFromItem(item0_0, QPointF(5, 5)).toPoint();
+ QTest::mouseClick(window, Qt::LeftButton, Qt::NoModifier, pos);
+ QCOMPARE(selectionModel.currentIndex(), tableView->modelIndex(cell0_0));
+ QVERIFY(item0_0->property(kCurrent).toBool());
+ QCOMPARE(tableView->currentColumn(), cell0_0.x());
+ QCOMPARE(tableView->currentRow(), cell0_0.y());
+
+ // Click on cell 1, 2
+ const QPoint cell1_2(1, 2);
+ auto item1_2 = tableView->itemAtCell(cell1_2);
+ QVERIFY(item1_2);
+ pos = contentItem->mapFromItem(item1_2, QPointF(5, 5)).toPoint();
+ QTest::mouseClick(window, Qt::LeftButton, Qt::NoModifier, pos);
+ QCOMPARE(selectionModel.currentIndex(), tableView->modelIndex(cell1_2));
+ QVERIFY(!item0_0->property(kCurrent).toBool());
+ QVERIFY(item1_2->property(kCurrent).toBool());
+ QCOMPARE(tableView->currentColumn(), cell1_2.x());
+ QCOMPARE(tableView->currentRow(), cell1_2.y());
+
+ // Position the view at the end of the table, and click on the bottom-right cell
+ const QPoint cellAtEnd(tableView->columns() - 1, tableView->rows() - 1);
+ tableView->positionViewAtCell(cellAtEnd, QQuickTableView::AlignBottom | QQuickTableView::AlignRight);
+ WAIT_UNTIL_POLISHED;
+ auto itemAtEnd = tableView->itemAtCell(cellAtEnd);
+ QVERIFY(itemAtEnd);
+ QVERIFY(!itemAtEnd->property(kCurrent).toBool());
+ pos = contentItem->mapFromItem(itemAtEnd, QPointF(5, 5)).toPoint();
+ QTest::mouseClick(window, Qt::LeftButton, Qt::NoModifier, pos);
+ QCOMPARE(selectionModel.currentIndex(), tableView->modelIndex(cellAtEnd));
+ QVERIFY(itemAtEnd->property(kCurrent).toBool());
+ QCOMPARE(tableView->currentColumn(), cellAtEnd.x());
+ QCOMPARE(tableView->currentRow(), cellAtEnd.y());
+}
+
+void tst_QQuickTableView::showMarginsWhenNavigatingToEnd()
+{
+ LOAD_TABLEVIEW("plaintableview.qml");
+
+ TestModel model(40, 40);
+ QItemSelectionModel selectionModel(&model);
+
+ tableView->setModel(QVariant::fromValue(&model));
+ tableView->setSelectionModel(&selectionModel);
+ tableView->setAnimate(false);
+ tableView->setFocus(true);
+
+ QQuickWindow *window = tableView->window();
+
+ WAIT_UNTIL_POLISHED;
+
+ const qreal margin = 10;
+ tableView->setLeftMargin(margin);
+ tableView->setRightMargin(margin);
+ tableView->setTopMargin(margin);
+ tableView->setBottomMargin(margin);
+
+ selectionModel.setCurrentIndex(tableView->modelIndex(QPoint(1, 1)), QItemSelectionModel::NoUpdate);
+
+ // move to cell 0, 1
+ QCOMPARE(tableView->contentX(), 0);
+ QTest::keyPress(window, Qt::Key_Left);
+ QCOMPARE(tableView->contentX(), -margin);
+
+ // move to cell 0, 0
+ QCOMPARE(tableView->contentY(), 0);
+ QTest::keyPress(window, Qt::Key_Up);
+ QCOMPARE(tableView->contentY(), -margin);
+
+ selectionModel.setCurrentIndex(tableView->modelIndex(QPoint(38, 38)), QItemSelectionModel::NoUpdate);
+ tableView->positionViewAtCell(tableView->cellAtIndex(selectionModel.currentIndex()), QQuickTableView::Contain);
+
+ WAIT_UNTIL_POLISHED;
+
+ // move to cell 39, 38
+ QTest::keyPress(window, Qt::Key_Right);
+ const qreal cellRightEdge = tableViewPrivate->loadedTableOuterRect.right();
+ QCOMPARE(tableView->contentX(), cellRightEdge + margin - tableView->width());
+
+ // move to cell 39, 39
+ QTest::keyPress(window, Qt::Key_Down);
+ const qreal cellBottomEdge = tableViewPrivate->loadedTableOuterRect.bottom();
+ QCOMPARE(tableView->contentY(), cellBottomEdge + margin - tableView->height());
+}
+
+void tst_QQuickTableView::disablePointerNavigation()
+{
+ LOAD_TABLEVIEW("tableviewwithselected1.qml");
+
+ TestModel model(40, 40);
+ QItemSelectionModel selectionModel(&model);
+
+ tableView->setModel(QVariant::fromValue(&model));
+ tableView->setSelectionModel(&selectionModel);
+ tableView->setFocus(true);
+ tableView->setPointerNavigationEnabled(false);
+ QQuickWindow *window = tableView->window();
+ QQuickItem *contentItem = window->contentItem();
+
+ WAIT_UNTIL_POLISHED;
+
+ QVERIFY(!selectionModel.currentIndex().isValid());
+
+ // Click on cell 0, 0, nothing should happen
+ const QPoint cell0_0(0, 0);
+ const auto item0_0 = tableView->itemAtCell(cell0_0);
+ QVERIFY(item0_0);
+ QPoint pos = contentItem->mapFromItem(item0_0, QPointF(5, 5)).toPoint();
+ QTest::mouseClick(window, Qt::LeftButton, Qt::NoModifier, pos);
+ QVERIFY(!selectionModel.currentIndex().isValid());
+ QVERIFY(!item0_0->property("current").toBool());
+ QCOMPARE(tableView->currentColumn(), -1);
+ QCOMPARE(tableView->currentRow(), -1);
+
+ // Enable navigation, and try again
+ tableView->setPointerNavigationEnabled(true);
+ QTest::mouseClick(window, Qt::LeftButton, Qt::NoModifier, pos);
+ QCOMPARE(selectionModel.currentIndex(), tableView->index(0, 0));
+ QVERIFY(item0_0->property("current").toBool());
+ QCOMPARE(tableView->currentColumn(), cell0_0.x());
+ QCOMPARE(tableView->currentRow(), cell0_0.y());
+
+ // Set an invalid current index in the selection model
+ selectionModel.setCurrentIndex(QModelIndex(), QItemSelectionModel::NoUpdate);
+ QVERIFY(!item0_0->property("current").toBool());
+ QCOMPARE(tableView->currentColumn(), -1);
+ QCOMPARE(tableView->currentRow(), -1);
+}
+
+void tst_QQuickTableView::disableKeyNavigation()
+{
+ LOAD_TABLEVIEW("tableviewwithselected1.qml");
+
+ TestModel model(40, 40);
+ QItemSelectionModel selectionModel(&model);
+
+ tableView->setModel(QVariant::fromValue(&model));
+ tableView->setSelectionModel(&selectionModel);
+ tableView->setKeyNavigationEnabled(false);
+ tableView->setFocus(true);
+ QQuickWindow *window = tableView->window();
+ const char kCurrent[] = "current";
+
+ WAIT_UNTIL_POLISHED;
+
+ // Start by making cell 1, 1 current
+ const QPoint cell1_1(1, 1);
+ selectionModel.setCurrentIndex(tableView->modelIndex(cell1_1), QItemSelectionModel::NoUpdate);
+ QCOMPARE(tableView->itemAtCell(cell1_1)->property(kCurrent).toBool(), true);
+
+ // Try to move currentIndex right by pressing Key_Right. Nothing should happen.
+ const QPoint cell2_1(2, 1);
+ QTest::keyPress(window, Qt::Key_Right);
+ QCOMPARE(selectionModel.currentIndex(), tableView->modelIndex(cell1_1));
+ QVERIFY(tableView->itemAtCell(cell1_1)->property(kCurrent).toBool());
+ QVERIFY(!tableView->itemAtCell(cell2_1)->property(kCurrent).toBool());
+
+ // Enable navigation, and try again
+ tableView->setKeyNavigationEnabled(true);
+ QTest::keyPress(window, Qt::Key_Right);
+ QCOMPARE(selectionModel.currentIndex(), tableView->modelIndex(cell2_1));
+ QVERIFY(!tableView->itemAtCell(cell1_1)->property(kCurrent).toBool());
+ QVERIFY(tableView->itemAtCell(cell2_1)->property(kCurrent).toBool());
+}
+
+void tst_QQuickTableView::selectUsingArrowKeys()
+{
+ // Select cells in the view using the keyboard
+ // by going in a square around cell 1, 1
+ LOAD_TABLEVIEW("tableviewwithselected1.qml");
+
+ TestModel model(40, 40);
+ QItemSelectionModel selectionModel(&model);
+
+ tableView->setModel(QVariant::fromValue(&model));
+ tableView->setSelectionModel(&selectionModel);
+ tableView->setFocus(true);
+ QQuickWindow *window = tableView->window();
+ const char kSelected[] = "selected";
+
+ WAIT_UNTIL_POLISHED;
+
+ // Check that all delegates have selected set to false upon start
+ for (auto fxItem : tableViewPrivate->loadedItems)
+ QCOMPARE(fxItem->item->property(kSelected).toBool(), false);
+
+ // Start by making cell 1, 1 current
+ const QPoint cell1_1(1, 1);
+ selectionModel.setCurrentIndex(tableView->modelIndex(cell1_1), QItemSelectionModel::NoUpdate);
+ QCOMPARE(tableView->itemAtCell(cell1_1)->property(kSelected).toBool(), false);
+
+ // Move currentIndex right while holding down shift to select
+ const QPoint cell2_1(2, 1);
+ QTest::keyPress(window, Qt::Key_Right, Qt::ShiftModifier);
+ QVERIFY(selectionModel.isSelected(tableView->modelIndex(cell1_1)));
+ QVERIFY(selectionModel.isSelected(tableView->modelIndex(cell2_1)));
+ QVERIFY(tableView->itemAtCell(cell1_1)->property(kSelected).toBool());
+ QVERIFY(tableView->itemAtCell(cell2_1)->property(kSelected).toBool());
+
+ // Move currentIndex down while holding down shift to select
+ const QPoint cell2_2(2, 2);
+ const QPoint cell1_2(1, 2);
+ QTest::keyPress(window, Qt::Key_Down, Qt::ShiftModifier);
+ QVERIFY(selectionModel.isSelected(tableView->modelIndex(cell1_1)));
+ QVERIFY(selectionModel.isSelected(tableView->modelIndex(cell2_1)));
+ QVERIFY(selectionModel.isSelected(tableView->modelIndex(cell2_2)));
+ QVERIFY(selectionModel.isSelected(tableView->modelIndex(cell1_2)));
+ QVERIFY(tableView->itemAtCell(cell1_1)->property(kSelected).toBool());
+ QVERIFY(tableView->itemAtCell(cell2_1)->property(kSelected).toBool());
+ QVERIFY(tableView->itemAtCell(cell2_2)->property(kSelected).toBool());
+ QVERIFY(tableView->itemAtCell(cell1_2)->property(kSelected).toBool());
+
+ // Move currentIndex left while holding down shift to select
+ QTest::keyPress(window, Qt::Key_Left, Qt::ShiftModifier);
+ QVERIFY(selectionModel.isSelected(tableView->modelIndex(cell1_1)));
+ QVERIFY(selectionModel.isSelected(tableView->modelIndex(cell1_2)));
+ QVERIFY(!selectionModel.isSelected(tableView->modelIndex(cell2_1)));
+ QVERIFY(!selectionModel.isSelected(tableView->modelIndex(cell2_2)));
+ QVERIFY(tableView->itemAtCell(cell1_1)->property(kSelected).toBool());
+ QVERIFY(tableView->itemAtCell(cell1_2)->property(kSelected).toBool());
+ QVERIFY(!tableView->itemAtCell(cell2_1)->property(kSelected).toBool());
+ QVERIFY(!tableView->itemAtCell(cell2_2)->property(kSelected).toBool());
+
+ // Move currentIndex left while holding down shift to select
+ const QPoint cell0_1(0, 1);
+ const QPoint cell0_2(0, 2);
+ QTest::keyPress(window, Qt::Key_Left, Qt::ShiftModifier);
+ QVERIFY(selectionModel.isSelected(tableView->modelIndex(cell0_1)));
+ QVERIFY(selectionModel.isSelected(tableView->modelIndex(cell0_2)));
+ QVERIFY(selectionModel.isSelected(tableView->modelIndex(cell1_1)));
+ QVERIFY(selectionModel.isSelected(tableView->modelIndex(cell1_2)));
+ QVERIFY(tableView->itemAtCell(cell0_1)->property(kSelected).toBool());
+ QVERIFY(tableView->itemAtCell(cell0_2)->property(kSelected).toBool());
+ QVERIFY(tableView->itemAtCell(cell1_1)->property(kSelected).toBool());
+ QVERIFY(tableView->itemAtCell(cell1_2)->property(kSelected).toBool());
+
+ // Move currentIndex up while holding down shift to select
+ QTest::keyPress(window, Qt::Key_Up, Qt::ShiftModifier);
+ QVERIFY(selectionModel.isSelected(tableView->modelIndex(cell0_1)));
+ QVERIFY(selectionModel.isSelected(tableView->modelIndex(cell1_1)));
+ QVERIFY(!selectionModel.isSelected(tableView->modelIndex(cell0_2)));
+ QVERIFY(!selectionModel.isSelected(tableView->modelIndex(cell1_2)));
+ QVERIFY(tableView->itemAtCell(cell0_1)->property(kSelected).toBool());
+ QVERIFY(tableView->itemAtCell(cell1_1)->property(kSelected).toBool());
+ QVERIFY(!tableView->itemAtCell(cell0_2)->property(kSelected).toBool());
+ QVERIFY(!tableView->itemAtCell(cell1_2)->property(kSelected).toBool());
+
+ // Move currentIndex up while holding down shift to select
+ const QPoint cell0_0(0, 0);
+ const QPoint cell1_0(1, 0);
+ QTest::keyPress(window, Qt::Key_Up, Qt::ShiftModifier);
+ QVERIFY(selectionModel.isSelected(tableView->modelIndex(cell0_0)));
+ QVERIFY(selectionModel.isSelected(tableView->modelIndex(cell0_1)));
+ QVERIFY(selectionModel.isSelected(tableView->modelIndex(cell1_0)));
+ QVERIFY(selectionModel.isSelected(tableView->modelIndex(cell1_1)));
+ QVERIFY(tableView->itemAtCell(cell0_0)->property(kSelected).toBool());
+ QVERIFY(tableView->itemAtCell(cell0_1)->property(kSelected).toBool());
+ QVERIFY(tableView->itemAtCell(cell1_0)->property(kSelected).toBool());
+ QVERIFY(tableView->itemAtCell(cell1_1)->property(kSelected).toBool());
+
+ // Move currentIndex right while holding down shift to select
+ QTest::keyPress(window, Qt::Key_Right, Qt::ShiftModifier);
+ QVERIFY(!selectionModel.isSelected(tableView->modelIndex(cell0_0)));
+ QVERIFY(!selectionModel.isSelected(tableView->modelIndex(cell0_1)));
+ QVERIFY(selectionModel.isSelected(tableView->modelIndex(cell1_0)));
+ QVERIFY(selectionModel.isSelected(tableView->modelIndex(cell1_1)));
+ QVERIFY(!tableView->itemAtCell(cell0_0)->property(kSelected).toBool());
+ QVERIFY(!tableView->itemAtCell(cell0_1)->property(kSelected).toBool());
+ QVERIFY(tableView->itemAtCell(cell1_0)->property(kSelected).toBool());
+ QVERIFY(tableView->itemAtCell(cell1_1)->property(kSelected).toBool());
+
+ // Finally, move currentIndex _without_ shift, which should clear the selection
+ QTest::keyPress(window, Qt::Key_Right);
+ QVERIFY(!selectionModel.isSelected(tableView->modelIndex(cell1_0)));
+ QVERIFY(!selectionModel.isSelected(tableView->modelIndex(cell1_1)));
+ QVERIFY(!tableView->itemAtCell(cell1_0)->property(kSelected).toBool());
+ QVERIFY(!tableView->itemAtCell(cell1_1)->property(kSelected).toBool());
+}
+
+void tst_QQuickTableView::selectUsingHomeAndEndKeys()
+{
+ // Select cells in the view by using the home and end keys
+ LOAD_TABLEVIEW("tableviewwithselected1.qml");
+
+ TestModel model(4, 40);
+ QItemSelectionModel selectionModel(&model);
+
+ tableView->setModel(QVariant::fromValue(&model));
+ tableView->setSelectionModel(&selectionModel);
+ tableView->setFocus(true);
+ QQuickWindow *window = tableView->window();
+ const char kSelected[] = "selected";
+
+ WAIT_UNTIL_POLISHED;
+
+ // Check that all delegates have selected set to false upon start
+ for (auto fxItem : tableViewPrivate->loadedItems)
+ QVERIFY(!fxItem->item->property(kSelected).toBool());
+
+ // Start by making cell 0, 0 current
+ const QPoint cell0_0(0, 0);
+ selectionModel.setCurrentIndex(tableView->modelIndex(cell0_0), QItemSelectionModel::NoUpdate);
+ QVERIFY(!tableView->itemAtCell(cell0_0)->property(kSelected).toBool());
+
+ // Move currentIndex to the end while holding down shift to select
+ const QPoint cellAtHorEnd(tableView->columns() - 1, 0);
+ QTest::keyPress(window, Qt::Key_End, Qt::ShiftModifier);
+ QTRY_VERIFY(tableView->itemAtCell(cellAtHorEnd));
+ for (int c = 0; c <= cellAtHorEnd.x(); ++c) {
+ const QPoint cell(c, cellAtHorEnd.y());
+ QVERIFY(selectionModel.isSelected(tableView->modelIndex(cell)));
+ const QQuickItem *item = tableView->itemAtCell(cell);
+ if (item)
+ QVERIFY(item->property(kSelected).toBool());
+ }
+
+ // Move currentIndex to home while holding down shift to select.
+ // This should result in only the first cell being selected.
+ const QPoint cellAtHome(0, 0);
+ QTest::keyPress(window, Qt::Key_Home, Qt::ShiftModifier);
+ QVERIFY(selectionModel.isSelected(tableView->modelIndex(cellAtHome)));
+ QTRY_VERIFY(tableView->itemAtCell(cellAtHome));
+ QVERIFY(tableView->itemAtCell(cellAtHome)->property(kSelected).toBool());
+ for (int c = 1; c <= cellAtHorEnd.x(); ++c) {
+ const QPoint cell(c, cellAtHorEnd.y());
+ QVERIFY(!selectionModel.isSelected(tableView->modelIndex(cell)));
+ const QQuickItem *item = tableView->itemAtCell(cell);
+ if (item)
+ QVERIFY(!item->property(kSelected).toBool());
+ }
+
+ // Reverse the test, by starting from cellAtHorEnd
+ selectionModel.setCurrentIndex(tableView->modelIndex(cellAtHorEnd), QItemSelectionModel::Clear);
+ tableView->positionViewAtCell(cellAtHorEnd, QQuickTableView::AlignTop | QQuickTableView::AlignRight);
+ WAIT_UNTIL_POLISHED;
+ QQuickItem *itemAtHorEnd = tableView->itemAtCell(cellAtHorEnd);
+ QVERIFY(itemAtHorEnd);
+ QCOMPARE(itemAtHorEnd->property(kSelected).toBool(), false);
+
+ // Move currentIndex home while holding down shift to select
+ QTest::keyPress(window, Qt::Key_Home, Qt::ShiftModifier);
+ QTRY_VERIFY(tableView->itemAtCell(cellAtHome));
+ for (int c = 0; c <= cellAtHorEnd.x(); ++c) {
+ const QPoint cell(c, cellAtHorEnd.y());
+ QVERIFY(selectionModel.isSelected(tableView->modelIndex(cell)));
+ const QQuickItem *item = tableView->itemAtCell(cell);
+ if (item)
+ QVERIFY(item->property(kSelected).toBool());
+ }
+
+ // Move currentIndex to end while holding down shift to select.
+ // This should result in only cellAtHorEnd being selected.
+ QTest::keyPress(window, Qt::Key_End, Qt::ShiftModifier);
+ QVERIFY(selectionModel.isSelected(tableView->modelIndex(cellAtHorEnd)));
+ QTRY_VERIFY(tableView->itemAtCell(cellAtHorEnd));
+ QVERIFY(tableView->itemAtCell(cellAtHorEnd)->property(kSelected).toBool());
+ for (int c = 0; c < cellAtHorEnd.x(); ++c) {
+ const QPoint cell(c, cellAtHorEnd.y());
+ QVERIFY(!selectionModel.isSelected(tableView->modelIndex(cell)));
+ const QQuickItem *item = tableView->itemAtCell(cell);
+ if (item)
+ QVERIFY(!item->property(kSelected).toBool());
+ }
+}
+
+void tst_QQuickTableView::selectUsingPageUpDownKeys()
+{
+ // Select cells in the view by using the page up and down keys
+ LOAD_TABLEVIEW("tableviewwithselected1.qml");
+
+ TestModel model(30, 3);
+ QItemSelectionModel selectionModel(&model);
+
+ tableView->setModel(QVariant::fromValue(&model));
+ tableView->setSelectionModel(&selectionModel);
+ tableView->setFocus(true);
+ QQuickWindow *window = tableView->window();
+ const char kSelected[] = "selected";
+
+ WAIT_UNTIL_POLISHED;
+
+ // Check that all delegates have selected set to false upon start
+ for (auto fxItem : tableViewPrivate->loadedItems)
+ QVERIFY(!fxItem->item->property(kSelected).toBool());
+
+ // Start by making cell 0, 0 current
+ const QPoint cell0_0(0, 0);
+ selectionModel.setCurrentIndex(tableView->modelIndex(cell0_0), QItemSelectionModel::NoUpdate);
+ QVERIFY(!tableView->itemAtCell(cell0_0)->property(kSelected).toBool());
+
+ // Move currentIndex page down while holding down shift to select
+ const QPoint cellAtBottom(0, tableView->bottomRow());
+ QTest::keyPress(window, Qt::Key_PageDown, Qt::ShiftModifier);
+ QVERIFY(tableView->itemAtCell(cellAtBottom));
+ for (int r = 0; r <= cellAtBottom.y(); ++r) {
+ const QPoint cell(cellAtBottom.x(), r);
+ QVERIFY(selectionModel.isSelected(tableView->modelIndex(cell)));
+ const QQuickItem *item = tableView->itemAtCell(cell);
+ if (item)
+ QVERIFY(item->property(kSelected).toBool());
+ }
+
+ // Move currentIndex page up while holding down shift to select
+ const QPoint cellAtTop(0, 0);
+ QTest::keyPress(window, Qt::Key_PageUp, Qt::ShiftModifier);
+ QVERIFY(tableView->itemAtCell(cellAtTop));
+ QVERIFY(tableView->itemAtCell(cellAtTop)->property(kSelected).toBool());
+ for (int r = 1; r <= cellAtBottom.y(); ++r) {
+ const QPoint cell(cellAtBottom.x(), r);
+ QVERIFY(!selectionModel.isSelected(tableView->modelIndex(cell)));
+ const QQuickItem *item = tableView->itemAtCell(cell);
+ if (item)
+ QVERIFY(!item->property(kSelected).toBool());
+ }
+
+ // Move currentIndex page down twice while holding down shift to select.
+ // This will select all cells in the first column, even the ones that are initially hidden.
+ const QPoint cellAtVerEnd(0, tableView->rows() - 1);
+ QTest::keyPress(window, Qt::Key_PageDown, Qt::ShiftModifier);
+ QTest::keyPress(window, Qt::Key_PageDown, Qt::ShiftModifier);
+ QTRY_VERIFY(tableView->itemAtCell(cellAtVerEnd));
+ QCOMPARE(tableView->bottomRow(), cellAtVerEnd.y());
+ for (int r = 0; r <= cellAtBottom.y(); ++r) {
+ const QPoint cell(cellAtBottom.x(), r);
+ QVERIFY(selectionModel.isSelected(tableView->modelIndex(cell)));
+ const QQuickItem *item = tableView->itemAtCell(cell);
+ if (item)
+ QVERIFY(item->property(kSelected).toBool());
+ }
+
+ // Reverse the test, by starting from cellAtVerEnd
+ selectionModel.clearSelection();
+ QVERIFY(!tableView->itemAtCell(cellAtVerEnd)->property(kSelected).toBool());
+
+ // Move currentIndex page up while holding down shift to select
+ const QPoint cellAtTopRow(0, tableView->topRow());
+ QTest::keyPress(window, Qt::Key_PageUp, Qt::ShiftModifier);
+ QTRY_VERIFY(tableView->itemAtCell(cellAtTopRow));
+ for (int r = cellAtTopRow.y(); r <= cellAtVerEnd.y(); ++r) {
+ const QPoint cell(cellAtBottom.x(), r);
+ QVERIFY(selectionModel.isSelected(tableView->modelIndex(cell)));
+ const QQuickItem *item = tableView->itemAtCell(cell);
+ if (item)
+ QVERIFY(item->property(kSelected).toBool());
+ }
+
+ // Move currentIndex page up once more while holding down shift to select.
+ // This will bring currentIndex to the top.
+ QTest::keyPress(window, Qt::Key_PageUp, Qt::ShiftModifier);
+ QTRY_VERIFY(tableView->itemAtCell(cellAtTop));
+ for (int r = cellAtTop.y(); r <= cellAtVerEnd.y(); ++r) {
+ const QPoint cell(cellAtBottom.x(), r);
+ QVERIFY(selectionModel.isSelected(tableView->modelIndex(cell)));
+ const QQuickItem *item = tableView->itemAtCell(cell);
+ if (item)
+ QVERIFY(item->property(kSelected).toBool());
+ }
+}
+
+void tst_QQuickTableView::testDeprecatedApi()
+{
+ // Check that you can still use Qt.Alignment as second argument
+ // to positionViewAtCell() (for backwards compatibility before Qt 6.4)
+ LOAD_TABLEVIEW("deprecatedapi.qml");
+
+ TestModel model(200, 200);
+ QItemSelectionModel selectionModel(&model);
+
+ tableView->setModel(QVariant::fromValue(&model));
+ tableView->setSelectionModel(&selectionModel);
+
+ WAIT_UNTIL_POLISHED;
+
+ QMetaObject::invokeMethod(tableView, "positionUsingDeprecatedEnum");
+
+ WAIT_UNTIL_POLISHED;
+
+ QCOMPARE(tableView->rightColumn(), model.columnCount() - 1);
+ QCOMPARE(tableView->bottomRow(), model.rowCount() - 1);
+}
+
+void tst_QQuickTableView::alternatingRows()
+{
+ // Check that you can set 'alternate'
+ LOAD_TABLEVIEW("plaintableview.qml");
+
+ QVERIFY(tableView->alternatingRows());
+ tableView->setAlternatingRows(false);
+ QVERIFY(!tableView->alternatingRows());
+ tableView->setAlternatingRows(true);
+ QVERIFY(tableView->alternatingRows());
+}
+
+void tst_QQuickTableView::boundDelegateComponent()
+{
+ QQmlEngine engine;
+ const QUrl url(testFileUrl("boundDelegateComponent.qml"));
+ QQmlComponent c(&engine, url);
+ QVERIFY2(c.isReady(), qPrintable(c.errorString()));
+
+ QTest::ignoreMessage(
+ QtWarningMsg, qPrintable(QLatin1String("%1:16: ReferenceError: index is not defined")
+ .arg(url.toString())));
+
+ QScopedPointer<QObject> o(c.create());
+ QVERIFY(!o.isNull());
+
+ QQmlContext *context = qmlContext(o.data());
+
+ QObject *inner = context->objectForName(QLatin1String("tableView"));
+ QVERIFY(inner != nullptr);
+ QQuickTableView *tableView = qobject_cast<QQuickTableView *>(inner);
+ QVERIFY(tableView != nullptr);
+ QObject *item = tableView->itemAtCell({0, 0});
+ QVERIFY(item);
+ QCOMPARE(item->objectName(), QLatin1String("fooouterundefined"));
+
+ QObject *inner2 = context->objectForName(QLatin1String("tableView2"));
+ QVERIFY(inner2 != nullptr);
+ QQuickTableView *tableView2 = qobject_cast<QQuickTableView *>(inner2);
+ QVERIFY(tableView2 != nullptr);
+ QObject *item2 = tableView2->itemAtCell({0, 0});
+ QVERIFY(item2);
+ QCOMPARE(item2->objectName(), QLatin1String("fooouter0"));
+
+ QQmlComponent *comp = qobject_cast<QQmlComponent *>(
+ context->objectForName(QLatin1String("outerComponent")));
+ QVERIFY(comp != nullptr);
+
+ for (int i = 0; i < 3 * 2; ++i) {
+ QTest::ignoreMessage(
+ QtWarningMsg,
+ qPrintable(QLatin1String("%1:54:21: ReferenceError: model is not defined")
+ .arg(url.toString())));
+ }
+
+ QScopedPointer<QObject> outerItem(comp->create(context));
+ QVERIFY(!outerItem.isNull());
+ QQuickTableView *innerTableView = qobject_cast<QQuickTableView *>(
+ qmlContext(outerItem.data())->objectForName(QLatin1String("innerTableView")));
+ QVERIFY(innerTableView != nullptr);
+ QCOMPARE(innerTableView->rows(), 3);
+ for (int i = 0; i < 3; ++i)
+ QVERIFY(innerTableView->itemAtIndex(innerTableView->index(i, 0))->objectName().isEmpty());
+}
+
+void tst_QQuickTableView::setColumnWidth_data()
+{
+ QTest::addColumn<int>("columnCount");
+ QTest::addColumn<int>("column");
+ QTest::addColumn<qreal>("size");
+
+ QTest::newRow("first column") << 5 << 0 << 10.;
+ QTest::newRow("second column") << 5 << 2 << 10.;
+ QTest::newRow("a hidden column") << 20 << 19 << 10.;
+ QTest::newRow("a column outside model") << 1 << 5 << 10.;
+}
+
+void tst_QQuickTableView::setColumnWidth()
+{
+ // Test that you can set the width of a column explicitly
+ QFETCH(int, columnCount);
+ QFETCH(int, column);
+ QFETCH(qreal, size);
+ LOAD_TABLEVIEW("plaintableview.qml");
+
+ auto model = TestModelAsVariant(2, columnCount);
+ tableView->setModel(model);
+
+ WAIT_UNTIL_POLISHED;
+
+ tableView->setColumnWidth(column, size);
+
+ WAIT_UNTIL_POLISHED;
+
+ QCOMPARE(tableView->explicitColumnWidth(column), size);
+ if (tableView->isColumnLoaded(column))
+ QCOMPARE(tableView->columnWidth(column), size);
+ else
+ QCOMPARE(tableView->columnWidth(column), -1);
+}
+
+
+void tst_QQuickTableView::setColumnWidthWhenProviderIsSet()
+{
+ // Test that explicitly set column widths will be
+ // ignored if a columnWidthProvider is set
+ LOAD_TABLEVIEW("userowcolumnprovider.qml");
+
+ auto model = TestModelAsVariant(5, 5);
+ tableView->setModel(model);
+
+ WAIT_UNTIL_POLISHED;
+
+ tableView->setColumnWidth(1, 100);
+
+ WAIT_UNTIL_POLISHED;
+
+ QCOMPARE(tableView->explicitColumnWidth(1), 100);
+ QCOMPARE(tableView->columnWidth(1), 11);
+}
+
+void tst_QQuickTableView::setColumnWidthForInvalidColumn()
+{
+ // Check that you cannot set a column width for
+ // a negative column index.
+ LOAD_TABLEVIEW("plaintableview.qml");
+
+ auto model = TestModelAsVariant(5, 5);
+ tableView->setModel(model);
+
+ WAIT_UNTIL_POLISHED;
+
+ QTest::ignoreMessage(QtWarningMsg, QRegularExpression(".*column must be greather than, or equal to, zero"));
+ tableView->setColumnWidth(-1, 10);
+
+ QCOMPARE(tableView->explicitColumnWidth(-1), -1);
+ QCOMPARE(tableView->columnWidth(-1), -1);
+}
+
+void tst_QQuickTableView::setColumnWidthWhenUsingSyncView()
+{
+ // Test that if you set an explicit column width on a TableView
+ // that has a sync view, then we set the column width on the
+ // sync view instead.
+ LOAD_TABLEVIEW("syncviewsimple.qml");
+ GET_QML_TABLEVIEW(tableViewH);
+ GET_QML_TABLEVIEW(tableViewHV);
+
+ const auto model = TestModelAsVariant(3, 3);
+ QQuickTableView *views[] = {tableView, tableViewH, tableViewHV};
+ for (auto view : views)
+ view->setModel(model);
+
+ const int column = 1;
+ const qreal size = 200;
+
+ tableView->setColumnWidthProvider(QJSValue());
+ tableViewH->setColumnWidth(column, size);
+
+ WAIT_UNTIL_POLISHED;
+
+ for (auto view : views) {
+ QCOMPARE(view->explicitColumnWidth(column), size);
+ QCOMPARE(view->columnWidth(column), size);
+ }
+}
+
+void tst_QQuickTableView::resetColumnWidth()
+{
+ // Check that you can reset a column width
+ // by setting its width to -1
+ LOAD_TABLEVIEW("plaintableview.qml");
+
+ auto model = TestModelAsVariant(5, 5);
+ tableView->setModel(model);
+
+ const int column = 1;
+ const qreal size = 10.;
+ const qreal defaultSize = 100.;
+
+ WAIT_UNTIL_POLISHED;
+
+ tableView->setColumnWidth(column, size);
+
+ WAIT_UNTIL_POLISHED;
+
+ QCOMPARE(tableView->explicitColumnWidth(column), size);
+ QCOMPARE(tableView->columnWidth(column), size);
+
+ tableView->setColumnWidth(column, -1);
+
+ WAIT_UNTIL_POLISHED;
+
+ QCOMPARE(tableView->explicitColumnWidth(column), -1);
+ QCOMPARE(tableView->columnWidth(column), defaultSize);
+}
+
+void tst_QQuickTableView::clearColumnWidths()
+{
+ // Check that clearColumnWidths() works as documented
+ LOAD_TABLEVIEW("plaintableview.qml");
+
+ auto model = TestModelAsVariant(5, 5);
+ tableView->setModel(model);
+
+ const qreal defaultSize = 100.;
+
+ WAIT_UNTIL_POLISHED;
+
+ tableView->setColumnWidth(0, 10);
+ tableView->setColumnWidth(1, 20);
+
+ WAIT_UNTIL_POLISHED;
+
+ QCOMPARE(tableView->explicitColumnWidth(0), 10);
+ QCOMPARE(tableView->columnWidth(0), 10);
+ QCOMPARE(tableView->explicitColumnWidth(1), 20);
+ QCOMPARE(tableView->columnWidth(1), 20);
+
+ tableView->clearColumnWidths();
+
+ WAIT_UNTIL_POLISHED;
+
+ QCOMPARE(tableView->explicitColumnWidth(0), -1);
+ QCOMPARE(tableView->columnWidth(0), defaultSize);
+ QCOMPARE(tableView->explicitColumnWidth(1), -1);
+ QCOMPARE(tableView->columnWidth(1), defaultSize);
+}
+
+void tst_QQuickTableView::setRowHeight_data()
+{
+ QTest::addColumn<int>("rowCount");
+ QTest::addColumn<int>("row");
+ QTest::addColumn<qreal>("size");
+
+ QTest::newRow("first row") << 5 << 0 << 10.;
+ QTest::newRow("second row") << 5 << 2 << 10.;
+ QTest::newRow("a hidden row") << 20 << 19 << 10.;
+ QTest::newRow("a row outside model") << 1 << 5 << 10.;
+}
+
+void tst_QQuickTableView::setRowHeight()
+{
+ // Test that you can set the height of a row explicitly
+ QFETCH(int, rowCount);
+ QFETCH(int, row);
+ QFETCH(qreal, size);
+ LOAD_TABLEVIEW("plaintableview.qml");
+
+ auto model = TestModelAsVariant(rowCount, 2);
+ tableView->setModel(model);
+
+ WAIT_UNTIL_POLISHED;
+
+ tableView->setRowHeight(row, size);
+
+ WAIT_UNTIL_POLISHED;
+
+ QCOMPARE(tableView->explicitRowHeight(row), size);
+ if (tableView->isRowLoaded(row))
+ QCOMPARE(tableView->rowHeight(row), size);
+ else
+ QCOMPARE(tableView->rowHeight(row), -1);
+}
+
+void tst_QQuickTableView::setRowHeightWhenProviderIsSet()
+{
+ // Test that explicitly set row heights will be
+ // ignored if a rowHeightProvider is set
+ LOAD_TABLEVIEW("userowcolumnprovider.qml");
+
+ auto model = TestModelAsVariant(5, 5);
+ tableView->setModel(model);
+
+ WAIT_UNTIL_POLISHED;
+
+ tableView->setRowHeight(1, 100);
+
+ WAIT_UNTIL_POLISHED;
+
+ QCOMPARE(tableView->explicitRowHeight(1), 100);
+ QCOMPARE(tableView->rowHeight(1), 11);
+}
+
+void tst_QQuickTableView::setRowHeightForInvalidRow()
+{
+ // Check that you cannot set a row height for
+ // a negative row index.
+ LOAD_TABLEVIEW("plaintableview.qml");
+
+ auto model = TestModelAsVariant(5, 5);
+ tableView->setModel(model);
+
+ WAIT_UNTIL_POLISHED;
+
+ QTest::ignoreMessage(QtWarningMsg, QRegularExpression(".*row must be greather than, or equal to, zero"));
+ tableView->setRowHeight(-1, 10);
+
+ QCOMPARE(tableView->explicitRowHeight(-1), -1);
+ QCOMPARE(tableView->rowHeight(-1), -1);
+}
+
+void tst_QQuickTableView::setRowHeightWhenUsingSyncView()
+{
+ // Test that if you set an explicit row height on a TableView
+ // that has a sync view, then we set the column width on the
+ // sync view instead.
+ LOAD_TABLEVIEW("syncviewsimple.qml");
+ GET_QML_TABLEVIEW(tableViewV);
+ GET_QML_TABLEVIEW(tableViewHV);
+
+ const auto model = TestModelAsVariant(3, 3);
+ QQuickTableView *views[] = {tableView, tableViewV, tableViewHV};
+ for (auto view : views)
+ view->setModel(model);
+
+ const int row = 1;
+ const qreal size = 200;
+
+ tableView->setRowHeightProvider(QJSValue());
+ tableViewV->setRowHeight(row, size);
+
+ WAIT_UNTIL_POLISHED;
+
+ for (auto view : views) {
+ QCOMPARE(view->explicitRowHeight(row), size);
+ QCOMPARE(view->rowHeight(row), size);
+ }
+}
+
+void tst_QQuickTableView::resetRowHeight()
+{
+ // Check that you can reset a row height
+ // by setting its width to -1
+ LOAD_TABLEVIEW("plaintableview.qml");
+
+ auto model = TestModelAsVariant(5, 5);
+ tableView->setModel(model);
+
+ const int row = 1;
+ const qreal size = 10.;
+ const qreal defaultSize = 50.;
+
+ WAIT_UNTIL_POLISHED;
+
+ tableView->setRowHeight(row, size);
+
+ WAIT_UNTIL_POLISHED;
+
+ QCOMPARE(tableView->explicitRowHeight(row), size);
+ QCOMPARE(tableView->rowHeight(row), size);
+
+ tableView->setRowHeight(row, -1);
+
+ WAIT_UNTIL_POLISHED;
+
+ QCOMPARE(tableView->explicitRowHeight(row), -1);
+ QCOMPARE(tableView->rowHeight(row), defaultSize);
+}
+
+void tst_QQuickTableView::clearRowHeights()
+{
+ // Check that clearRowHeights() works as documented
+ LOAD_TABLEVIEW("plaintableview.qml");
+
+ auto model = TestModelAsVariant(5, 5);
+ tableView->setModel(model);
+
+ const qreal defaultSize = 50.;
+
+ WAIT_UNTIL_POLISHED;
+
+ tableView->setRowHeight(0, 10);
+ tableView->setRowHeight(1, 20);
+
+ WAIT_UNTIL_POLISHED;
+
+ QCOMPARE(tableView->explicitRowHeight(0), 10);
+ QCOMPARE(tableView->rowHeight(0), 10);
+ QCOMPARE(tableView->explicitRowHeight(1), 20);
+ QCOMPARE(tableView->rowHeight(1), 20);
+
+ tableView->clearRowHeights();
+
+ WAIT_UNTIL_POLISHED;
+
+ QCOMPARE(tableView->explicitRowHeight(0), -1);
+ QCOMPARE(tableView->rowHeight(0), defaultSize);
+ QCOMPARE(tableView->explicitRowHeight(1), -1);
+ QCOMPARE(tableView->rowHeight(1), defaultSize);
+}
+
+void tst_QQuickTableView::deletedDelegate()
+{
+ QQmlEngine engine;
+ QQmlComponent component(&engine, testFileUrl("deletedDelegate.qml"));
+ std::unique_ptr<QObject> root(component.create());
+ QVERIFY(root);
+ auto tv = root->findChild<QQuickTableView *>("tableview");
+ QVERIFY(tv);
+ // we need one event loop iteration for the deferred delete to trigger
+ // thus the QTRY_VERIFY
+ QTRY_COMPARE(tv->delegate(), nullptr);
+}
+
+void tst_QQuickTableView::tableViewInteractive()
+{
+ LOAD_TABLEVIEW("tableviewinteractive.qml");
+
+ auto *root = view->rootObject();
+ QVERIFY(root);
+ auto *window = root->window();
+ QVERIFY(window);
+
+ int eventCount = root->property("eventCount").toInt();
+ QCOMPARE(eventCount, 0);
+
+ // Event though we make 'interactive' as false, the TableView has
+ // pointerNacigationEnabled set as true by default, which allows it to consume
+ // mouse events and thus, eventCount still be zero
+ tableView->setInteractive(false);
+ QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, QPoint(100, 100));
+ QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, QPoint(100, 100));
+ eventCount = root->property("eventCount").toInt();
+ QCOMPARE(eventCount, 0);
+
+ // Making both 'interactive' and 'pointerNavigationEnabled' as false, doesn't
+ // allow TableView (and its parent Flickable) to consume mouse event and it
+ // passes to the below visual item
+ tableView->setInteractive(false);
+ tableView->setPointerNavigationEnabled(false);
+ QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, QPoint(100, 100));
+ QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, QPoint(100, 100));
+ eventCount = root->property("eventCount").toInt();
+ QCOMPARE(eventCount, 1);
+
+ // Making 'interactive' as true and 'pointerNavigationEnabled' as false,
+ // allows parent of TableView (i.e. Flickable) to consume mouse events
+ tableView->setInteractive(true);
+ tableView->setPointerNavigationEnabled(false);
+ QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, QPoint(100, 100));
+ QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, QPoint(100, 100));
+ eventCount = root->property("eventCount").toInt();
+ QCOMPARE(eventCount, 1);
+}
+
+void tst_QQuickTableView::columnResizing_data()
+{
+ QTest::addColumn<int>("column");
+ QTest::addColumn<bool>("pointerNavigationEnabled");
+
+ QTest::newRow("first") << 0 << true;
+ QTest::newRow("middle") << 1 << true;
+ QTest::newRow("middle") << 1 << false;
+ QTest::newRow("last") << 2 << true;
+}
+
+void tst_QQuickTableView::columnResizing()
+{
+ // Check that the user can drag on the horizontal
+ // end of a cell to resize the whole column.
+ QFETCH(int, column);
+ QFETCH(bool, pointerNavigationEnabled);
+ LOAD_TABLEVIEW("tableviewwithselected2.qml");
+
+ auto model = TestModelAsVariant(3, 3);
+ tableView->setModel(model);
+ tableView->setResizableColumns(true);
+ // Resizing column should not be affected by pointerNavigationEnabled
+ // (since it is controller by its own property).
+ tableView->setPointerNavigationEnabled(pointerNavigationEnabled);
+
+ WAIT_UNTIL_POLISHED;
+
+ QCOMPARE(tableView->explicitColumnWidth(column), -1);
+
+ // A resize shouldn't also change the current index or start a selection.
+ QSignalSpy currentIndexSpy(tableView->selectionModel(), &QItemSelectionModel::currentChanged);
+ QSignalSpy selectionSpy(tableView->selectionModel(), &QItemSelectionModel::selectionChanged);
+
+ const auto item = tableView->itemAtIndex(tableView->index(0, column));
+ QQuickWindow *window = item->window();
+
+ const qreal columnStartWidth = tableView->columnWidth(column);
+ const QPoint localPos = QPoint(item->width(), item->height() / 2);
+ const QPoint startPos = window->contentItem()->mapFromItem(item, localPos).toPoint();
+ const QPoint startDragDist = QPoint(qApp->styleHints()->startDragDistance() + 1, 0);
+ const QPoint dragLength(100, 0);
+
+ QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, startPos);
+ QTest::mouseMove(window, startPos + startDragDist);
+ QTest::mouseMove(window, startPos + dragLength);
+ QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, startPos + dragLength);
+
+ const qreal newColumnWidth = columnStartWidth + dragLength.x() - startDragDist.x();
+ QCOMPARE(tableView->explicitColumnWidth(column), newColumnWidth);
+ WAIT_UNTIL_POLISHED;
+ QCOMPARE(tableView->columnWidth(column), newColumnWidth);
+
+ QCOMPARE(currentIndexSpy.count(), 0);
+ QCOMPARE(selectionSpy.count(), 0);
+}
+
+void tst_QQuickTableView::rowResizing_data()
+{
+ QTest::addColumn<int>("row");
+
+ QTest::newRow("first") << 0;
+ QTest::newRow("middle") << 1;
+ QTest::newRow("last") << 2;
+}
+
+void tst_QQuickTableView::rowResizing()
+{
+ // Check that the user can drag on the vertical
+ // end of a cell to resize the whole row.
+ QFETCH(int, row);
+ LOAD_TABLEVIEW("tableviewwithselected2.qml");
+
+ auto model = TestModelAsVariant(3, 3);
+ tableView->setModel(model);
+ tableView->setResizableRows(true);
+
+ WAIT_UNTIL_POLISHED;
+
+ QCOMPARE(tableView->explicitRowHeight(row), -1);
+
+ // A resize shouldn't also change the current index or start a selection.
+ QSignalSpy currentIndexSpy(tableView->selectionModel(), &QItemSelectionModel::currentChanged);
+ QSignalSpy selectionSpy(tableView->selectionModel(), &QItemSelectionModel::selectionChanged);
+
+ const auto item = tableView->itemAtIndex(tableView->index(row, 0));
+ QQuickWindow *window = item->window();
+
+ const qreal rowStartHeight = tableView->rowHeight(row);
+ const QPoint localPos = QPoint(item->width() / 2, item->height());
+ const QPoint startPos = window->contentItem()->mapFromItem(item, localPos).toPoint();
+ const QPoint startDragDist = QPoint(0, qApp->styleHints()->startDragDistance() + 1);
+ const QPoint dragLength(0, 100);
+
+ QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, startPos);
+ QTest::mouseMove(window, startPos + startDragDist);
+ QTest::mouseMove(window, startPos + dragLength);
+ QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, startPos + dragLength);
+
+ const qreal newRowHeight = rowStartHeight + dragLength.y() - startDragDist.y();
+ QCOMPARE(tableView->explicitRowHeight(row), newRowHeight);
+ WAIT_UNTIL_POLISHED;
+ QCOMPARE(tableView->rowHeight(row), newRowHeight);
+
+ QCOMPARE(currentIndexSpy.count(), 0);
+ QCOMPARE(selectionSpy.count(), 0);
+}
+
+void tst_QQuickTableView::rowAndColumnResizing_data()
+{
+ QTest::addColumn<int>("rowAndColumn");
+ QTest::addColumn<bool>("addDelegateDragHandler");
+
+ QTest::newRow("first") << 0 << false;
+ QTest::newRow("middle") << 1 << false;
+ QTest::newRow("last") << 2 << false;
+
+ QTest::newRow("first, addDelegateDragHandler") << 0 << true;
+}
+
+void tst_QQuickTableView::rowAndColumnResizing()
+{
+ // Check that the user can drag in the corner of a cell
+ // to resize both the row and the column at the same time.
+ QFETCH(int, rowAndColumn);
+ QFETCH(bool, addDelegateDragHandler);
+ LOAD_TABLEVIEW("tableviewwithselected2.qml");
+
+ auto model = TestModelAsVariant(3, 3);
+ tableView->setModel(model);
+ tableView->setResizableColumns(true);
+ tableView->setResizableRows(true);
+
+ WAIT_UNTIL_POLISHED;
+
+ QCOMPARE(tableView->explicitColumnWidth(rowAndColumn), -1);
+ QCOMPARE(tableView->explicitRowHeight(rowAndColumn), -1);
+
+ const auto item = tableView->itemAtIndex(tableView->index(rowAndColumn, rowAndColumn));
+ QVERIFY(item);
+
+ if (addDelegateDragHandler) {
+ // Check that the grab permissions set on the resize handler
+ // allows you to add an ordinary drag handler to a delegate
+ // without blocking the resize handler.
+ new QQuickDragHandler(item);
+ }
+
+ QQuickWindow *window = item->window();
+
+ const qreal columnStartWidth = tableView->columnWidth(rowAndColumn);
+ const qreal rowStartHeight = tableView->rowHeight(rowAndColumn);
+
+ const QPoint localPos = QPoint(item->width(), item->height());
+ const QPoint startPos = window->contentItem()->mapFromItem(item, localPos).toPoint();
+ const qreal startDist = qApp->styleHints()->startDragDistance();
+ const QPoint startDragDist = QPoint(startDist + 1, startDist + 1);
+ const QPoint dragLength(100, 100);
+
+ QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, startPos);
+ QTest::mouseMove(window, startPos + startDragDist);
+ QTest::mouseMove(window, startPos + dragLength);
+ QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, startPos + dragLength);
+
+ const qreal newColumnWidth = columnStartWidth + dragLength.x() - startDragDist.x();
+ const qreal newRowHeight = rowStartHeight + dragLength.y() - startDragDist.y();
+ QCOMPARE(tableView->explicitColumnWidth(rowAndColumn), newColumnWidth);
+ QCOMPARE(tableView->explicitRowHeight(rowAndColumn), newRowHeight);
+ WAIT_UNTIL_POLISHED;
+ QCOMPARE(tableView->columnWidth(rowAndColumn), newColumnWidth);
+ QCOMPARE(tableView->rowHeight(rowAndColumn), newRowHeight);
+
+ // A resize shouldn't also change the current index
+ QVERIFY(!tableView->selectionModel()->currentIndex().isValid());
+}
+
+void tst_QQuickTableView::columnResizingDisabled()
+{
+ // Check that the user cannot drag on the horizontal end of a cell
+ // to resize a column if not resizableColumns is enabled.
+ // In that case, a drag should drag the flickable instead.
+ LOAD_TABLEVIEW("plaintableview.qml");
+
+ auto model = TestModelAsVariant(3, 3);
+ tableView->setModel(model);
+
+ WAIT_UNTIL_POLISHED;
+
+ QCOMPARE(tableView->contentY(), 0);
+
+ const int row = 1;
+ QCOMPARE(tableView->explicitRowHeight(row), -1);
+
+ const auto item = tableView->itemAtIndex(tableView->index(row, 0));
+ QQuickWindow *window = item->window();
+
+ const QPoint localPos = QPoint(item->width() / 2, item->height());
+ const QPoint startPos = window->contentItem()->mapFromItem(item, localPos).toPoint();
+ const QPoint startDragDist = QPoint(0, qApp->styleHints()->startDragDistance() + 1);
+ const QPoint dragLength(0, 100);
+
+ QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, startPos);
+ QTest::mouseMove(window, startPos + startDragDist);
+ QTest::mouseMove(window, startPos + dragLength);
+ QTest::mouseMove(window, startPos + (dragLength * 2));
+ QVERIFY(tableView->contentY() < 0);
+ QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, startPos + dragLength);
+
+ QVERIFY(!QQuickTest::qIsPolishScheduled(item));
+ QCOMPARE(tableView->explicitRowHeight(row), -1);
+ QCOMPARE(tableView->rowHeight(row), 50);
+}
+
+void tst_QQuickTableView::rowResizingDisabled()
+{
+ // Check that the user cannot drag on the vertical end of a cell to
+ // resize a row if not resizableRows is enabled.
+ // In that case, a drag should drag the flickable instead.
+ LOAD_TABLEVIEW("plaintableview.qml");
+
+ auto model = TestModelAsVariant(3, 3);
+ tableView->setModel(model);
+
+ WAIT_UNTIL_POLISHED;
+
+ QCOMPARE(tableView->contentY(), 0);
+
+ const int row = 1;
+ QCOMPARE(tableView->explicitRowHeight(row), -1);
+
+ const auto item = tableView->itemAtIndex(tableView->index(row, 0));
+ QQuickWindow *window = item->window();
+
+ const QPoint localPos = QPoint(item->width() / 2, item->height());
+ const QPoint startPos = window->contentItem()->mapFromItem(item, localPos).toPoint();
+ const QPoint startDragDist = QPoint(0, qApp->styleHints()->startDragDistance() + 1);
+ const QPoint dragLength(0, 100);
+
+ QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, startPos);
+ QTest::mouseMove(window, startPos + startDragDist);
+ QTest::mouseMove(window, startPos + dragLength);
+ QTest::mouseMove(window, startPos + (dragLength * 2));
+ QVERIFY(tableView->contentY() < 0);
+ QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, startPos + dragLength);
+
+ QVERIFY(!QQuickTest::qIsPolishScheduled(item));
+ QCOMPARE(tableView->explicitRowHeight(row), -1);
+ QCOMPARE(tableView->rowHeight(row), 50);
+}
+
+void tst_QQuickTableView::dragFromCellCenter()
+{
+ // Check that the user cannot resize a row (or column) by dragging
+ // from the center of a cell. In that case, a drag should
+ // drag the flickable instead.
+ LOAD_TABLEVIEW("plaintableview.qml");
+
+ auto model = TestModelAsVariant(3, 3);
+ tableView->setModel(model);
+ tableView->setResizableRows(true);
+
+ WAIT_UNTIL_POLISHED;
+
+ QCOMPARE(tableView->contentY(), 0);
+
+ const int row = 1;
+ QCOMPARE(tableView->explicitRowHeight(row), -1);
+
+ const auto item = tableView->itemAtIndex(tableView->index(row, 0));
+ QQuickWindow *window = item->window();
+
+ const QPoint localPos = QPoint(item->width() / 2, item->height() / 2);
+ const QPoint startPos = window->contentItem()->mapFromItem(item, localPos).toPoint();
+ const QPoint startDragDist = QPoint(0, qApp->styleHints()->startDragDistance() + 1);
+ const QPoint dragLength(0, 100);
+
+ QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, startPos);
+ QTest::mouseMove(window, startPos + startDragDist);
+ QTest::mouseMove(window, startPos + dragLength);
+ QTest::mouseMove(window, startPos + (dragLength * 2));
+ QVERIFY(tableView->contentY() < 0);
+ QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, startPos + dragLength);
+
+ QVERIFY(!QQuickTest::qIsPolishScheduled(item));
+ QCOMPARE(tableView->explicitRowHeight(row), -1);
+ QCOMPARE(tableView->rowHeight(row), 50);
+}
+
+void tst_QQuickTableView::tapOnResizeArea_data()
+{
+ QTest::addColumn<bool>("resizableRows");
+ QTest::addColumn<bool>("resizableColumns");
+ QTest::addColumn<bool>("interactive");
+
+ for (bool interactive : {true, false}) {
+ QTest::newRow("resize disabled") << false << false << interactive;
+ QTest::newRow("resizableRows") << true << false << interactive;
+ QTest::newRow("resizableColumns") << false << true << interactive;
+ QTest::newRow("resizableRows && resizableColumns") << true << true << interactive;
+ }
+}
+
+void tst_QQuickTableView::tapOnResizeArea()
+{
+ // Check that if a tap or a press happens on the resize area between the
+ // cells, we only change the current index if the resizing is disabled.
+ QFETCH(bool, resizableRows);
+ QFETCH(bool, resizableColumns);
+ QFETCH(bool, interactive);
+ LOAD_TABLEVIEW("tableviewwithselected2.qml");
+
+ auto model = TestModel(3, 3);
+ tableView->setModel(QVariant::fromValue(&model));
+ tableView->setResizableRows(resizableRows);
+ tableView->setResizableColumns(resizableColumns);
+ tableView->setInteractive(interactive);
+ tableView->setPointerNavigationEnabled(true);
+
+ WAIT_UNTIL_POLISHED;
+
+ const QPoint cell(1, 1);
+ const auto item = tableView->itemAtCell(cell);
+ QQuickWindow *window = item->window();
+
+ const QPoint localPos = QPoint(item->width() - 1, item->height() - 1);
+ const QPoint tapPos = window->contentItem()->mapFromItem(item, localPos).toPoint();
+
+ // Start by moving the mouse out of the way
+ QTest::mouseMove(window, tapPos + QPoint(200, 200));
+ // Then do a tap on the resize area
+ QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, tapPos);
+ QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, tapPos);
+
+ if (resizableRows || resizableColumns)
+ QVERIFY(!tableView->selectionModel()->currentIndex().isValid());
+ else
+ QCOMPARE(tableView->selectionModel()->currentIndex(), model.index(1, 1));
+}
+
+void tst_QQuickTableView::editUsingEditTriggers_data()
+{
+ QTest::addColumn<QQuickTableView::EditTriggers>("editTriggers");
+ QTest::addColumn<bool>("interactive");
+
+ // We need to test both with and without interactive, since SingleTapped
+ // actions will happen already on press in a TableView that is not interactive!
+ for (bool interactive : {true, false}) {
+ QTest::newRow("NoEditTriggers") << QQuickTableView::EditTriggers(QQuickTableView::NoEditTriggers) << interactive;
+ QTest::newRow("SingleTapped") << QQuickTableView::EditTriggers(QQuickTableView::SingleTapped) << interactive;
+ QTest::newRow("DoubleTapped") << QQuickTableView::EditTriggers(QQuickTableView::DoubleTapped) << interactive;
+ QTest::newRow("SelectedTapped") << QQuickTableView::EditTriggers(QQuickTableView::SelectedTapped) << interactive;
+ QTest::newRow("EditKeyPressed") << QQuickTableView::EditTriggers(QQuickTableView::EditKeyPressed) << interactive;
+ QTest::newRow("AnyKeyPressed") << QQuickTableView::EditTriggers(QQuickTableView::EditKeyPressed) << interactive;
+ QTest::newRow("DoubleTapped | EditKeyPressed")
+ << QQuickTableView::EditTriggers(QQuickTableView::DoubleTapped | QQuickTableView::EditKeyPressed) << interactive;
+ QTest::newRow("SingleTapped | AnyKeyPressed")
+ << QQuickTableView::EditTriggers(QQuickTableView::SingleTapped | QQuickTableView::AnyKeyPressed) << interactive;
+ }
+}
+
+void tst_QQuickTableView::editUsingEditTriggers()
+{
+ // Check that you can start to edit in TableView
+ // using the available edit triggers.
+ QFETCH(QQuickTableView::EditTriggers, editTriggers);
+ QFETCH(bool, interactive);
+ LOAD_TABLEVIEW("editdelegate.qml");
+
+ auto model = TestModel(4, 4);
+ tableView->setModel(QVariant::fromValue(&model));
+ tableView->setInteractive(interactive);
+ tableView->forceActiveFocus();
+
+ QCOMPARE(tableView->editTriggers(), QQuickTableView::DoubleTapped | QQuickTableView::EditKeyPressed);
+ tableView->setEditTriggers(editTriggers);
+
+ const char kEditItem[] = "editItem";
+ const char kEditIndex[] = "editIndex";
+
+ WAIT_UNTIL_POLISHED;
+
+ QVERIFY(!tableView->property(kEditItem).value<QQuickItem *>());
+ QVERIFY(!tableView->property(kEditIndex).value<QModelIndex>().isValid());
+
+ const QPoint cell1(1, 1);
+ const QPoint cell2(2, 1);
+ const QModelIndex index1 = tableView->modelIndex(cell1);
+ const QModelIndex index2 = tableView->modelIndex(cell2);
+ const auto item1 = tableView->itemAtCell(cell1);
+ const auto item2 = tableView->itemAtCell(cell2);
+ QVERIFY(item1);
+ QVERIFY(item2);
+
+ QQuickWindow *window = tableView->window();
+
+ const QPoint localPos = QPoint(item1->width() - 1, item1->height() - 1);
+ const QPoint localPosOutside = QPoint(tableView->contentWidth() + 10, tableView->contentHeight() + 10);
+ const QPoint tapPos1 = window->contentItem()->mapFromItem(item1, localPos).toPoint();
+ const QPoint tapPos2 = window->contentItem()->mapFromItem(item2, localPos).toPoint();
+ const QPoint tapOutsideContentItem = window->contentItem()->mapFromItem(item2, localPosOutside).toPoint();
+
+ if (editTriggers & QQuickTableView::SingleTapped) {
+ // edit cell 1
+ QTest::mouseClick(window, Qt::LeftButton, Qt::NoModifier, tapPos1);
+ QCOMPARE(tableView->selectionModel()->currentIndex(), index1);
+ const auto editItem1 = tableView->property(kEditItem).value<QQuickItem *>();
+ QVERIFY(editItem1);
+ QVERIFY(editItem1->hasActiveFocus());
+ QCOMPARE(tableView->property(kEditIndex).value<QModelIndex>(), index1);
+
+ // edit cell 2 (without closing the previous edit session first)
+ QTest::mouseClick(window, Qt::LeftButton, Qt::NoModifier, tapPos2);
+ QCOMPARE(tableView->selectionModel()->currentIndex(), index2);
+ const auto editItem2 = tableView->property(kEditItem).value<QQuickItem *>();
+ QVERIFY(editItem2);
+ QVERIFY(editItem2->hasActiveFocus());
+ QCOMPARE(tableView->property(kEditIndex).value<QModelIndex>(), index2);
+
+ // single tap outside content item should close the editor
+ QTest::mouseClick(window, Qt::LeftButton, Qt::NoModifier, tapOutsideContentItem);
+ QVERIFY(!tableView->property(kEditItem).value<QQuickItem *>());
+ QVERIFY(!tableView->property(kEditIndex).value<QModelIndex>().isValid());
+ QCOMPARE(tableView->selectionModel()->currentIndex(), index2);
+ }
+
+ if (editTriggers & QQuickTableView::DoubleTapped) {
+ // edit cell 1
+ QTest::mouseDClick(window, Qt::LeftButton, Qt::NoModifier, tapPos1);
+ QCOMPARE(tableView->selectionModel()->currentIndex(), index1);
+ const auto editItem1 = tableView->property(kEditItem).value<QQuickItem *>();
+ QVERIFY(editItem1);
+ QVERIFY(editItem1->hasActiveFocus());
+ QCOMPARE(tableView->property(kEditIndex).value<QModelIndex>(), index1);
+
+ // edit cell 2 (without closing the previous edit session first)
+ QTest::mouseDClick(window, Qt::LeftButton, Qt::NoModifier, tapPos2);
+ QCOMPARE(tableView->selectionModel()->currentIndex(), index2);
+ const auto editItem2 = tableView->property(kEditItem).value<QQuickItem *>();
+ QVERIFY(editItem2);
+ QVERIFY(editItem2->hasActiveFocus());
+ QCOMPARE(tableView->property(kEditIndex).value<QModelIndex>(), index2);
+
+ // single tap outside the edit item should close the editor
+ QTest::mouseClick(window, Qt::LeftButton, Qt::NoModifier, tapPos1);
+ QVERIFY(!tableView->property(kEditItem).value<QQuickItem *>());
+ QVERIFY(!tableView->property(kEditIndex).value<QModelIndex>().isValid());
+ QCOMPARE(tableView->selectionModel()->currentIndex(), index1);
+
+ if (!(editTriggers & QQuickTableView::SingleTapped)) {
+ // single tap on a cell should not open the editor
+ QTest::mouseClick(window, Qt::LeftButton, Qt::NoModifier, tapPos1);
+ QVERIFY(!tableView->property(kEditItem).value<QQuickItem *>());
+ QVERIFY(!tableView->property(kEditIndex).value<QModelIndex>().isValid());
+ }
+
+ // single tap outside content item should make sure editing ends
+ QTest::mouseClick(window, Qt::LeftButton, Qt::NoModifier, tapOutsideContentItem);
+ QVERIFY(!tableView->property(kEditItem).value<QQuickItem *>());
+ QVERIFY(!tableView->property(kEditIndex).value<QModelIndex>().isValid());
+ }
+
+ if (editTriggers & QQuickTableView::SelectedTapped) {
+ // select cell first, then tap on it
+ tableView->selectionModel()->setCurrentIndex(index1, QItemSelectionModel::Select);
+ QTest::mouseClick(window, Qt::LeftButton, Qt::NoModifier, tapPos1);
+ QCOMPARE(tableView->selectionModel()->currentIndex(), index1);
+ const auto editItem1 = tableView->property(kEditItem).value<QQuickItem *>();
+ QVERIFY(editItem1);
+ QVERIFY(editItem1->hasActiveFocus());
+ QCOMPARE(tableView->property(kEditIndex).value<QModelIndex>(), index1);
+
+ // tap on a non-selected cell. This should close the editor, and move
+ // the current index, but not begin to edit the cell.
+ QTest::mouseClick(window, Qt::LeftButton, Qt::NoModifier, tapPos2);
+ QCOMPARE(tableView->selectionModel()->currentIndex(), index2);
+ QVERIFY(!tableView->property(kEditItem).value<QQuickItem *>());
+ QVERIFY(!tableView->property(kEditIndex).value<QModelIndex>().isValid());
+
+ // tap on a non-selected cell while no editor is active
+ tableView->selectionModel()->setCurrentIndex(index1, QItemSelectionModel::NoUpdate);
+ QTest::mouseClick(window, Qt::LeftButton, Qt::NoModifier, tapPos2);
+ QVERIFY(!tableView->property(kEditItem).value<QQuickItem *>());
+ QVERIFY(!tableView->property(kEditIndex).value<QModelIndex>().isValid());
+ QCOMPARE(tableView->selectionModel()->currentIndex(), index2);
+
+ // tap on the current cell. This alone should not start an edit (unless it's also selected)
+ QTest::mouseClick(window, Qt::LeftButton, Qt::NoModifier, tapPos1);
+ QVERIFY(!tableView->property(kEditItem).value<QQuickItem *>());
+ QVERIFY(!tableView->property(kEditIndex).value<QModelIndex>().isValid());
+ }
+
+ if (editTriggers & QQuickTableView::EditKeyPressed) {
+ tableView->selectionModel()->setCurrentIndex(index1, QItemSelectionModel::NoUpdate);
+ QTest::keyClick(window, Qt::Key_Return);
+ const auto editItem1 = tableView->property(kEditItem).value<QQuickItem *>();
+ QVERIFY(editItem1);
+ QVERIFY(editItem1->hasActiveFocus());
+ QCOMPARE(tableView->property(kEditIndex).value<QModelIndex>(), index1);
+
+ // Pressing escape should close the editor
+ QTest::keyClick(window, Qt::Key_Escape);
+ QVERIFY(!tableView->property(kEditItem).value<QQuickItem *>());
+ QVERIFY(!tableView->property(kEditIndex).value<QModelIndex>().isValid());
+ QCOMPARE(tableView->selectionModel()->currentIndex(), index1);
+
+ // Pressing Enter to open the editor again
+ QTest::keyClick(window, Qt::Key_Enter);
+ const auto editItem2 = tableView->property(kEditItem).value<QQuickItem *>();
+ QVERIFY(editItem2);
+ QVERIFY(editItem2->hasActiveFocus());
+ QCOMPARE(tableView->property(kEditIndex).value<QModelIndex>(), index1);
+
+ // single tap outside the edit item should close the editor
+ QTest::mouseClick(window, Qt::LeftButton, Qt::NoModifier, tapPos2);
+ QVERIFY(!tableView->property(kEditItem).value<QQuickItem *>());
+ QVERIFY(!tableView->property(kEditIndex).value<QModelIndex>().isValid());
+ }
+
+ if (editTriggers & QQuickTableView::AnyKeyPressed) {
+ // Pressing key x should start to edit. And in case of AnyKeyPressed, we
+ // also replay the key event to the focus object.
+ tableView->selectionModel()->setCurrentIndex(index1, QItemSelectionModel::NoUpdate);
+ QTest::keyClick(window, Qt::Key_X);
+ QCOMPARE(tableView->selectionModel()->currentIndex(), index1);
+ QCOMPARE(tableView->property(kEditIndex).value<QModelIndex>(), index1);
+ auto textInput1 = tableView->property(kEditItem).value<QQuickTextInput *>();
+ QVERIFY(textInput1);
+ QVERIFY(textInput1->hasActiveFocus());
+ QCOMPARE(textInput1->text(), "x");
+
+ // Pressing escape should close the editor
+ QTest::keyClick(window, Qt::Key_Escape);
+ QVERIFY(!tableView->property(kEditItem).value<QQuickItem *>());
+ QVERIFY(!tableView->property(kEditIndex).value<QModelIndex>().isValid());
+ QCOMPARE(tableView->selectionModel()->currentIndex(), index1);
+
+ // Pressing a modifier key alone should not open the editor
+ QTest::keyClick(window, Qt::Key_Shift);
+ QVERIFY(!tableView->property(kEditItem).value<QQuickItem *>());
+ QTest::keyClick(window, Qt::Key_Control);
+ QVERIFY(!tableView->property(kEditItem).value<QQuickItem *>());
+ QTest::keyClick(window, Qt::Key_Alt);
+ QVERIFY(!tableView->property(kEditItem).value<QQuickItem *>());
+ QTest::keyClick(window, Qt::Key_Meta);
+ QVERIFY(!tableView->property(kEditItem).value<QQuickItem *>());
+
+ // Pressing enter should also start to edit. But this is a
+ // special case, we don't replay enter into the focus object.
+ tableView->selectionModel()->setCurrentIndex(index1, QItemSelectionModel::NoUpdate);
+ QTest::keyClick(window, Qt::Key_Enter);
+ QCOMPARE(tableView->selectionModel()->currentIndex(), index1);
+ QCOMPARE(tableView->property(kEditIndex).value<QModelIndex>(), index1);
+ auto textInput2 = tableView->property(kEditItem).value<QQuickTextInput *>();
+ QVERIFY(textInput2);
+ QVERIFY(textInput2->hasActiveFocus());
+ QCOMPARE(textInput2->text(), "1");
+
+ if (!(editTriggers & QQuickTableView::SingleTapped)) {
+ // single tap outside the edit item should close the editor
+ QTest::mouseClick(window, Qt::LeftButton, Qt::NoModifier, tapPos2);
+ QVERIFY(!tableView->property(kEditItem).value<QQuickItem *>());
+ QVERIFY(!tableView->property(kEditIndex).value<QModelIndex>().isValid());
+ }
+
+ // single tap outside content item should make sure editing ends
+ QTest::mouseClick(window, Qt::LeftButton, Qt::NoModifier, tapOutsideContentItem);
+ QVERIFY(!tableView->property(kEditItem).value<QQuickItem *>());
+ QVERIFY(!tableView->property(kEditIndex).value<QModelIndex>().isValid());
+ }
+
+ if (editTriggers == QQuickTableView::NoEditTriggers) {
+ QTest::mouseClick(window, Qt::LeftButton, Qt::NoModifier, tapPos1);
+ QVERIFY(!tableView->property(kEditItem).value<QQuickItem *>());
+ QVERIFY(!tableView->property(kEditIndex).value<QModelIndex>().isValid());
+ QTest::mouseDClick(window, Qt::LeftButton, Qt::NoModifier, tapPos1);
+ QVERIFY(!tableView->property(kEditItem).value<QQuickItem *>());
+ QVERIFY(!tableView->property(kEditIndex).value<QModelIndex>().isValid());
+ tableView->selectionModel()->setCurrentIndex(index1, QItemSelectionModel::NoUpdate);
+ QTest::keyClick(window, Qt::Key_Return);
+ QVERIFY(!tableView->property(kEditItem).value<QQuickItem *>());
+ QVERIFY(!tableView->property(kEditIndex).value<QModelIndex>().isValid());
+ QTest::keyClick(window, Qt::Key_Enter);
+ QVERIFY(!tableView->property(kEditItem).value<QQuickItem *>());
+ QVERIFY(!tableView->property(kEditIndex).value<QModelIndex>().isValid());
+ QTest::keyClick(window, Qt::Key_X);
+ QVERIFY(!tableView->property(kEditItem).value<QQuickItem *>());
+ QVERIFY(!tableView->property(kEditIndex).value<QModelIndex>().isValid());
+ }
+}
+
+void tst_QQuickTableView::editUsingTab()
+{
+ // Check that the you can commit and start to edit
+ // the next cell by pressing tab and backtab.
+ LOAD_TABLEVIEW("editdelegate.qml");
+
+ auto model = TestModel(4, 4);
+ tableView->setModel(QVariant::fromValue(&model));
+ tableView->forceActiveFocus();
+
+ const char kEditItem[] = "editItem";
+ const char kEditIndex[] = "editIndex";
+
+ WAIT_UNTIL_POLISHED;
+
+ const QPoint cell1(1, 1);
+ const QPoint cell2(2, 1);
+ const QModelIndex index1 = tableView->modelIndex(cell1);
+ const QModelIndex index2 = tableView->modelIndex(cell2);
+
+ QQuickWindow *window = tableView->window();
+
+ // Edit cell 1
+ tableView->edit(index1);
+ QCOMPARE(tableView->property(kEditIndex).value<QModelIndex>(), index1);
+ const QQuickItem *editItem1 = tableView->property(kEditItem).value<QQuickItem *>();
+ QVERIFY(editItem1);
+
+ // Press Tab to edit cell 2
+ QTest::keyClick(window, Qt::Key_Tab);
+ QCOMPARE(tableView->property(kEditIndex).value<QModelIndex>(), index2);
+ const QQuickItem *editItem2 = tableView->property(kEditItem).value<QQuickItem *>();
+ QVERIFY(editItem2);
+
+ // Press Backtab to edit cell 1
+ QTest::keyClick(window, Qt::Key_Backtab);
+ QCOMPARE(tableView->property(kEditIndex).value<QModelIndex>(), index1);
+ const QQuickItem *editItem3 = tableView->property(kEditItem).value<QQuickItem *>();
+ QVERIFY(editItem3);
+}
+
+void tst_QQuickTableView::editDelegateComboBox()
+{
+ // Using a ComboBox as an edit delegate should be a quite common
+ // use case. So test that it works.
+ LOAD_TABLEVIEW("editdelegate_combobox.qml");
+
+ auto model = TestModel(4, 4);
+ tableView->setModel(QVariant::fromValue(&model));
+ tableView->forceActiveFocus();
+
+ const char kEditItem[] = "editItem";
+ const char kEditIndex[] = "editIndex";
+ const char kCommitCount[] = "commitCount";
+ const char kComboFocusCount[] = "comboFocusCount";
+
+ WAIT_UNTIL_POLISHED;
+
+ const QPoint cell1(1, 1);
+ const QPoint cell2(2, 1);
+ const QModelIndex index1 = tableView->modelIndex(cell1);
+ const QModelIndex index2 = tableView->modelIndex(cell2);
+
+ QQuickWindow *window = tableView->window();
+
+ // Edit cell 1
+ tableView->edit(index1);
+ QCOMPARE(tableView->property(kEditIndex).value<QModelIndex>(), index1);
+ const QQuickItem *editItem1 = tableView->property(kEditItem).value<QQuickItem *>();
+ QVERIFY(editItem1);
+ QCOMPARE(tableView->property(kComboFocusCount).value<int>(), 1);
+
+ // Press Tab to edit cell 2
+ QTest::keyClick(window, Qt::Key_Tab);
+ QCOMPARE(tableView->property(kCommitCount).value<int>(), 1);
+ QCOMPARE(tableView->property(kEditIndex).value<QModelIndex>(), index2);
+ const QQuickItem *editItem2 = tableView->property(kEditItem).value<QQuickItem *>();
+ QVERIFY(editItem2);
+ QCOMPARE(tableView->property(kComboFocusCount).value<int>(), 2);
+
+ // Press Enter to commit
+ QTest::keyClick(window, Qt::Key_Enter);
+ QCOMPARE(tableView->property(kCommitCount).value<int>(), 2);
+ QCOMPARE(tableView->property(kComboFocusCount).value<int>(), 2);
+ QVERIFY(!tableView->property(kEditIndex).value<QModelIndex>().isValid());
+ QVERIFY(!tableView->property(kEditItem).value<QQuickItem *>());
+
+ // Edit cell 1
+ tableView->edit(index1);
+ // Press escape to close editor
+ QTest::keyClick(window, Qt::Key_Escape);
+ QCOMPARE(tableView->property(kCommitCount).value<int>(), 2);
+ QCOMPARE(tableView->property(kComboFocusCount).value<int>(), 3);
+ QVERIFY(!tableView->property(kEditIndex).value<QModelIndex>().isValid());
+ QVERIFY(!tableView->property(kEditItem).value<QQuickItem *>());
+
+ // Edit cell 2
+ tableView->edit(index2);
+ // Press space to open combo menu
+ QTest::keyClick(window, Qt::Key_Space);
+ // Press Enter to commit and close the editor
+ QTest::keyClick(window, Qt::Key_Enter);
+ QCOMPARE(tableView->property(kCommitCount).value<int>(), 3);
+ QCOMPARE(tableView->property(kComboFocusCount).value<int>(), 4);
+ QVERIFY(!tableView->property(kEditIndex).value<QModelIndex>().isValid());
+ QVERIFY(!tableView->property(kEditItem).value<QQuickItem *>());
+}
+
+void tst_QQuickTableView::editOnNonEditableCell_data()
+{
+ QTest::addColumn<QQuickTableView::EditTriggers>("editTriggers");
+
+ QTest::newRow("SingleTapped") << QQuickTableView::EditTriggers(QQuickTableView::SingleTapped);
+ QTest::newRow("DoubleTapped") << QQuickTableView::EditTriggers(QQuickTableView::DoubleTapped);
+ QTest::newRow("SelectedTapped") << QQuickTableView::EditTriggers(QQuickTableView::SelectedTapped);
+ QTest::newRow("EditKeyPressed") << QQuickTableView::EditTriggers(QQuickTableView::EditKeyPressed);
+ QTest::newRow("AnyKeyPressed") << QQuickTableView::EditTriggers(QQuickTableView::EditKeyPressed);
+}
+
+void tst_QQuickTableView::editOnNonEditableCell()
+{
+ // Check that the user cannot edit a non-editable cell from the edit triggers.
+ // Note: we don't want TableView to print out warnings in this case, since
+ // the user is not doing anything wrong. We only want to print out warnings if
+ // the application is calling edit() explicitly on a cell that cannot be edited
+ // (separate test below).
+ QFETCH(QQuickTableView::EditTriggers, editTriggers);
+ LOAD_TABLEVIEW("editdelegate.qml");
+
+ auto model = TestModel(4, 4);
+ // set flags that exclude Qt::ItemIsEditable
+ model.setFlags(Qt::ItemIsEnabled);
+ tableView->setModel(QVariant::fromValue(&model));
+ tableView->setEditTriggers(editTriggers);
+ tableView->forceActiveFocus();
+
+ const char kEditItem[] = "editItem";
+ const char kEditIndex[] = "editIndex";
+
+ WAIT_UNTIL_POLISHED;
+
+ const QPoint cell(1, 1);
+ const QModelIndex index1 = tableView->modelIndex(cell);
+ const auto item = tableView->itemAtCell(cell);
+ QVERIFY(item);
+
+ QQuickWindow *window = tableView->window();
+
+ const QPoint localPos = QPoint(item->width() - 1, item->height() - 1);
+ const QPoint tapPos = window->contentItem()->mapFromItem(item, localPos).toPoint();
+
+ if (editTriggers & QQuickTableView::SingleTapped) {
+ QTest::mouseClick(window, Qt::LeftButton, Qt::NoModifier, tapPos);
+ QVERIFY(!tableView->property(kEditItem).value<QQuickItem *>());
+ QVERIFY(!tableView->property(kEditIndex).value<QModelIndex>().isValid());
+ }
+
+ if (editTriggers & QQuickTableView::DoubleTapped) {
+ QTest::mouseDClick(window, Qt::LeftButton, Qt::NoModifier, tapPos);
+ QVERIFY(!tableView->property(kEditItem).value<QQuickItem *>());
+ QVERIFY(!tableView->property(kEditIndex).value<QModelIndex>().isValid());
+ }
+
+ if (editTriggers & QQuickTableView::SelectedTapped) {
+ // select cell first, then tap on it
+ tableView->selectionModel()->setCurrentIndex(index1, QItemSelectionModel::NoUpdate);
+ QTest::mouseClick(window, Qt::LeftButton, Qt::NoModifier, tapPos);
+ QVERIFY(!tableView->property(kEditItem).value<QQuickItem *>());
+ QVERIFY(!tableView->property(kEditIndex).value<QModelIndex>().isValid());
+ }
+
+ if (editTriggers & QQuickTableView::EditKeyPressed) {
+ tableView->selectionModel()->setCurrentIndex(index1, QItemSelectionModel::NoUpdate);
+ QTest::keyClick(window, Qt::Key_Enter);
+ QVERIFY(!tableView->property(kEditItem).value<QQuickItem *>());
+ QVERIFY(!tableView->property(kEditIndex).value<QModelIndex>().isValid());
+ QTest::keyClick(window, Qt::Key_Return);
+ QVERIFY(!tableView->property(kEditItem).value<QQuickItem *>());
+ QVERIFY(!tableView->property(kEditIndex).value<QModelIndex>().isValid());
+ }
+
+ if (editTriggers & QQuickTableView::AnyKeyPressed) {
+ tableView->selectionModel()->setCurrentIndex(index1, QItemSelectionModel::NoUpdate);
+ QTest::keyClick(window, Qt::Key_X);
+ QVERIFY(!tableView->property(kEditItem).value<QQuickItem *>());
+ QVERIFY(!tableView->property(kEditIndex).value<QModelIndex>().isValid());
+ QTest::keyClick(window, Qt::Key_Enter);
+ QVERIFY(!tableView->property(kEditItem).value<QQuickItem *>());
+ QVERIFY(!tableView->property(kEditIndex).value<QModelIndex>().isValid());
+ }
+}
+
+void tst_QQuickTableView::noEditDelegate_data()
+{
+ QTest::addColumn<QQuickTableView::EditTriggers>("editTriggers");
+
+ QTest::newRow("NoEditTriggers") << QQuickTableView::EditTriggers(QQuickTableView::NoEditTriggers);
+ QTest::newRow("SingleTapped") << QQuickTableView::EditTriggers(QQuickTableView::SingleTapped);
+ QTest::newRow("DoubleTapped") << QQuickTableView::EditTriggers(QQuickTableView::DoubleTapped);
+ QTest::newRow("SelectedTapped") << QQuickTableView::EditTriggers(QQuickTableView::SelectedTapped);
+ QTest::newRow("EditKeyPressed") << QQuickTableView::EditTriggers(QQuickTableView::EditKeyPressed);
+ QTest::newRow("AnyKeyPressed") << QQuickTableView::EditTriggers(QQuickTableView::EditKeyPressed);
+}
+
+void tst_QQuickTableView::noEditDelegate()
+{
+ // Check that you cannot start to edit if
+ // no edit delegate has been set.
+ QFETCH(QQuickTableView::EditTriggers, editTriggers);
+ LOAD_TABLEVIEW("tableviewwithselected2.qml");
+
+ auto model = TestModel(4, 4);
+ tableView->setModel(QVariant::fromValue(&model));
+ tableView->setEditTriggers(editTriggers);
+ tableView->forceActiveFocus();
+
+ const char kEditItem[] = "editItem";
+ const char kEditIndex[] = "editIndex";
+
+ WAIT_UNTIL_POLISHED;
+
+ QVERIFY(!tableView->property(kEditItem).value<QQuickItem *>());
+ QVERIFY(!tableView->property(kEditIndex).value<QModelIndex>().isValid());
+
+ const QPoint cell(1, 1);
+ const QModelIndex index1 = tableView->modelIndex(cell);
+ const auto item = tableView->itemAtCell(cell);
+ QVERIFY(item);
+
+ QQuickWindow *window = tableView->window();
+
+ const QPoint localPos = QPoint(item->width() - 1, item->height() - 1);
+ const QPoint tapPos = window->contentItem()->mapFromItem(item, localPos).toPoint();
+
+ if (editTriggers & QQuickTableView::SingleTapped) {
+ QTest::mouseClick(window, Qt::LeftButton, Qt::NoModifier, tapPos);
+ QVERIFY(!tableView->property(kEditItem).value<QQuickItem *>());
+ QVERIFY(!tableView->property(kEditIndex).value<QModelIndex>().isValid());
+ }
+
+ if (editTriggers & QQuickTableView::DoubleTapped) {
+ QTest::mouseDClick(window, Qt::LeftButton, Qt::NoModifier, tapPos);
+ QVERIFY(!tableView->property(kEditItem).value<QQuickItem *>());
+ QVERIFY(!tableView->property(kEditIndex).value<QModelIndex>().isValid());
+ }
+
+ if (editTriggers & QQuickTableView::SelectedTapped) {
+ // select cell first, then tap on it
+ tableView->selectionModel()->setCurrentIndex(index1, QItemSelectionModel::NoUpdate);
+ QTest::mouseClick(window, Qt::LeftButton, Qt::NoModifier, tapPos);
+ QVERIFY(!tableView->property(kEditItem).value<QQuickItem *>());
+ QVERIFY(!tableView->property(kEditIndex).value<QModelIndex>().isValid());
+ }
+
+ if (editTriggers & QQuickTableView::EditKeyPressed) {
+ tableView->selectionModel()->setCurrentIndex(index1, QItemSelectionModel::NoUpdate);
+ QTest::keyClick(window, Qt::Key_Enter);
+ QVERIFY(!tableView->property(kEditItem).value<QQuickItem *>());
+ QVERIFY(!tableView->property(kEditIndex).value<QModelIndex>().isValid());
+ QTest::keyClick(window, Qt::Key_Return);
+ QVERIFY(!tableView->property(kEditItem).value<QQuickItem *>());
+ QVERIFY(!tableView->property(kEditIndex).value<QModelIndex>().isValid());
+ }
+
+ if (editTriggers & QQuickTableView::AnyKeyPressed) {
+ tableView->selectionModel()->setCurrentIndex(index1, QItemSelectionModel::NoUpdate);
+ QTest::keyClick(window, Qt::Key_X);
+ QVERIFY(!tableView->property(kEditItem).value<QQuickItem *>());
+ QVERIFY(!tableView->property(kEditIndex).value<QModelIndex>().isValid());
+ QTest::keyClick(window, Qt::Key_Enter);
+ QVERIFY(!tableView->property(kEditItem).value<QQuickItem *>());
+ QVERIFY(!tableView->property(kEditIndex).value<QModelIndex>().isValid());
+ }
+
+ if (editTriggers == QQuickTableView::NoEditTriggers) {
+ QTest::mouseClick(window, Qt::LeftButton, Qt::NoModifier, tapPos);
+ QVERIFY(!tableView->property(kEditItem).value<QQuickItem *>());
+ QVERIFY(!tableView->property(kEditIndex).value<QModelIndex>().isValid());
+ QTest::mouseDClick(window, Qt::LeftButton, Qt::NoModifier, tapPos);
+ QVERIFY(!tableView->property(kEditItem).value<QQuickItem *>());
+ QVERIFY(!tableView->property(kEditIndex).value<QModelIndex>().isValid());
+ tableView->selectionModel()->setCurrentIndex(index1, QItemSelectionModel::NoUpdate);
+ QTest::keyClick(window, Qt::Key_Return);
+ QVERIFY(!tableView->property(kEditItem).value<QQuickItem *>());
+ QVERIFY(!tableView->property(kEditIndex).value<QModelIndex>().isValid());
+ QTest::keyClick(window, Qt::Key_Enter);
+ QVERIFY(!tableView->property(kEditItem).value<QQuickItem *>());
+ QVERIFY(!tableView->property(kEditIndex).value<QModelIndex>().isValid());
+ QTest::keyClick(window, Qt::Key_X);
+ QVERIFY(!tableView->property(kEditItem).value<QQuickItem *>());
+ QVERIFY(!tableView->property(kEditIndex).value<QModelIndex>().isValid());
+ }
+}
+
+void tst_QQuickTableView::editAndCloseEditor()
+{
+ // Check that the application can call edit() and closeEditor()
+ LOAD_TABLEVIEW("editdelegate.qml");
+
+ auto model = TestModel(4, 4);
+ tableView->setModel(QVariant::fromValue(&model));
+ tableView->forceActiveFocus();
+
+ const char kEditItem[] = "editItem";
+ const char kEditIndex[] = "editIndex";
+
+ WAIT_UNTIL_POLISHED;
+
+ const QPoint cell1(1, 1);
+ const QPoint cell2(2, 2);
+ const QModelIndex index1 = tableView->modelIndex(cell1);
+ const QModelIndex index2 = tableView->modelIndex(cell2);
+
+ const auto cellItem1 = tableView->itemAtCell(tableView->cellAtIndex(index1));
+ const auto cellItem2 = tableView->itemAtCell(tableView->cellAtIndex(index2));
+ QVERIFY(cellItem1);
+ QVERIFY(cellItem2);
+ QCOMPARE(cellItem1->property("editing").toBool(), false);
+ QCOMPARE(cellItem2->property("editing").toBool(), false);
+
+ // Edit cell 1
+ tableView->edit(index1);
+ QCOMPARE(tableView->selectionModel()->currentIndex(), index1);
+ const QQuickItem *editItem1 = tableView->property(kEditItem).value<QQuickItem *>();
+ QVERIFY(editItem1);
+ QVERIFY(editItem1->hasActiveFocus());
+ QCOMPARE(tableView->property(kEditIndex).value<QModelIndex>(), index1);
+ QCOMPARE(editItem1->parentItem(), cellItem1);
+ QCOMPARE(editItem1->property("editing").toBool(), true);
+ QCOMPARE(cellItem1->property("editing").toBool(), true);
+
+ // Edit cell 2
+ tableView->edit(index2);
+ QCOMPARE(tableView->selectionModel()->currentIndex(), index2);
+ const QQuickItem *editItem2 = tableView->property(kEditItem).value<QQuickItem *>();
+ QVERIFY(editItem2);
+ QVERIFY(editItem2->hasActiveFocus());
+ QCOMPARE(tableView->property(kEditIndex).value<QModelIndex>(), index2);
+ QCOMPARE(editItem2->parentItem(), cellItem2);
+ QCOMPARE(editItem2->property("editing").toBool(), true);
+ QCOMPARE(cellItem2->property("editing").toBool(), true);
+ QCOMPARE(cellItem1->property("editing").toBool(), false);
+
+ // Close the editor
+ tableView->closeEditor();
+ QCOMPARE(tableView->selectionModel()->currentIndex(), index2);
+ QVERIFY(!tableView->property(kEditItem).value<QQuickItem *>());
+ QVERIFY(!tableView->property(kEditIndex).value<QModelIndex>().isValid());
+ QCOMPARE(cellItem2->property("editing").toBool(), false);
+}
+
+void tst_QQuickTableView::editWarning_noEditDelegate()
+{
+ // Check that the TableView will print out a warning if the
+ // application calls edit() on a cell that has no editDelegate.
+ LOAD_TABLEVIEW("tableviewwithselected2.qml");
+
+ auto model = TestModel(4, 4);
+ tableView->setModel(QVariant::fromValue(&model));
+
+ WAIT_UNTIL_POLISHED;
+
+ QTest::ignoreMessage(QtWarningMsg, QRegularExpression(".*cannot edit: no TableView.editDelegate set!"));
+ tableView->edit(tableView->index(1, 1));
+}
+
+void tst_QQuickTableView::editWarning_invalidIndex()
+{
+ // Check that the TableView will print out a warning if the
+ // application calls edit() on an invalid index.
+ LOAD_TABLEVIEW("editdelegate.qml");
+
+ auto model = TestModel(4, 4);
+ tableView->setModel(QVariant::fromValue(&model));
+
+ WAIT_UNTIL_POLISHED;
+
+ QTest::ignoreMessage(QtWarningMsg, QRegularExpression(".*cannot edit: index is not valid!"));
+ tableView->edit(tableView->index(-1, -1));
+}
+
+void tst_QQuickTableView::editWarning_nonEditableModelItem()
+{
+ // Check that the TableView will print out a warning if the
+ // application calls edit() on cell that cannot, according
+ // to the model flags, be edited.
+ LOAD_TABLEVIEW("editdelegate.qml");
+
+ auto model = TestModel(4, 4);
+ tableView->setModel(QVariant::fromValue(&model));
+ // set flags that exclude Qt::ItemIsEditable
+ model.setFlags(Qt::ItemIsEnabled);
+
+ WAIT_UNTIL_POLISHED;
+
+ QTest::ignoreMessage(QtWarningMsg, QRegularExpression(".*cannot edit:.*flags.*Qt::ItemIsEditable"));
+ tableView->edit(tableView->index(1, 1));
+}
+
+void tst_QQuickTableView::attachedPropertiesOnEditDelegate()
+{
+ // Check that the TableView.commit signal is emitted when
+ // the user presses enter or return, but not when e.g pressing escape.
+ // Also check that TableView.view is correct.
+ LOAD_TABLEVIEW("editdelegate.qml");
+
+ auto model = TestModel(4, 4);
+ tableView->setModel(QVariant::fromValue(&model));
+ tableView->forceActiveFocus();
+
+ const char kEditItem[] = "editItem";
+ const char kEditIndex[] = "editIndex";
+
+ WAIT_UNTIL_POLISHED;
+
+ const QPoint cell(1, 1);
+ const QModelIndex index = tableView->modelIndex(cell);
+ QQuickWindow *window = tableView->window();
+
+ // Open the edit
+ tableView->edit(index);
+ QCOMPARE(tableView->property(kEditIndex).value<QModelIndex>(), index);
+ QQuickItem *editItem1 = tableView->property(kEditItem).value<QQuickItem *>();
+ QVERIFY(editItem1);
+ const auto attached1 = getAttachedObject(editItem1);
+ QVERIFY(attached1);
+ QSignalSpy commitSpy1(attached1, &QQuickTableViewAttached::commit);
+
+ // Check that TableView has been assigned to TableView.view
+ QCOMPARE(attached1->view(), tableView);
+
+ // Accept and close the edit, check commit signal
+ QTest::keyClick(window, Qt::Key_Enter);
+ QVERIFY(!tableView->property(kEditItem).value<QQuickItem *>());
+ QVERIFY(!tableView->property(kEditIndex).value<QModelIndex>().isValid());
+ QCOMPARE(commitSpy1.count(), 1);
+
+ // Repeat once more, but use Key_Return to accept instead
+ tableView->edit(index);
+ QCOMPARE(tableView->property(kEditIndex).value<QModelIndex>(), index);
+ QQuickItem *editItem2 = tableView->property(kEditItem).value<QQuickItem *>();
+ QVERIFY(editItem2);
+ const auto attached2 = getAttachedObject(editItem2);
+ QVERIFY(attached2);
+ QSignalSpy commitSpy2(attached2, &QQuickTableViewAttached::commit);
+
+ QTest::keyClick(window, Qt::Key_Return);
+ QVERIFY(!tableView->property(kEditItem).value<QQuickItem *>());
+ QVERIFY(!tableView->property(kEditIndex).value<QModelIndex>().isValid());
+ QCOMPARE(commitSpy1.count(), 1);
+ QCOMPARE(commitSpy2.count(), 1);
+
+ // Repeat once more, but use Key_Escape instead.
+ // This should close the edit, but without an accepted signal.
+ tableView->edit(index);
+ QCOMPARE(tableView->property(kEditIndex).value<QModelIndex>(), index);
+ QQuickItem *editItem3 = tableView->property(kEditItem).value<QQuickItem *>();
+ QVERIFY(editItem3);
+ const auto attached3 = getAttachedObject(editItem3);
+ QVERIFY(editItem3);
+ QSignalSpy commitSpy3(attached3, &QQuickTableViewAttached::commit);
+
+ QTest::keyClick(window, Qt::Key_Escape);
+ QVERIFY(!tableView->property(kEditItem).value<QQuickItem *>());
+ QVERIFY(!tableView->property(kEditIndex).value<QModelIndex>().isValid());
+ QCOMPARE(commitSpy3.count(), 0);
+
+ // Repeat once more, but tap outside the edit item.
+ // This should close the edit, but without an accepted signal.
+ tableView->edit(index);
+ QCOMPARE(tableView->property(kEditIndex).value<QModelIndex>(), index);
+ QQuickItem *editItem4 = tableView->property(kEditItem).value<QQuickItem *>();
+ QVERIFY(editItem4);
+ const auto attached4 = getAttachedObject(editItem4);
+ QVERIFY(editItem4);
+ QSignalSpy commitSpy4(attached4, &QQuickTableViewAttached::commit);
+
+ const QPoint tapPos = window->contentItem()->mapFromItem(editItem4, QPointF(-10, -10)).toPoint();
+ QTest::mouseClick(window, Qt::LeftButton, Qt::NoModifier, tapPos);
+ QVERIFY(!tableView->property(kEditItem).value<QQuickItem *>());
+ QVERIFY(!tableView->property(kEditIndex).value<QModelIndex>().isValid());
+ QCOMPARE(commitSpy4.count(), 0);
+}
+
+void tst_QQuickTableView::requiredPropertiesOnEditDelegate()
+{
+ // Check that all expected required properties on the edit
+ // delegate (like row, column, current) has correct values.
+ LOAD_TABLEVIEW("editdelegate.qml");
+
+ TestModel model(4, 4);
+ QItemSelectionModel selectionModel(&model);
+
+ tableView->setModel(QVariant::fromValue(&model));
+ tableView->setSelectionModel(&selectionModel);
+
+ const char kEditItem[] = "editItem";
+
+ WAIT_UNTIL_POLISHED;
+
+ const QPoint cell(1, 1);
+ const QModelIndex index1 = tableView->modelIndex(cell);
+ const QModelIndex index2 = tableView->index(2, 2);
+
+ tableView->edit(index1);
+
+ auto textInput = tableView->property(kEditItem).value<QQuickTextInput *>();
+ QVERIFY(textInput);
+ // Check that "text: display" in the edit delegate works
+ QCOMPARE(textInput->text(), "1");
+
+ QCOMPARE(textInput->property("current").toBool(), true);
+ QCOMPARE(textInput->property("selected").toBool(), false);
+ QCOMPARE(textInput->property("editing").toBool(), true);
+ selectionModel.select(index1, QItemSelectionModel::Select);
+ QCOMPARE(textInput->property("selected").toBool(), true);
+ selectionModel.setCurrentIndex(index2, QItemSelectionModel::Select);
+ QCOMPARE(textInput->property("current").toBool(), false);
+}
+
+void tst_QQuickTableView::resettingRolesRespected()
+{
+ LOAD_TABLEVIEW("resetModelData.qml");
+
+ TestModel model(1, 1);
+ tableView->setModel(QVariant::fromValue(&model));
+
+ WAIT_UNTIL_POLISHED;
+
+ QVERIFY(!tableView->property("success").toBool());
+ model.useCustomRoleNames(true);
+ QTRY_VERIFY(tableView->property("success").toBool());
+}
+
+void tst_QQuickTableView::checkScroll_data()
+{
+ QTest::addColumn<bool>("resizableColumns");
+ QTest::addColumn<bool>("resizableRows");
+ QTest::newRow("T, T") << true << true;
+ QTest::newRow("T, F") << true << false;
+ QTest::newRow("F, T") << false << true;
+ QTest::newRow("F, F") << false << false;
+}
+
+/*!
+ Make sure that the TableView is scrollable regardless
+ of the values of resizableColumns and resizableRows.
+*/
+void tst_QQuickTableView::checkScroll() // QTBUG-116566
+{
+ QFETCH(bool, resizableColumns);
+ QFETCH(bool, resizableRows);
+ LOAD_TABLEVIEW("plaintableview.qml"); // gives us 'tableView' variable
+
+ tableView->setResizableColumns(resizableColumns);
+ tableView->setResizableRows(resizableRows);
+
+ auto model = TestModelAsVariant(20, 10);
+ tableView->setModel(model);
+
+ WAIT_UNTIL_POLISHED;
+
+ // Scroll with the mouse wheel
+ sendWheelEvent(view, {10, 10}, {0, -120}, {0, -120});
+
+ // Check that scrolling succeeded
+ QTRY_COMPARE_GT(tableView->contentY(), 20);
+}
+
+void tst_QQuickTableView::checkRebuildJsModel()
+{
+ LOAD_TABLEVIEW("resetJsModelData.qml"); // gives us 'tableView' variable
+
+ // Generate javascript model
+ const int size = 5;
+ const char* modelUpdated = "modelUpdated";
+
+ QJSEngine jsEngine;
+ QJSValue jsArray;
+ jsArray = jsEngine.newArray(size);
+ for (int i = 0; i < size; ++i)
+ jsArray.setProperty(i, QRandomGenerator::global()->generate());
+
+ QVariant jsModel = QVariant::fromValue(jsArray);
+ tableView->setModel(jsModel);
+ WAIT_UNTIL_POLISHED;
+
+ // Model change would be triggered for the first time
+ QCOMPARE(tableView->property(modelUpdated).toInt(), 1);
+
+ // Set the same model once again and check if model changes
+ tableView->setModel(jsModel);
+ QCOMPARE(tableView->property(modelUpdated).toInt(), 1);
+}
+
QTEST_MAIN(tst_QQuickTableView)
#include "tst_qquicktableview.moc"