1/****************************************************************************
2**
3** Copyright (C) 2020 The Qt Company Ltd.
4** Contact: http://www.qt.io/licensing/
5**
6** This file is part of the test suite of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL3$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see http://www.qt.io/terms-conditions. For further
15** information use the contact form at http://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPLv3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or later as published by the Free
28** Software Foundation and appearing in the file LICENSE.GPL included in
29** the packaging of this file. Please review the following information to
30** ensure the GNU General Public License version 2.0 requirements will be
31** met: http://www.gnu.org/licenses/gpl-2.0.html.
32**
33** $QT_END_LICENSE$
34**
35****************************************************************************/
36
37#include "../shared/qtest_quickcontrols.h"
38#include "../shared/util.h"
39#include <QtTest/qsignalspy.h>
40#include <QtTest/qtest.h>
41
42#include <QAbstractItemModelTester>
43#include <QtQml/QQmlEngine>
44#include <QtQuick/private/qquickwindow_p.h>
45#include <QtQuick/private/qquicktext_p.h>
46#include <QtQuickTemplates2/private/qquickapplicationwindow_p.h>
47#include <QtQuickTemplates2/private/qquickheaderview_p.h>
48#include <private/qquickheaderview_p_p.h>
49
50class TestTableModel : public QAbstractTableModel {
51 Q_OBJECT
52 Q_PROPERTY(int rowCount READ rowCount WRITE setRowCount NOTIFY rowCountChanged)
53 Q_PROPERTY(int columnCount READ columnCount WRITE setColumnCount NOTIFY columnCountChanged)
54
55public:
56 TestTableModel(QObject *parent = nullptr)
57 : QAbstractTableModel(parent)
58 {
59 }
60
61 int rowCount(const QModelIndex &index = QModelIndex()) const override
62 {
63 if (index.isValid())
64 return 0;
65 return m_rows;
66 }
67 virtual void setRowCount(int count)
68 {
69 beginResetModel();
70 m_rows = count;
71 emit rowCountChanged();
72 endResetModel();
73 }
74
75 int columnCount(const QModelIndex &index = QModelIndex()) const override
76 {
77 if (index.isValid())
78 return 0;
79 return m_cols;
80 }
81 virtual void setColumnCount(int count)
82 {
83 beginResetModel();
84 m_cols = count;
85 emit columnCountChanged();
86 endResetModel();
87 }
88
89 int indexValue(const QModelIndex &index) const
90 {
91 return index.row() + (index.column() * rowCount());
92 }
93
94 Q_INVOKABLE QModelIndex toQModelIndex(int serialIndex)
95 {
96 return createIndex(arow: serialIndex % rowCount(), acolumn: serialIndex / rowCount());
97 }
98
99 Q_INVOKABLE QVariant data(int row, int col)
100 {
101 return data(index: createIndex(arow: row, acolumn: col), role: Qt::DisplayRole);
102 }
103 QVariant data(const QModelIndex &index, int role) const override
104 {
105 if (!index.isValid())
106 return QVariant();
107
108 switch (role) {
109 case Qt::DisplayRole:
110 return QString("%1, %2, checked: %3 ")
111 .arg(a: index.row())
112 .arg(a: index.column())
113 .arg(a: m_checkedCells.contains(value: indexValue(index)));
114 case Qt::EditRole:
115 return m_checkedCells.contains(value: indexValue(index));
116 default:
117 return QVariant();
118 }
119 }
120
121 bool setData(const QModelIndex &index, const QVariant &value,
122 int role = Qt::EditRole) override
123 {
124
125 if (role != Qt::EditRole)
126 return false;
127
128 int i = indexValue(index);
129 bool checked = value.toBool();
130 if (checked == m_checkedCells.contains(value: i))
131 return false;
132
133 if (checked)
134 m_checkedCells.insert(value: i);
135 else
136 m_checkedCells.remove(value: i);
137
138 emit dataChanged(topLeft: index, bottomRight: index, roles: { role });
139 return true;
140 }
141
142 Q_INVOKABLE QHash<int, QByteArray> roleNames() const override
143 {
144 return {
145 { Qt::DisplayRole, "display" },
146 { Qt::EditRole, "edit" }
147 };
148 }
149
150signals:
151 void rowCountChanged();
152 void columnCountChanged();
153
154private:
155 int m_rows = 0;
156 int m_cols = 0;
157
158 QSet<int> m_checkedCells;
159};
160
161class TestTableModelWithHeader : public TestTableModel {
162
163 Q_OBJECT
164public:
165 void setRowCount(int count) override
166 {
167 vData.resize(size: count);
168 TestTableModel::setRowCount(count);
169 }
170
171 void setColumnCount(int count) override
172 {
173 hData.resize(size: count);
174 TestTableModel::setColumnCount(count);
175 }
176 Q_INVOKABLE QVariant headerData(int section, Qt::Orientation orientation,
177 int role = Qt::DisplayRole) const override
178 {
179 auto sectionCount = orientation == Qt::Horizontal ? columnCount() : rowCount();
180 if (section < 0 || section >= sectionCount)
181 return QVariant();
182 switch (role) {
183 case Qt::DisplayRole:
184 case Qt::EditRole: {
185 auto &data = orientation == Qt::Horizontal ? hData : vData;
186 return data[section].toString();
187 }
188 default:
189 return QVariant();
190 }
191 }
192 Q_INVOKABLE bool setHeaderData(int section, Qt::Orientation orientation,
193 const QVariant &value, int role = Qt::EditRole) override
194 {
195 qDebug() << Q_FUNC_INFO
196 << "section:" << section
197 << "orient:" << orientation
198 << "value:" << value
199 << "role:" << QAbstractItemModel::roleNames()[role];
200 auto sectionCount = orientation == Qt::Horizontal ? columnCount() : rowCount();
201 if (section < 0 || section >= sectionCount)
202 return false;
203 auto &data = orientation == Qt::Horizontal ? hData : vData;
204 data[section] = value;
205 emit headerDataChanged(orientation, first: section, last: section);
206 return true;
207 }
208
209private:
210 QVector<QVariant> hData, vData;
211};
212
213class tst_QQuickHeaderView : public QQmlDataTest {
214 Q_OBJECT
215
216private slots:
217 void initTestCase() override;
218 void cleanupTestCase();
219 void init();
220 void cleanup();
221
222 void defaults();
223 void testHeaderDataProxyModel();
224 void testOrientation();
225 void testModel();
226 void listModel();
227
228private:
229 QQmlEngine *engine;
230 QString errorString;
231
232 std::unique_ptr<QObject> rootObjectFromQml(const char *file)
233 {
234 auto component = new QQmlComponent(engine);
235 component->loadUrl(url: testFileUrl(fileName: file));
236 auto root = component->create();
237 if (!root)
238 errorString = component->errorString();
239 return std::unique_ptr<QObject>(new QObject(root));
240 }
241};
242
243void tst_QQuickHeaderView::initTestCase()
244{
245 QQmlDataTest::initTestCase();
246 qmlRegisterType<TestTableModel>(uri: "TestTableModel", versionMajor: 0, versionMinor: 1, qmlName: "TestTableModel");
247 qmlRegisterType<TestTableModelWithHeader>(uri: "TestTableModelWithHeader", versionMajor: 0, versionMinor: 1, qmlName: "TestTableModelWithHeader");
248 qmlRegisterType<QHeaderDataProxyModel>(uri: "HeaderDataProxyModel", versionMajor: 0, versionMinor: 1, qmlName: "HeaderDataProxyModel");
249}
250
251void tst_QQuickHeaderView::cleanupTestCase()
252{
253}
254
255void tst_QQuickHeaderView::init()
256{
257 engine = new QQmlEngine(this);
258}
259
260void tst_QQuickHeaderView::cleanup()
261{
262 if (engine) {
263 delete engine;
264 engine = nullptr;
265 }
266}
267
268void tst_QQuickHeaderView::defaults()
269{
270 QQmlComponent component(engine);
271 component.loadUrl(url: testFileUrl(fileName: "Window.qml"));
272
273 QScopedPointer<QObject> root(component.create());
274 QVERIFY2(root, qPrintable(component.errorString()));
275
276 auto hhv = root->findChild<QQuickHorizontalHeaderView *>(aName: "horizontalHeader");
277 QVERIFY(hhv);
278 auto vhv = root->findChild<QQuickVerticalHeaderView *>(aName: "verticalHeader");
279 QVERIFY(vhv);
280 auto tm = root->findChild<TestTableModel *>(aName: "tableModel");
281 QVERIFY(tm);
282 auto pm = root->findChild<QHeaderDataProxyModel *>(aName: "proxyModel");
283 QVERIFY(pm);
284 auto tv = root->findChild<QQuickTableView *>(aName: "tableView");
285 QVERIFY(tv);
286}
287
288void tst_QQuickHeaderView::testHeaderDataProxyModel()
289{
290 TestTableModel model;
291 model.setColumnCount(10);
292 model.setRowCount(7);
293 QHeaderDataProxyModel model2;
294 model2.setSourceModel(&model);
295 QAbstractItemModelTester tester(&model2, QAbstractItemModelTester::FailureReportingMode::QtTest);
296}
297
298void tst_QQuickHeaderView::testOrientation()
299{
300 QQmlComponent component(engine);
301 component.loadUrl(url: testFileUrl(fileName: "Window.qml"));
302
303 QScopedPointer<QObject> root(component.create());
304 QVERIFY2(root, qPrintable(component.errorString()));
305
306 auto hhv = root->findChild<QQuickHorizontalHeaderView *>(aName: "horizontalHeader");
307 QVERIFY(hhv);
308 QCOMPARE(hhv->columns(), 10);
309 QCOMPARE(hhv->rows(), 1);
310 auto vhv = root->findChild<QQuickVerticalHeaderView *>(aName: "verticalHeader");
311 QVERIFY(vhv);
312
313 hhv->setSyncDirection(Qt::Vertical);
314 hhv->flick(xVelocity: 10, yVelocity: 20);
315
316 vhv->setSyncDirection(Qt::Horizontal);
317 vhv->flick(xVelocity: 20, yVelocity: 10);
318
319 QVERIFY(QTest::qWaitForWindowActive(qobject_cast<QWindow *>(root.data())));
320 // Explicitly setting a different synDirection is ignored
321 QCOMPARE(hhv->syncDirection(), Qt::Horizontal);
322 QCOMPARE(hhv->flickableDirection(), QQuickFlickable::HorizontalFlick);
323 QCOMPARE(vhv->syncDirection(), Qt::Vertical);
324 QCOMPARE(vhv->flickableDirection(), QQuickFlickable::VerticalFlick);
325}
326
327void tst_QQuickHeaderView::testModel()
328{
329 QQmlComponent component(engine);
330 component.loadUrl(url: testFileUrl(fileName: "Window.qml"));
331
332 QScopedPointer<QObject> root(component.create());
333 QVERIFY2(root, qPrintable(component.errorString()));
334
335 auto hhv = root->findChild<QQuickHorizontalHeaderView *>(aName: "horizontalHeader");
336 QVERIFY(hhv);
337 auto thm = root->findChild<TestTableModel *>(aName: "tableHeaderModel");
338 QVERIFY(thm);
339 auto pm = root->findChild<QHeaderDataProxyModel *>(aName: "proxyModel");
340 QVERIFY(pm);
341
342 QSignalSpy modelChangedSpy(hhv, SIGNAL(modelChanged()));
343 QVERIFY(modelChangedSpy.isValid());
344
345 hhv->setModel(QVariant::fromValue(value: thm));
346 QCOMPARE(modelChangedSpy.count(), 0);
347
348 hhv->setModel(QVariant::fromValue(value: pm));
349 QCOMPARE(modelChangedSpy.count(), 1);
350
351 TestTableModel ttm2;
352 ttm2.setRowCount(100);
353 ttm2.setColumnCount(30);
354 hhv->setModel(QVariant::fromValue(value: &ttm2));
355 QCOMPARE(modelChangedSpy.count(), 2);
356}
357
358void tst_QQuickHeaderView::listModel()
359{
360 QQmlComponent component(engine);
361 component.loadUrl(url: testFileUrl(fileName: "ListModel.qml"));
362
363 QScopedPointer<QObject> root(component.create());
364 QVERIFY2(root, qPrintable(component.errorString()));
365
366 if (!QTest::qWaitForWindowActive(window: qobject_cast<QWindow *>(o: root.data())))
367 QSKIP("Window failed to become active!");
368
369 auto hhv = root->findChild<QQuickHorizontalHeaderView *>(aName: "horizontalHeader");
370 QVERIFY(hhv);
371 auto vhv = root->findChild<QQuickVerticalHeaderView *>(aName: "verticalHeader");
372 QVERIFY(vhv);
373
374 auto hhvCell1 = hhv->childAt(x: 0, y: 0)->childAt(x: 0, y: 0)->findChild<QQuickText *>();
375 QVERIFY(hhvCell1);
376 QCOMPARE(hhvCell1->property("text"), "AAA");
377
378 auto hhvCell2 = hhv->childAt(x: hhvCell1->width() + 5, y: 0)->
379 childAt(x: hhvCell1->width() + 5, y: 0)->findChild<QQuickText *>();
380 QVERIFY(hhvCell2);
381 QCOMPARE(hhvCell2->property("text"), "BBB");
382
383 auto vhvCell1 = vhv->childAt(x: 0, y: 0)->childAt(x: 0, y: 0)->findChild<QQuickText *>();
384 QVERIFY(vhvCell1);
385 QCOMPARE(vhvCell1->property("text"), "111");
386
387 auto vhvCell2 = vhv->childAt(x: 0, y: vhvCell1->height() + 5)->
388 childAt(x: 0, y: vhvCell1->height() + 5)->findChild<QQuickText *>();
389 QVERIFY(vhvCell2);
390 QCOMPARE(vhvCell2->property("text"), "222");
391}
392
393QTEST_MAIN(tst_QQuickHeaderView)
394
395#include "tst_qquickheaderview.moc"
396

source code of qtquickcontrols2/tests/auto/qquickheaderview/tst_qquickheaderview.cpp