diff options
| author | Dilek Akcay <dilek.akcay@qt.io> | 2026-06-18 09:40:41 +0200 |
|---|---|---|
| committer | Dilek Akcay <dilek.akcay@qt.io> | 2026-09-23 06:25:46 +0000 |
| commit | ce122053fb922ee63cab5ca1b3141ffd075f3782 (patch) | |
| tree | a9e4c1bfee8aff946ba9218917be9818e76e21c7 /tests | |
| parent | d86bf0d533ea11bddebe5fcf2192b7a30ab83993 (diff) | |
QtQuick: Add sorting support to TableView and HorizontalHeaderView
Add sorting API to TableView, including sortingEnabled, sortColumn,
sortOrder, and sortByColumn(). Sorting requests initiated from
the header are handled by the synced TableView, which determines
when and how QAIM::sort() is called, while the model or proxy model
remains responsible for defining the actual sorting behavior.
HorizontalHeaderView now supports showing and clearing a sort indicator
for the synced view. The clearable indicator follows the same interaction
pattern as QHeaderView: clicking a section applies the default order,
clicking again toggles the order, and clicking a third time clears the
sort state when clearable sorting is enabled.
An auto test is added for sorting behavior and TestTableModelWithHeader
functions are updated to support the new test function.
[ChangeLog][QtQuick][TableView] Added API for sorting TableView columns
from HorizontalHeaderView. TableView now exposes sortingEnabled, sortColumn,
sortOrder, and sortByColumn(), and HorizontalHeaderView supports
clearable sort indicators for the synced view.
Task-number: QTBUG-147620
Change-Id: I0644b022f0dcb8d2c44d31f4f75be80af257426a
Reviewed-by: SanthoshKumar Selvaraj <santhosh.kumar.selvaraj@qt.io>
Diffstat (limited to 'tests')
6 files changed, 648 insertions, 3 deletions
diff --git a/tests/auto/quick/qquicktableview/data/sortByColumn.qml b/tests/auto/quick/qquicktableview/data/sortByColumn.qml new file mode 100644 index 0000000000..3629e1ba84 --- /dev/null +++ b/tests/auto/quick/qquicktableview/data/sortByColumn.qml @@ -0,0 +1,39 @@ +// Copyright (C) 2026 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick +import QtQuick.Window +import QtQuick.Controls +import TestModel 0.1 + +ApplicationWindow { + width: 400 + height: 400 + visible: true + + TableView { + id: tv + objectName: "tableView" + width: parent.width + height: parent.height + model: sortModel + } + + SortFilterProxyModel { + id: sortModel + objectName: "sortModel" + sourceModel: TestModel { + rowCount: 4 + columnCount: 2 + } + + sorters: [ + RoleSorter { + roleName: "display" + column: tv.sortColumn >= 0 ? tv.sortColumn : 0 + sortOrder: tv.sortOrder + enabled: tv.sortColumn >= 0 + } + ] + } +} diff --git a/tests/auto/quick/qquicktableview/tst_qquicktableview.cpp b/tests/auto/quick/qquicktableview/tst_qquicktableview.cpp index f579321785..674aff0373 100644 --- a/tests/auto/quick/qquicktableview/tst_qquicktableview.cpp +++ b/tests/auto/quick/qquicktableview/tst_qquicktableview.cpp @@ -31,6 +31,8 @@ #include <QtQuickTestUtils/private/viewtestutils_p.h> #include <QtQuickTestUtils/private/visualtestutils_p.h> +#include <QtQmlModels/private/qqmlsortfilterproxymodel_p.h> + using namespace QQuickViewTestUtils; using namespace QQuickVisualTestUtils; @@ -328,6 +330,11 @@ private slots: void delegateChooserDataChange(); void requiredRoleUpdatedAfterProxyModelResort(); + + void checkSortStateAfterModelChanged(); + + void sortByColumn_data(); + void sortByColumn(); }; tst_QQuickTableView::tst_QQuickTableView() @@ -8887,6 +8894,164 @@ void tst_QQuickTableView::delegateChooserDataChange() tableView->setModel({ }); } +void tst_QQuickTableView::checkSortStateAfterModelChanged() +{ + QQmlEngine engine; + QQmlComponent component(&engine, testFileUrl("sortByColumn.qml")); + + QScopedPointer<QObject> root(component.create()); + QVERIFY2(root, qPrintable(component.errorString())); + + auto *window = qobject_cast<QWindow *>(root.data()); + QVERIFY(QTest::qWaitForWindowActive(window)); + + auto *tv = root->findChild<QQuickTableView *>("tableView"); + QVERIFY(tv); + + auto *sortModel = tv->model().value<QQmlSortFilterProxyModel *>(); + QVERIFY2(sortModel, "The view's model type is not QQmlSortFilterProxyModel"); + + auto *sourceModel = qobject_cast<TestModel *>(sortModel->sourceModel()); + QVERIFY2(sourceModel, "The proxy model's source model is not TestModel"); + + sourceModel->setModelData(QPoint(0,0), QSize(1,1), QString("cat")); + sourceModel->setModelData(QPoint(0,1), QSize(1,1), QString("dog")); + sourceModel->setModelData(QPoint(0,2), QSize(1,1), QString("bird")); + sourceModel->setModelData(QPoint(0,3), QSize(1,1), QString("fish")); + + QSignalSpy sortColumnChangedSpy(tv, SIGNAL(sortColumnChanged())); + QVERIFY(sortColumnChangedSpy.isValid()); + + QSignalSpy sortOrderChangedSpy(tv, SIGNAL(sortOrderChanged())); + QVERIFY(sortOrderChangedSpy.isValid()); + + QCOMPARE(tv->sortColumn(), -1); + QCOMPARE(tv->sortOrder(), Qt::AscendingOrder); + + tv->sortByColumn(1, Qt::DescendingOrder); + + QCOMPARE(tv->sortColumn(), 1); + QCOMPARE(tv->sortOrder(), Qt::DescendingOrder); + QCOMPARE(sortColumnChangedSpy.size(), 1); + QCOMPARE(sortOrderChangedSpy.size(), 1); + + TestModel newModel(3, 2); + tv->setModel(QVariant::fromValue(&newModel)); + + QCOMPARE(tv->sortColumn(), -1); + QCOMPARE(tv->sortOrder(), Qt::AscendingOrder); + QCOMPARE(sortColumnChangedSpy.size(), 2); + QCOMPARE(sortOrderChangedSpy.size(), 2); +} + +void tst_QQuickTableView::sortByColumn_data() +{ + QTest::addColumn<bool>("sortingEnabled"); + QTest::addColumn<int>("column"); + QTest::addColumn<Qt::SortOrder>("order"); + QTest::addColumn<QStringList>("modelAfterSort"); + + QTest::addColumn<int>("sortColumnChangedCountAfterFirstCall"); + QTest::addColumn<int>("sortOrderChangedCountAfterFirstCall"); + + QTest::addColumn<int>("sortColumnChangedCountAfterSecondCall"); + QTest::addColumn<int>("sortOrderChangedCountAfterSecondCall"); + + const QStringList ascendingOrder = { "bird", "cat", "dog", "fish"}; + const QStringList descendingOrder = { "fish", "dog", "cat", "bird"}; + + QTest::newRow("disabled, ascending") + << false + << 0 << Qt::AscendingOrder << ascendingOrder + << 1 << 0 + << 1 << 0; + + QTest::newRow("disabled, descending") + << false + << 0 << Qt::DescendingOrder << descendingOrder + << 1 << 1 + << 1 << 1; + + QTest::newRow("enabled, descending") + << true + << 0 << Qt::DescendingOrder << descendingOrder + << 1 << 1 + << 1 << 1; +} + +void tst_QQuickTableView::sortByColumn() +{ + QFETCH(bool, sortingEnabled); + QFETCH(int, column); + QFETCH(Qt::SortOrder, order); + QFETCH(QStringList, modelAfterSort); + QFETCH(int, sortColumnChangedCountAfterFirstCall); + QFETCH(int, sortOrderChangedCountAfterFirstCall); + QFETCH(int, sortColumnChangedCountAfterSecondCall); + QFETCH(int, sortOrderChangedCountAfterSecondCall); + + QQmlEngine engine; + QQmlComponent component(&engine, testFileUrl("sortByColumn.qml")); + + QScopedPointer<QObject> root(component.create()); + QVERIFY2(root, qPrintable(component.errorString())); + + auto *window = qobject_cast<QWindow *>(root.data()); + QVERIFY(QTest::qWaitForWindowActive(window)); + + auto *tv = root->findChild<QQuickTableView *>("tableView"); + QVERIFY(tv); + + auto *sortModel = tv->model().value<QQmlSortFilterProxyModel *>(); + QVERIFY2(sortModel, "The view's model type is not QQmlSortFilterProxyModel"); + + auto *sourceModel = qobject_cast<TestModel *>(sortModel->sourceModel()); + QVERIFY2(sourceModel, "The proxy model's source model is not TestModel"); + + sourceModel->setModelData(QPoint(0,0), QSize(1,1), QString("cat")); + sourceModel->setModelData(QPoint(0,1), QSize(1,1), QString("dog")); + sourceModel->setModelData(QPoint(0,2), QSize(1,1), QString("bird")); + sourceModel->setModelData(QPoint(0,3), QSize(1,1), QString("fish")); + + auto modelColumnData = [](QAbstractItemModel *model, int column) { + QStringList list; + for (int row = 0; row < model->rowCount(); ++row) + list << model->data(model->index(row, column), Qt::DisplayRole).toString(); + return list; + }; + + const QStringList defaultOrder = { "cat", "dog", "bird", "fish"}; + + QSignalSpy sortColumnChangedSpy(tv, SIGNAL(sortColumnChanged())); + QVERIFY(sortColumnChangedSpy.isValid()); + + QSignalSpy sortOrderChangedSpy(tv, SIGNAL(sortOrderChanged())); + QVERIFY(sortOrderChangedSpy.isValid()); + + tv->setSortingEnabled(sortingEnabled); + QCOMPARE(tv->sortingEnabled(), sortingEnabled); + + QCOMPARE(modelColumnData(sortModel, 0), defaultOrder); + QCOMPARE(tv->sortColumn(), -1); + QCOMPARE(tv->sortOrder(), Qt::AscendingOrder); + + tv->sortByColumn(column, order); + + QCOMPARE(tv->sortColumn(), column); + QCOMPARE(tv->sortOrder(), order); + QCOMPARE(modelColumnData(sortModel, 0), modelAfterSort); + QCOMPARE(sortColumnChangedSpy.size(), sortColumnChangedCountAfterFirstCall); + QCOMPARE(sortOrderChangedSpy.size(), sortOrderChangedCountAfterFirstCall); + + tv->sortByColumn(column, order); + + QCOMPARE(tv->sortColumn(), column); + QCOMPARE(tv->sortOrder(), order); + QCOMPARE(modelColumnData(sortModel, 0), modelAfterSort); + QCOMPARE(sortColumnChangedSpy.size(), sortColumnChangedCountAfterSecondCall); + QCOMPARE(sortOrderChangedSpy.size(), sortOrderChangedCountAfterSecondCall); +} + void tst_QQuickTableView::verifyThatOnlyRemovedRowsArePooled() { // Check that we don't put delegate items above or below a removed or inserted row diff --git a/tests/auto/quickcontrols/qquickheaderview/data/horizontalHeaderClickedWithoutSyncView.qml b/tests/auto/quickcontrols/qquickheaderview/data/horizontalHeaderClickedWithoutSyncView.qml new file mode 100644 index 0000000000..87ee22166d --- /dev/null +++ b/tests/auto/quickcontrols/qquickheaderview/data/horizontalHeaderClickedWithoutSyncView.qml @@ -0,0 +1,19 @@ +// Copyright (C) 2026 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick +import QtQuick.Window +import QtQuick.Controls + +ApplicationWindow { + width: 400 + height: 400 + visible: true + + HorizontalHeaderView { + id: hh + objectName: "horizontalHeader" + implicitWidth: 40 + model: 3 + } +} diff --git a/tests/auto/quickcontrols/qquickheaderview/data/sorting.qml b/tests/auto/quickcontrols/qquickheaderview/data/sorting.qml new file mode 100644 index 0000000000..ec9e0be509 --- /dev/null +++ b/tests/auto/quickcontrols/qquickheaderview/data/sorting.qml @@ -0,0 +1,71 @@ +// Copyright (C) 2026 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick +import QtQuick.Window +import QtQuick.Controls +import TestTableModelWithHeader + +Window { + objectName: "sorting" + width: 400 + height: 400 + visible: true + + TestTableModelWithHeader { + id: tableHeaderModel + objectName: "tableHeaderModel" + rowCount: 4 + columnCount: 2 + } + + HorizontalHeaderView { + id: hh + objectName: "horizontalHeader" + anchors.top: parent.top + x: vh.width + syncView: tv + showSortIndicator: true + sortIndicatorClearable: true + } + + VerticalHeaderView { + id: vh + objectName: "verticalHeader" + anchors.left: parent.left + y: hh.height + syncView: tv + } + + TableView { + id: tv + objectName: "tableView" + anchors.top: hh.bottom + anchors.left: vh.right + anchors.right: parent.right + anchors.bottom: parent.bottom + model: sortModel + sortingEnabled: true + delegate: Rectangle { + implicitHeight: 25 + implicitWidth: 50 + color: "red" + Text { + text: model.display + } + } + } + + SortFilterProxyModel { + id: sortModel + sourceModel: tableHeaderModel + sorters: [ + RoleSorter { + roleName: "display" + column: tv.sortColumn >= 0 ? tv.sortColumn : 0 + sortOrder: tv.sortOrder + enabled: tv.sortColumn >= 0 + } + ] + } +} diff --git a/tests/auto/quickcontrols/qquickheaderview/data/sortingAfterColumnMoved.qml b/tests/auto/quickcontrols/qquickheaderview/data/sortingAfterColumnMoved.qml new file mode 100644 index 0000000000..b8ce354055 --- /dev/null +++ b/tests/auto/quickcontrols/qquickheaderview/data/sortingAfterColumnMoved.qml @@ -0,0 +1,60 @@ +// Copyright (C) 2026 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick +import QtQuick.Window +import QtQuick.Controls +import TestTableModelWithHeader + +ApplicationWindow { + width: 400 + height: 400 + visible: true + + TestTableModelWithHeader { + id: tableHeaderModel + objectName: "tableHeaderModel" + rowCount: 2 + columnCount: 2 + } + + HorizontalHeaderView { + id: hh + objectName: "horizontalHeader" + width: 200 + height: 25 + syncView: tv + showSortIndicator: true + movableColumns: true + } + + TableView { + id: tv + objectName: "tableView" + y: hh.height + width: 200 + height: 100 + model: sortModel + sortingEnabled: true + delegate: Rectangle { + implicitHeight: 25 + implicitWidth: 50 + Text { + text: model.display + } + } + } + + SortFilterProxyModel { + id: sortModel + sourceModel: tableHeaderModel + sorters: [ + RoleSorter { + roleName: "display" + column: tv.sortColumn >= 0 ? tv.sortColumn : 0 + sortOrder: tv.sortOrder + enabled: tv.sortColumn >= 0 + } + ] + } +} diff --git a/tests/auto/quickcontrols/qquickheaderview/tst_qquickheaderview.cpp b/tests/auto/quickcontrols/qquickheaderview/tst_qquickheaderview.cpp index 8d7db61c41..be9cfd976e 100644 --- a/tests/auto/quickcontrols/qquickheaderview/tst_qquickheaderview.cpp +++ b/tests/auto/quickcontrols/qquickheaderview/tst_qquickheaderview.cpp @@ -18,6 +18,7 @@ #include <QtQuickTemplates2/private/qquicklabel_p.h> #include <private/qquickheaderview_p_p.h> #include <private/qquickheaderviewdelegate_p.h> +#include <QtQmlModels/private/qqmlsortfilterproxymodel_p.h> using namespace QQuickVisualTestUtils; @@ -143,12 +144,21 @@ public: void setRowCount(int count) override { vData.resize(count); + tableData.resize(count); + + for (auto &row : tableData) + row.resize(columnCount()); + TestTableModel::setRowCount(count); } void setColumnCount(int count) override { hData.resize(count); + + for (auto &row : tableData) + row.resize(count); + TestTableModel::setColumnCount(count); } @@ -157,11 +167,41 @@ public: switch (role) { case CustomRole: return QString("%1-%2").arg(index.column()).arg(index.row()); + case Qt::DisplayRole: + if (index.row() >= 0 && index.row() < tableData.size() + && index.column() >= 0 && index.column() < tableData.at(index.row()).size() + && tableData.at(index.row()).at(index.column()).isValid()) { + return tableData.at(index.row()).at(index.column()); + } default: return TestTableModel::data(index, role); } } + bool setData(const QModelIndex &index, const QVariant &value, + int role = Qt::EditRole) override + { + if (!index.isValid()) + return false; + + if (role == Qt::DisplayRole) { + if (index.row() < 0 || index.row() >= tableData.size()) + return false; + + if (index.column() < 0 || index.column() >= tableData.at(index.row()).size()) + return false; + + if (tableData[index.row()][index.column()] == value) + return false; + + tableData[index.row()][index.column()] = value; + emit dataChanged(index, index, { role }); + return true; + } + + return TestTableModel::setData(index, value, role); + } + Q_INVOKABLE QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override { @@ -174,12 +214,17 @@ public: auto &data = orientation == Qt::Horizontal ? hData : vData; return data[section].toString(); } + case Qt::InitialSortOrderRole: + if (orientation == Qt::Horizontal) + return horizontalSortOrder.value(section); + return QVariant(); case CustomRole: return (orientation == Qt::Horizontal ? "c" : "r") + QString::number(section); default: return QVariant(); } } + Q_INVOKABLE bool setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role = Qt::EditRole) override { @@ -191,6 +236,22 @@ public: auto sectionCount = orientation == Qt::Horizontal ? columnCount() : rowCount(); if (section < 0 || section >= sectionCount) return false; + if (role == Qt::InitialSortOrderRole) { + if (orientation != Qt::Horizontal) + return false; + + if (horizontalSortOrder.value(section) == value) + return false; + + if (value.isValid()) + horizontalSortOrder.insert(section, value); + else + horizontalSortOrder.remove(section); + + emit headerDataChanged(orientation, section, section); + return true; + } + auto &data = orientation == Qt::Horizontal ? hData : vData; data[section] = value; emit headerDataChanged(orientation, section, section); @@ -206,6 +267,8 @@ public: private: QList<QVariant> hData, vData; + QHash<int, QVariant> horizontalSortOrder; + QVector<QVector<QVariant>> tableData; }; class tst_QQuickHeaderView : public QQmlDataTest { @@ -235,6 +298,10 @@ private slots: void horizontalHeaderViewWithListModel_data(); void horizontalHeaderViewWithListModel(); void reorderEmptyModel(); + void horizontalHeaderClickedWithoutSyncView(); + void sorting_data(); + void sorting(); + void sortingAfterColumnMoved(); private: QQmlEngine *engine; @@ -463,9 +530,10 @@ void tst_QQuickHeaderView::headerData() QVERIFY(headerView); const auto firstHeaderCell = headerView->itemAtIndex(headerView->index(0, 0)); QVERIFY(firstHeaderCell); - const auto label = firstHeaderCell->findChild<QQuickLabel *>(); - QVERIFY(label); - QCOMPARE(label->text(), "c0"); + const auto delegate = qobject_cast<QQuickHeaderViewDelegate *>(firstHeaderCell); + QVERIFY(delegate); + QVERIFY(delegate->contentItem()); + QCOMPARE(delegate->contentItem()->property("text").toString(), QStringLiteral("c0")); } void tst_QQuickHeaderView::warnMissingDefaultRole() @@ -567,6 +635,229 @@ void tst_QQuickHeaderView::reorderEmptyModel() QVERIFY(!columnMovedSpy.isEmpty()); } +void tst_QQuickHeaderView::horizontalHeaderClickedWithoutSyncView() +{ + QQmlComponent component(engine); + component.loadUrl(testFileUrl("horizontalHeaderClickedWithoutSyncView.qml")); + + QScopedPointer<QObject> root(component.create()); + QVERIFY2(root, qPrintable(component.errorString())); + + auto *window = qobject_cast<QWindow *>(root.data()); + QVERIFY(QTest::qWaitForWindowActive(window)); + + auto hh = root->findChild<QQuickHorizontalHeaderView *>("horizontalHeader"); + QVERIFY(hh); + + QSignalSpy headerClickedSpy(hh, SIGNAL(headerClicked(int))); + QVERIFY(headerClickedSpy.isValid()); + + const QPointF localPos = QPointF(hh->width() / 6, hh->height() / 2); + const QPoint point = hh->mapToScene(localPos).toPoint(); + QTest::mouseClick(window, Qt::LeftButton, Qt::NoModifier, point); + + QCOMPARE(headerClickedSpy.size(), 1); +} + +void tst_QQuickHeaderView::sorting_data() +{ + QTest::addColumn<bool>("sortIndicatorClearable"); + QTest::addColumn<bool>("sortingEnabled"); + QTest::addColumn<QVariant>("initialSortOrder"); + + QTest::addColumn<int>("sortColumnAfterFirstClick"); + QTest::addColumn<Qt::SortOrder>("sortOrderAfterFirstClick"); + QTest::addColumn<QStringList>("modelAfterFirstClick"); + + QTest::addColumn<int>("sortColumnAfterSecondClick"); + QTest::addColumn<Qt::SortOrder>("sortOrderAfterSecondClick"); + QTest::addColumn<QStringList>("modelAfterSecondClick"); + + QTest::addColumn<int>("sortColumnAfterThirdClick"); + QTest::addColumn<Qt::SortOrder>("sortOrderAfterThirdClick"); + QTest::addColumn<QStringList>("modelAfterThirdClick"); + + const QStringList defaultOrder = { "cat", "dog", "bird", "fish"}; + const QStringList ascendingOrder = { "bird", "cat", "dog", "fish"}; + const QStringList descendingOrder = { "fish", "dog", "cat", "bird"}; + + QTest::newRow("clearable, enabled, default sortOrder") + << true << true << QVariant() + << 0 << Qt::AscendingOrder << ascendingOrder + << 0 << Qt::DescendingOrder << descendingOrder + << -1 << Qt::AscendingOrder << defaultOrder; + + QTest::newRow("not clearable, enabled, default sortOrder") + << false << true << QVariant() + << 0 << Qt::AscendingOrder << ascendingOrder + << 0 << Qt::DescendingOrder << descendingOrder + << 0 << Qt::AscendingOrder << ascendingOrder; + + QTest::newRow("clearable, disabled, default sortOrder") + << true << false << QVariant() + << -1 << Qt::AscendingOrder << defaultOrder + << -1 << Qt::AscendingOrder << defaultOrder + << -1 << Qt::AscendingOrder << defaultOrder; + + QTest::newRow("clearable, enabled, initial descending sortOrder") + << true << true << QVariant::fromValue(int(Qt::DescendingOrder)) + << 0 << Qt::DescendingOrder << descendingOrder + << 0 << Qt::AscendingOrder << ascendingOrder + << -1 << Qt::AscendingOrder << defaultOrder; +} + +void tst_QQuickHeaderView::sorting() +{ + QFETCH(bool, sortIndicatorClearable); + QFETCH(bool, sortingEnabled); + QFETCH(QVariant, initialSortOrder); + QFETCH(int, sortColumnAfterFirstClick); + QFETCH(Qt::SortOrder, sortOrderAfterFirstClick); + QFETCH(QStringList, modelAfterFirstClick); + QFETCH(int, sortColumnAfterSecondClick); + QFETCH(Qt::SortOrder, sortOrderAfterSecondClick); + QFETCH(QStringList, modelAfterSecondClick); + QFETCH(int, sortColumnAfterThirdClick); + QFETCH(Qt::SortOrder, sortOrderAfterThirdClick); + QFETCH(QStringList, modelAfterThirdClick); + + QQmlComponent component(engine); + component.loadUrl(testFileUrl("sorting.qml")); + + QScopedPointer<QObject> root(component.create()); + QVERIFY2(root, qPrintable(component.errorString())); + + auto *window = qobject_cast<QWindow *>(root.data()); + QVERIFY(QTest::qWaitForWindowActive(window)); + + auto hh = root->findChild<QQuickHorizontalHeaderView *>("horizontalHeader"); + QVERIFY(hh); + + auto *tv = root->findChild<QQuickTableView *>("tableView"); + QVERIFY(tv); + + hh->setSortIndicatorClearable(sortIndicatorClearable); + tv->setSortingEnabled(sortingEnabled); + + auto *sortModel = tv->model().value<QQmlSortFilterProxyModel *>(); + QVERIFY2(sortModel, "The view's model type is not QQmlSortFilterProxyModel"); + auto *tableModel = qobject_cast<TestTableModelWithHeader *>(sortModel->sourceModel()); + QVERIFY2(tableModel, "The sort model's source model is not TestTableModelWithHeader"); + + tableModel->setHeaderData(0, Qt::Horizontal, QStringLiteral("Name"), Qt::DisplayRole); + tableModel->setHeaderData(1, Qt::Horizontal, QStringLiteral("Color"), Qt::DisplayRole); + + if (initialSortOrder.isValid()) + tableModel->setHeaderData(0, Qt::Horizontal, initialSortOrder, Qt::InitialSortOrderRole); + + tableModel->setHeaderData(0, Qt::Vertical, QStringLiteral("1"), Qt::DisplayRole); + tableModel->setHeaderData(1, Qt::Vertical, QStringLiteral("2"), Qt::DisplayRole); + tableModel->setHeaderData(2, Qt::Vertical, QStringLiteral("3"), Qt::DisplayRole); + tableModel->setHeaderData(3, Qt::Vertical, QStringLiteral("4"), Qt::DisplayRole); + + tableModel->setData(tableModel->index(0,0), "cat", Qt::DisplayRole); + tableModel->setData(tableModel->index(0,1), "black", Qt::DisplayRole); + tableModel->setData(tableModel->index(1,0), "dog", Qt::DisplayRole); + tableModel->setData(tableModel->index(1,1), "brown", Qt::DisplayRole); + tableModel->setData(tableModel->index(2,0), "bird", Qt::DisplayRole); + tableModel->setData(tableModel->index(2,1), "white", Qt::DisplayRole); + tableModel->setData(tableModel->index(3,0), "fish", Qt::DisplayRole); + tableModel->setData(tableModel->index(3,1), "gold", Qt::DisplayRole); + + QCOMPARE(tableModel->headerData(0, Qt::Horizontal, Qt::DisplayRole).toString(), "Name"); + QCOMPARE(tableModel->data(tableModel->index(0,0), Qt::DisplayRole).toString(), "cat"); + + QCOMPARE(tableModel->rowCount(), 4); + QCOMPARE(tableModel->columnCount(), 2); + + QCOMPARE(tv->sortColumn(), -1); + QCOMPARE(tv->sortOrder(), Qt::AscendingOrder); + + auto modelColumnData = [](QAbstractItemModel *model, int column) { + QStringList list; + for (int row = 0; row < model->rowCount(); ++row) + list << model->data(model->index(row, column), Qt::DisplayRole).toString(); + return list; + }; + + const QPointF localPos = QPointF(hh->columnWidth(0) / 2, hh->height() / 2); + const QPoint pos = hh->mapToScene(localPos).toPoint(); + + QTest::mouseClick(window, Qt::LeftButton, Qt::NoModifier, pos); + QCOMPARE(tv->sortColumn(), sortColumnAfterFirstClick); + QCOMPARE(tv->sortOrder(), sortOrderAfterFirstClick); + QCOMPARE(modelColumnData(sortModel, 0), modelAfterFirstClick); + + QTest::mouseClick(window, Qt::LeftButton, Qt::NoModifier, pos); + QCOMPARE(tv->sortColumn(), sortColumnAfterSecondClick); + QCOMPARE(tv->sortOrder(), sortOrderAfterSecondClick); + QCOMPARE(modelColumnData(sortModel, 0), modelAfterSecondClick); + + QTest::mouseClick(window, Qt::LeftButton, Qt::NoModifier, pos); + QCOMPARE(tv->sortColumn(), sortColumnAfterThirdClick); + QCOMPARE(tv->sortOrder(), sortOrderAfterThirdClick); + QCOMPARE(modelColumnData(sortModel, 0), modelAfterThirdClick); +} + +void tst_QQuickHeaderView::sortingAfterColumnMoved() +{ + QQmlComponent component(engine); + component.loadUrl(testFileUrl("sortingAfterColumnMoved.qml")); + + QScopedPointer<QObject> root(component.create()); + QVERIFY2(root, qPrintable(component.errorString())); + + auto *window = qobject_cast<QWindow *>(root.data()); + QVERIFY(QTest::qWaitForWindowActive(window)); + + auto hh = root->findChild<QQuickHorizontalHeaderView *>("horizontalHeader"); + QVERIFY(hh); + + auto *tv = root->findChild<QQuickTableView *>("tableView"); + QVERIFY(tv); + + hh->setSortIndicatorClearable(true); + tv->setSortingEnabled(true); + + auto *sortModel = tv->model().value<QQmlSortFilterProxyModel *>(); + QVERIFY2(sortModel, "The view's model type is not QQmlSortFilterProxyModel"); + auto *tableModel = qobject_cast<TestTableModelWithHeader *>(sortModel->sourceModel()); + QVERIFY2(tableModel, "The sort model's source model is not TestTableModelWithHeader"); + + tableModel->setHeaderData(0, Qt::Horizontal, QStringLiteral("Name"), Qt::DisplayRole); + tableModel->setHeaderData(1, Qt::Horizontal, QStringLiteral("Color"), Qt::DisplayRole); + + tableModel->setHeaderData(0, Qt::Vertical, QStringLiteral("1"), Qt::DisplayRole); + tableModel->setHeaderData(1, Qt::Vertical, QStringLiteral("2"), Qt::DisplayRole); + + tableModel->setData(tableModel->index(0, 0), QStringLiteral("cat"), Qt::DisplayRole); + tableModel->setData(tableModel->index(0, 1), QStringLiteral("brown"), Qt::DisplayRole); + tableModel->setData(tableModel->index(1, 0), QStringLiteral("dog"), Qt::DisplayRole); + tableModel->setData(tableModel->index(1, 1), QStringLiteral("black"), Qt::DisplayRole); + + QCOMPARE(tableModel->headerData(0, Qt::Horizontal, Qt::DisplayRole).toString(), "Name"); + QCOMPARE(tableModel->data(tableModel->index(0,0), Qt::DisplayRole).toString(), "cat"); + + QCOMPARE(tableModel->rowCount(), 2); + QCOMPARE(tableModel->columnCount(), 2); + + QCOMPARE(tv->sortColumn(), -1); + QCOMPARE(tv->sortOrder(), Qt::AscendingOrder); + + QSignalSpy columnMovedSpy(hh, SIGNAL(columnMoved(int,int,int))); + QVERIFY(columnMovedSpy.isValid()); + hh->moveColumn(0, 1); + QVERIFY(!columnMovedSpy.isEmpty()); + + const QPointF localPos = QPointF(hh->columnWidth(0) / 2, hh->height() / 2); + const QPoint pos = hh->mapToScene(localPos).toPoint(); + + QTest::mouseClick(window, Qt::LeftButton, Qt::NoModifier, pos); + QCOMPARE(tv->sortColumn(), 1); + QCOMPARE(tv->sortOrder(), Qt::AscendingOrder); + QCOMPARE(sortModel->data(sortModel->index(0,1), Qt::DisplayRole).toString(), "black"); +} + QTEST_MAIN(tst_QQuickHeaderView) #include "tst_qquickheaderview.moc" |
