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 | |
50 | class 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 | |
55 | public: |
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 | |
150 | signals: |
151 | void rowCountChanged(); |
152 | void columnCountChanged(); |
153 | |
154 | private: |
155 | int m_rows = 0; |
156 | int m_cols = 0; |
157 | |
158 | QSet<int> m_checkedCells; |
159 | }; |
160 | |
161 | class : public TestTableModel { |
162 | |
163 | Q_OBJECT |
164 | public: |
165 | void (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 (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 (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 | |
209 | private: |
210 | QVector<QVariant> , ; |
211 | }; |
212 | |
213 | class : public QQmlDataTest { |
214 | Q_OBJECT |
215 | |
216 | private 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 | |
228 | private: |
229 | QQmlEngine *; |
230 | QString ; |
231 | |
232 | std::unique_ptr<QObject> (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 | |
243 | void tst_QQuickHeaderView::() |
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 | |
251 | void tst_QQuickHeaderView::() |
252 | { |
253 | } |
254 | |
255 | void tst_QQuickHeaderView::() |
256 | { |
257 | engine = new QQmlEngine(this); |
258 | } |
259 | |
260 | void tst_QQuickHeaderView::() |
261 | { |
262 | if (engine) { |
263 | delete engine; |
264 | engine = nullptr; |
265 | } |
266 | } |
267 | |
268 | void tst_QQuickHeaderView::() |
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 | |
288 | void tst_QQuickHeaderView::() |
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 | |
298 | void tst_QQuickHeaderView::() |
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 | |
327 | void tst_QQuickHeaderView::() |
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 | |
358 | void tst_QQuickHeaderView::() |
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 | |
393 | QTEST_MAIN(tst_QQuickHeaderView) |
394 | |
395 | #include "tst_qquickheaderview.moc" |
396 | |