1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2019 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the test suite of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at https://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT |
21 | ** included in the packaging of this file. Please review the following |
22 | ** information to ensure the GNU General Public License requirements will |
23 | ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
24 | ** |
25 | ** $QT_END_LICENSE$ |
26 | ** |
27 | ****************************************************************************/ |
28 | |
29 | #include <QtTest/QtTest> |
30 | #include <QtQuick/qquickview.h> |
31 | #include <QtQml/qqmlengine.h> |
32 | #include <QtQml/qqmlcomponent.h> |
33 | #include <QtQml/qqmlcontext.h> |
34 | #include <QtQml/qqmlexpression.h> |
35 | #include <QtQml/qqmlincubator.h> |
36 | #include <QtQuickShapes/private/qquickshape_p.h> |
37 | #include <QStandardPaths> |
38 | |
39 | #include "../../shared/util.h" |
40 | #include "../shared/viewtestutil.h" |
41 | #include "../shared/visualtestutil.h" |
42 | |
43 | using namespace QQuickViewTestUtil; |
44 | using namespace QQuickVisualTestUtil; |
45 | |
46 | class PolygonProvider : public QObject |
47 | { |
48 | Q_OBJECT |
49 | Q_PROPERTY(QVector<QPolygonF> paths READ paths WRITE setPaths NOTIFY pathsChanged) |
50 | |
51 | public: |
52 | QVector<QPolygonF> paths() const { return m_paths; } |
53 | void setPaths(QVector<QPolygonF> paths) |
54 | { |
55 | if (m_paths == paths) |
56 | return; |
57 | m_paths = paths; |
58 | emit pathsChanged(); |
59 | } |
60 | |
61 | signals: |
62 | void pathsChanged(); |
63 | |
64 | private: |
65 | QVector<QPolygonF> m_paths; |
66 | }; |
67 | |
68 | class tst_QQuickShape : public QQmlDataTest |
69 | { |
70 | Q_OBJECT |
71 | public: |
72 | tst_QQuickShape(); |
73 | |
74 | private slots: |
75 | void initValues(); |
76 | void vpInitValues(); |
77 | void basicShape(); |
78 | void changeSignals(); |
79 | void render(); |
80 | void renderWithMultipleSp(); |
81 | void radialGrad(); |
82 | void conicalGrad(); |
83 | void renderPolyline(); |
84 | void renderMultiline(); |
85 | void polylineDataTypes_data(); |
86 | void polylineDataTypes(); |
87 | void multilineDataTypes_data(); |
88 | void multilineDataTypes(); |
89 | void multilineStronglyTyped(); |
90 | |
91 | private: |
92 | QVector<QPolygonF> m_lowPolyLogo; |
93 | }; |
94 | |
95 | tst_QQuickShape::tst_QQuickShape() |
96 | { |
97 | // Force the software backend to get reliable rendering results regardless of the hw and drivers. |
98 | QQuickWindow::setSceneGraphBackend(QSGRendererInterface::Software); |
99 | |
100 | const char *uri = "tst_qquickpathitem" ; |
101 | qmlRegisterType<QQuickShape>(uri, versionMajor: 1, versionMinor: 0, qmlName: "Shape" ); |
102 | qmlRegisterType<QQuickShapePath, 14>(uri, versionMajor: 1, versionMinor: 0, qmlName: "ShapePath" ); |
103 | qmlRegisterUncreatableType<QQuickShapeGradient>(uri, versionMajor: 1, versionMinor: 0, qmlName: "ShapeGradient" , reason: QQuickShapeGradient::tr(s: "ShapeGradient is an abstract base class" )); |
104 | qmlRegisterType<QQuickShapeLinearGradient>(uri, versionMajor: 1, versionMinor: 0, qmlName: "LinearGradient" ); |
105 | qmlRegisterType<QQuickShapeRadialGradient>(uri, versionMajor: 1, versionMinor: 0, qmlName: "RadialGradient" ); |
106 | qmlRegisterType<QQuickShapeConicalGradient>(uri, versionMajor: 1, versionMinor: 0, qmlName: "ConicalGradient" ); |
107 | qmlRegisterType<QQuickPathPolyline>(uri, versionMajor: 1, versionMinor: 0, qmlName: "PathPolyline" ); |
108 | qmlRegisterType<PolygonProvider>(uri: "Qt.test" , versionMajor: 1, versionMinor: 0, qmlName: "PolygonProvider" ); |
109 | |
110 | m_lowPolyLogo << (QPolygonF() << |
111 | QPointF(20, 0) << |
112 | QPointF(140, 0) << |
113 | QPointF(140, 80) << |
114 | QPointF(120, 100) << |
115 | QPointF(0, 100) << |
116 | QPointF(0, 20) << |
117 | QPointF(20, 0) ) |
118 | << (QPolygonF() << QPointF(20, 80) << |
119 | QPointF(60, 80) << |
120 | QPointF(80, 60) << |
121 | QPointF(80, 20) << |
122 | QPointF(40, 20) << |
123 | QPointF(20, 40) << |
124 | QPointF(20, 80) ) |
125 | << (QPolygonF() << QPointF(80, 80) << |
126 | QPointF(70, 70) ) |
127 | << (QPolygonF() << QPointF(120, 80) << |
128 | QPointF(100, 60) << |
129 | QPointF(100, 20) ) |
130 | << (QPolygonF() << QPointF(100, 40) << |
131 | QPointF(120, 40) ); |
132 | } |
133 | |
134 | void tst_QQuickShape::initValues() |
135 | { |
136 | QQmlEngine engine; |
137 | QQmlComponent c(&engine, testFileUrl(fileName: "pathitem1.qml" )); |
138 | QQuickShape *obj = qobject_cast<QQuickShape *>(object: c.create()); |
139 | |
140 | QVERIFY(obj != nullptr); |
141 | QVERIFY(obj->rendererType() == QQuickShape::UnknownRenderer); |
142 | QVERIFY(!obj->asynchronous()); |
143 | QVERIFY(!obj->vendorExtensionsEnabled()); |
144 | QVERIFY(obj->status() == QQuickShape::Null); |
145 | auto vps = obj->data(); |
146 | QVERIFY(vps.count(&vps) == 0); |
147 | |
148 | delete obj; |
149 | } |
150 | |
151 | void tst_QQuickShape::vpInitValues() |
152 | { |
153 | QQmlEngine engine; |
154 | QQmlComponent c(&engine, testFileUrl(fileName: "pathitem2.qml" )); |
155 | QQuickShape *obj = qobject_cast<QQuickShape *>(object: c.create()); |
156 | |
157 | QVERIFY(obj != nullptr); |
158 | QVERIFY(obj->rendererType() == QQuickShape::UnknownRenderer); |
159 | QVERIFY(!obj->asynchronous()); |
160 | QVERIFY(!obj->vendorExtensionsEnabled()); |
161 | QVERIFY(obj->status() == QQuickShape::Null); |
162 | auto vps = obj->data(); |
163 | QVERIFY(vps.count(&vps) == 2); |
164 | |
165 | QQuickShapePath *vp = qobject_cast<QQuickShapePath *>(object: vps.at(&vps, 0)); |
166 | QVERIFY(vp != nullptr); |
167 | QQmlListReference pathList(vp, "pathElements" ); |
168 | QCOMPARE(pathList.count(), 0); |
169 | QCOMPARE(vp->strokeColor(), QColor(Qt::white)); |
170 | QCOMPARE(vp->strokeWidth(), 1.0f); |
171 | QCOMPARE(vp->fillColor(), QColor(Qt::white)); |
172 | QCOMPARE(vp->fillRule(), QQuickShapePath::OddEvenFill); |
173 | QCOMPARE(vp->joinStyle(), QQuickShapePath::BevelJoin); |
174 | QCOMPARE(vp->miterLimit(), 2); |
175 | QCOMPARE(vp->capStyle(), QQuickShapePath::SquareCap); |
176 | QCOMPARE(vp->strokeStyle(), QQuickShapePath::SolidLine); |
177 | QCOMPARE(vp->dashOffset(), 0.0f); |
178 | QCOMPARE(vp->dashPattern(), QVector<qreal>() << 4 << 2); |
179 | QVERIFY(!vp->fillGradient()); |
180 | |
181 | delete obj; |
182 | } |
183 | |
184 | void tst_QQuickShape::basicShape() |
185 | { |
186 | QScopedPointer<QQuickView> window(createView()); |
187 | |
188 | window->setSource(testFileUrl(fileName: "pathitem3.qml" )); |
189 | qApp->processEvents(); |
190 | |
191 | QQuickShape *obj = findItem<QQuickShape>(parent: window->rootObject(), objectName: "pathItem" ); |
192 | QVERIFY(obj != nullptr); |
193 | QQmlListReference list(obj, "data" ); |
194 | QCOMPARE(list.count(), 1); |
195 | QQuickShapePath *vp = qobject_cast<QQuickShapePath *>(object: list.at(0)); |
196 | QVERIFY(vp != nullptr); |
197 | QCOMPARE(vp->strokeWidth(), 4.0f); |
198 | QVERIFY(vp->fillGradient() != nullptr); |
199 | QCOMPARE(vp->strokeStyle(), QQuickShapePath::DashLine); |
200 | |
201 | vp->setStrokeWidth(5.0f); |
202 | QCOMPARE(vp->strokeWidth(), 5.0f); |
203 | |
204 | QQuickShapeLinearGradient *lgrad = qobject_cast<QQuickShapeLinearGradient *>(object: vp->fillGradient()); |
205 | QVERIFY(lgrad != nullptr); |
206 | QCOMPARE(lgrad->spread(), QQuickShapeGradient::PadSpread); |
207 | QCOMPARE(lgrad->x1(), 20.0f); |
208 | QQmlListReference stopList(lgrad, "stops" ); |
209 | QCOMPARE(stopList.count(), 5); |
210 | QVERIFY(stopList.at(2) != nullptr); |
211 | |
212 | QQuickPath *path = vp; |
213 | QCOMPARE(path->startX(), 20.0f); |
214 | QQmlListReference pathList(path, "pathElements" ); |
215 | QCOMPARE(pathList.count(), 3); |
216 | } |
217 | |
218 | void tst_QQuickShape::changeSignals() |
219 | { |
220 | QScopedPointer<QQuickView> window(createView()); |
221 | |
222 | window->setSource(testFileUrl(fileName: "pathitem3.qml" )); |
223 | qApp->processEvents(); |
224 | |
225 | QQuickShape *obj = findItem<QQuickShape>(parent: window->rootObject(), objectName: "pathItem" ); |
226 | QVERIFY(obj != nullptr); |
227 | |
228 | QSignalSpy asyncPropSpy(obj, SIGNAL(asynchronousChanged())); |
229 | obj->setAsynchronous(true); |
230 | obj->setAsynchronous(false); |
231 | QCOMPARE(asyncPropSpy.count(), 2); |
232 | |
233 | QQmlListReference list(obj, "data" ); |
234 | QQuickShapePath *vp = qobject_cast<QQuickShapePath *>(object: list.at(0)); |
235 | QVERIFY(vp != nullptr); |
236 | |
237 | // Verify that VisualPath property changes emit shapePathChanged(). |
238 | QSignalSpy vpChangeSpy(vp, SIGNAL(shapePathChanged())); |
239 | QSignalSpy strokeColorPropSpy(vp, SIGNAL(strokeColorChanged())); |
240 | vp->setStrokeColor(Qt::blue); |
241 | vp->setStrokeWidth(1.0f); |
242 | QQuickShapeGradient *g = vp->fillGradient(); |
243 | vp->setFillGradient(nullptr); |
244 | vp->setFillColor(Qt::yellow); |
245 | vp->setFillRule(QQuickShapePath::WindingFill); |
246 | vp->setJoinStyle(QQuickShapePath::MiterJoin); |
247 | vp->setMiterLimit(5); |
248 | vp->setCapStyle(QQuickShapePath::RoundCap); |
249 | vp->setDashOffset(10); |
250 | vp->setDashPattern(QVector<qreal>() << 1 << 2 << 3 << 4); |
251 | QCOMPARE(strokeColorPropSpy.count(), 1); |
252 | QCOMPARE(vpChangeSpy.count(), 10); |
253 | |
254 | // Verify that property changes from Path and its elements bubble up and result in shapePathChanged(). |
255 | QQuickPath *path = vp; |
256 | path->setStartX(30); |
257 | QCOMPARE(vpChangeSpy.count(), 11); |
258 | QQmlListReference pathList(path, "pathElements" ); |
259 | qobject_cast<QQuickPathLine *>(object: pathList.at(1))->setY(200); |
260 | QCOMPARE(vpChangeSpy.count(), 12); |
261 | |
262 | // Verify that property changes from the gradient bubble up and result in shapePathChanged(). |
263 | vp->setFillGradient(g); |
264 | QCOMPARE(vpChangeSpy.count(), 13); |
265 | QQuickShapeLinearGradient *lgrad = qobject_cast<QQuickShapeLinearGradient *>(object: g); |
266 | lgrad->setX2(200); |
267 | QCOMPARE(vpChangeSpy.count(), 14); |
268 | QQmlListReference stopList(lgrad, "stops" ); |
269 | QCOMPARE(stopList.count(), 5); |
270 | qobject_cast<QQuickGradientStop *>(object: stopList.at(1))->setPosition(0.3); |
271 | QCOMPARE(vpChangeSpy.count(), 15); |
272 | qobject_cast<QQuickGradientStop *>(object: stopList.at(1))->setColor(Qt::black); |
273 | QCOMPARE(vpChangeSpy.count(), 16); |
274 | } |
275 | |
276 | void tst_QQuickShape::render() |
277 | { |
278 | QScopedPointer<QQuickView> window(createView()); |
279 | |
280 | window->setSource(testFileUrl(fileName: "pathitem3.qml" )); |
281 | window->show(); |
282 | QVERIFY(QTest::qWaitForWindowExposed(window.data())); |
283 | |
284 | if ((QGuiApplication::platformName() == QLatin1String("offscreen" )) |
285 | || (QGuiApplication::platformName() == QLatin1String("minimal" ))) |
286 | QEXPECT_FAIL("" , "Failure due to grabWindow not functional on offscreen/minimal platforms" , Abort); |
287 | |
288 | QImage img = window->grabWindow(); |
289 | QVERIFY(!img.isNull()); |
290 | |
291 | QImage refImg(testFileUrl(fileName: "pathitem3.png" ).toLocalFile()); |
292 | QVERIFY(!refImg.isNull()); |
293 | |
294 | QString errorMessage; |
295 | const QImage actualImg = img.convertToFormat(f: refImg.format()); |
296 | QVERIFY2(QQuickVisualTestUtil::compareImages(actualImg, refImg, &errorMessage), |
297 | qPrintable(errorMessage)); |
298 | } |
299 | |
300 | void tst_QQuickShape::renderWithMultipleSp() |
301 | { |
302 | QScopedPointer<QQuickView> window(createView()); |
303 | |
304 | window->setSource(testFileUrl(fileName: "pathitem4.qml" )); |
305 | window->show(); |
306 | QVERIFY(QTest::qWaitForWindowExposed(window.data())); |
307 | |
308 | if ((QGuiApplication::platformName() == QLatin1String("offscreen" )) |
309 | || (QGuiApplication::platformName() == QLatin1String("minimal" ))) |
310 | QEXPECT_FAIL("" , "Failure due to grabWindow not functional on offscreen/minimal platforms" , Abort); |
311 | |
312 | QImage img = window->grabWindow(); |
313 | QVERIFY(!img.isNull()); |
314 | |
315 | QImage refImg(testFileUrl(fileName: "pathitem4.png" ).toLocalFile()); |
316 | QVERIFY(!refImg.isNull()); |
317 | |
318 | QString errorMessage; |
319 | const QImage actualImg = img.convertToFormat(f: refImg.format()); |
320 | QVERIFY2(QQuickVisualTestUtil::compareImages(actualImg, refImg, &errorMessage), |
321 | qPrintable(errorMessage)); |
322 | } |
323 | |
324 | void tst_QQuickShape::radialGrad() |
325 | { |
326 | QScopedPointer<QQuickView> window(createView()); |
327 | |
328 | window->setSource(testFileUrl(fileName: "pathitem5.qml" )); |
329 | window->show(); |
330 | QVERIFY(QTest::qWaitForWindowExposed(window.data())); |
331 | |
332 | if ((QGuiApplication::platformName() == QLatin1String("offscreen" )) |
333 | || (QGuiApplication::platformName() == QLatin1String("minimal" ))) |
334 | QEXPECT_FAIL("" , "Failure due to grabWindow not functional on offscreen/minimal platforms" , Abort); |
335 | |
336 | QImage img = window->grabWindow(); |
337 | QVERIFY(!img.isNull()); |
338 | |
339 | QImage refImg(testFileUrl(fileName: "pathitem5.png" ).toLocalFile()); |
340 | QVERIFY(!refImg.isNull()); |
341 | |
342 | QString errorMessage; |
343 | const QImage actualImg = img.convertToFormat(f: refImg.format()); |
344 | QVERIFY2(QQuickVisualTestUtil::compareImages(actualImg, refImg, &errorMessage), |
345 | qPrintable(errorMessage)); |
346 | } |
347 | |
348 | void tst_QQuickShape::conicalGrad() |
349 | { |
350 | QScopedPointer<QQuickView> window(createView()); |
351 | |
352 | window->setSource(testFileUrl(fileName: "pathitem6.qml" )); |
353 | window->show(); |
354 | QVERIFY(QTest::qWaitForWindowExposed(window.data())); |
355 | |
356 | if ((QGuiApplication::platformName() == QLatin1String("offscreen" )) |
357 | || (QGuiApplication::platformName() == QLatin1String("minimal" ))) |
358 | QEXPECT_FAIL("" , "Failure due to grabWindow not functional on offscreen/minimal platforms" , Abort); |
359 | |
360 | QImage img = window->grabWindow(); |
361 | QVERIFY(!img.isNull()); |
362 | |
363 | QImage refImg(testFileUrl(fileName: "pathitem6.png" ).toLocalFile()); |
364 | QVERIFY(!refImg.isNull()); |
365 | |
366 | QString errorMessage; |
367 | const QImage actualImg = img.convertToFormat(f: refImg.format()); |
368 | QVERIFY2(QQuickVisualTestUtil::compareImages(actualImg, refImg, &errorMessage), |
369 | qPrintable(errorMessage)); |
370 | } |
371 | |
372 | void tst_QQuickShape::renderPolyline() |
373 | { |
374 | QScopedPointer<QQuickView> window(createView()); |
375 | |
376 | window->setSource(testFileUrl(fileName: "pathitem7.qml" )); |
377 | window->show(); |
378 | QVERIFY(QTest::qWaitForWindowExposed(window.data())); |
379 | |
380 | if ((QGuiApplication::platformName() == QLatin1String("offscreen" )) |
381 | || (QGuiApplication::platformName() == QLatin1String("minimal" ))) |
382 | QEXPECT_FAIL("" , "Failure due to grabWindow not functional on offscreen/minimal platforms" , Abort); |
383 | |
384 | QImage img = window->grabWindow(); |
385 | QVERIFY(!img.isNull()); |
386 | |
387 | QImage refImg(testFileUrl(fileName: "pathitem3.png" ).toLocalFile()); // It's a recreation of pathitem3 using PathPolyline |
388 | QVERIFY(!refImg.isNull()); |
389 | |
390 | QString errorMessage; |
391 | const QImage actualImg = img.convertToFormat(f: refImg.format()); |
392 | const bool res = QQuickVisualTestUtil::compareImages(ia: actualImg, ib: refImg, errorMessage: &errorMessage); |
393 | if (!res) { // For visual inspection purposes. |
394 | QTest::qWait(ms: 5000); |
395 | const QString &tempLocation = QStandardPaths::writableLocation(type: QStandardPaths::TempLocation); |
396 | actualImg.save(fileName: tempLocation + QLatin1String("/pathitem7.png" )); |
397 | } |
398 | QVERIFY2(res, qPrintable(errorMessage)); |
399 | } |
400 | |
401 | void tst_QQuickShape::renderMultiline() |
402 | { |
403 | QScopedPointer<QQuickView> window(createView()); |
404 | |
405 | window->setSource(testFileUrl(fileName: "pathitem8.qml" )); |
406 | window->show(); |
407 | QVERIFY(QTest::qWaitForWindowExposed(window.data())); |
408 | |
409 | if ((QGuiApplication::platformName() == QLatin1String("offscreen" )) |
410 | || (QGuiApplication::platformName() == QLatin1String("minimal" ))) |
411 | QEXPECT_FAIL("" , "Failure due to grabWindow not functional on offscreen/minimal platforms" , Abort); |
412 | |
413 | QImage img = window->grabWindow(); |
414 | QVERIFY(!img.isNull()); |
415 | |
416 | QImage refImg(testFileUrl(fileName: "pathitem8.png" ).toLocalFile()); |
417 | QVERIFY(!refImg.isNull()); |
418 | |
419 | QString errorMessage; |
420 | const QImage actualImg = img.convertToFormat(f: refImg.format()); |
421 | const bool res = QQuickVisualTestUtil::compareImages(ia: actualImg, ib: refImg, errorMessage: &errorMessage); |
422 | if (!res) { // For visual inspection purposes. |
423 | QTest::qWait(ms: 5000); |
424 | const QString &tempLocation = QStandardPaths::writableLocation(type: QStandardPaths::TempLocation); |
425 | actualImg.save(fileName: tempLocation + QLatin1String("/pathitem8.png" )); |
426 | } |
427 | QVERIFY2(res, qPrintable(errorMessage)); |
428 | } |
429 | |
430 | void tst_QQuickShape::polylineDataTypes_data() |
431 | { |
432 | QTest::addColumn<QVariant>(name: "path" ); |
433 | |
434 | QTest::newRow(dataTag: "polygon" ) << QVariant::fromValue(value: m_lowPolyLogo.first()); |
435 | { |
436 | QVector<QPointF> points; |
437 | points << m_lowPolyLogo.first(); |
438 | QTest::newRow(dataTag: "vector of points" ) << QVariant::fromValue(value: points); |
439 | } |
440 | { |
441 | QList<QPointF> points; |
442 | for (const auto &point : m_lowPolyLogo.first()) |
443 | points << point; |
444 | QTest::newRow(dataTag: "list of points" ) << QVariant::fromValue(value: points); |
445 | } |
446 | { |
447 | QVariantList points; |
448 | for (const auto &point : m_lowPolyLogo.first()) |
449 | points << point; |
450 | QTest::newRow(dataTag: "QVariantList of points" ) << QVariant::fromValue(value: points); |
451 | } |
452 | { |
453 | QVector<QPoint> points; |
454 | for (const auto &point : m_lowPolyLogo.first()) |
455 | points << point.toPoint(); |
456 | QTest::newRow(dataTag: "vector of QPoint (integer points)" ) << QVariant::fromValue(value: points); |
457 | } |
458 | // Oddly, QPolygon is not supported, even though it's really QVector<QPoint>. |
459 | // We don't want to have a special case for it in QQuickPathPolyline::setPath(), |
460 | // but it could potentially be supported by fixing one of the QVariant conversions. |
461 | } |
462 | |
463 | void tst_QQuickShape::polylineDataTypes() |
464 | { |
465 | QFETCH(QVariant, path); |
466 | |
467 | QScopedPointer<QQuickView> window(createView()); |
468 | window->setSource(testFileUrl(fileName: "polyline.qml" )); |
469 | QQuickShape *shape = qobject_cast<QQuickShape *>(object: window->rootObject()); |
470 | QVERIFY(shape); |
471 | shape->setProperty(name: "path" , value: path); |
472 | window->show(); |
473 | QVERIFY(QTest::qWaitForWindowExposed(window.data())); |
474 | |
475 | if ((QGuiApplication::platformName() == QLatin1String("offscreen" )) |
476 | || (QGuiApplication::platformName() == QLatin1String("minimal" ))) |
477 | QEXPECT_FAIL("" , "Failure due to grabWindow not functional on offscreen/minimal platforms" , Abort); |
478 | |
479 | QImage img = window->grabWindow(); |
480 | QVERIFY(!img.isNull()); |
481 | |
482 | QImage refImg(testFileUrl(fileName: "polyline.png" ).toLocalFile()); |
483 | QVERIFY(!refImg.isNull()); |
484 | |
485 | QString errorMessage; |
486 | const QImage actualImg = img.convertToFormat(f: refImg.format()); |
487 | const bool res = QQuickVisualTestUtil::compareImages(ia: actualImg, ib: refImg, errorMessage: &errorMessage); |
488 | if (!res) { // For visual inspection purposes. |
489 | QTest::qWait(ms: 5000); |
490 | const QString &tempLocation = QStandardPaths::writableLocation(type: QStandardPaths::TempLocation); |
491 | actualImg.save(fileName: tempLocation + QLatin1String("/polyline.png" )); |
492 | } |
493 | QVERIFY2(res, qPrintable(errorMessage)); |
494 | |
495 | QCOMPARE(shape->property("path" ).value<QVector<QPointF>>(), m_lowPolyLogo.first()); |
496 | // Verify that QML sees it as an array of points |
497 | int i = 0; |
498 | for (QPointF p : m_lowPolyLogo.first()) { |
499 | QMetaObject::invokeMethod(obj: shape, member: "checkVertexAt" , Q_ARG(QVariant, QVariant::fromValue<int>(i++))); |
500 | QCOMPARE(shape->property("vertexBeingChecked" ).toPointF(), p); |
501 | } |
502 | } |
503 | |
504 | void tst_QQuickShape::multilineDataTypes_data() |
505 | { |
506 | QTest::addColumn<QVariant>(name: "paths" ); |
507 | |
508 | QTest::newRow(dataTag: "vector of polygons" ) << QVariant::fromValue(value: m_lowPolyLogo); |
509 | { |
510 | QVector<QVector<QPointF>> paths; |
511 | for (const auto &poly : m_lowPolyLogo) { |
512 | QVector<QPointF> points; |
513 | points << poly; |
514 | paths << points; |
515 | } |
516 | QTest::newRow(dataTag: "vector of point vectors" ) << QVariant::fromValue(value: paths); |
517 | } |
518 | { |
519 | QList<QVector<QPointF>> paths; |
520 | for (const auto &poly : m_lowPolyLogo) { |
521 | QVector<QPointF> points; |
522 | points << poly; |
523 | paths << points; |
524 | } |
525 | QTest::newRow(dataTag: "list of point vectors" ) << QVariant::fromValue(value: paths); |
526 | } |
527 | { |
528 | QList<QList<QPointF>> paths; |
529 | for (const auto &poly : m_lowPolyLogo) { |
530 | QList<QPointF> points; |
531 | for (const auto &point : poly) |
532 | points << point; |
533 | paths << points; |
534 | } |
535 | QTest::newRow(dataTag: "list of point lists" ) << QVariant::fromValue(value: paths); |
536 | } |
537 | { |
538 | QVariantList paths; |
539 | for (const auto &poly : m_lowPolyLogo) { |
540 | QVector<QPointF> points; |
541 | points << poly; |
542 | paths << QVariant::fromValue(value: points); |
543 | } |
544 | QTest::newRow(dataTag: "QVariantList of point vectors" ) << QVariant::fromValue(value: paths); |
545 | } |
546 | { |
547 | QVariantList paths; |
548 | for (const auto &poly : m_lowPolyLogo) { |
549 | QList<QPointF> points; |
550 | for (const auto &point : poly) |
551 | points << point; |
552 | paths << QVariant::fromValue(value: points); |
553 | } |
554 | QTest::newRow(dataTag: "QVariantList of point lists" ) << QVariant::fromValue(value: paths); |
555 | } |
556 | { |
557 | QVariantList paths; |
558 | for (const auto &poly : m_lowPolyLogo) { |
559 | QVariantList points; |
560 | for (const auto &point : poly) |
561 | points << point; |
562 | paths << QVariant::fromValue(value: points); |
563 | } |
564 | QTest::newRow(dataTag: "QVariantList of QVariantLists" ) << QVariant::fromValue(value: paths); |
565 | } |
566 | /* These could be supported if QVariant knew how to convert lists and vectors of QPolygon to QPolygonF. |
567 | But they are omitted for now because we don't want to have special cases for them |
568 | in QQuickPathMultiline::setPaths(). Floating point is preferred for geometry in Qt Quick. |
569 | { |
570 | QList<QPolygon> paths; |
571 | for (const auto &poly : m_lowPolyLogo) |
572 | paths << poly.toPolygon(); |
573 | QTest::newRow("list of QPolygon (integer points)") << QVariant::fromValue(paths); |
574 | } |
575 | { |
576 | QVector<QPolygon> paths; |
577 | for (const auto &poly : m_lowPolyLogo) |
578 | paths << poly.toPolygon(); |
579 | QTest::newRow("vector of QPolygon (integer points)") << QVariant::fromValue(paths); |
580 | } |
581 | */ |
582 | { |
583 | QList<QList<QPoint>> paths; |
584 | for (const auto &poly : m_lowPolyLogo) { |
585 | QList<QPoint> points; |
586 | for (const auto &point : poly) |
587 | points << point.toPoint(); |
588 | paths << points; |
589 | } |
590 | QTest::newRow(dataTag: "list of integer point lists" ) << QVariant::fromValue(value: paths); |
591 | } |
592 | { |
593 | QVector<QList<QPoint>> paths; |
594 | for (const auto &poly : m_lowPolyLogo) { |
595 | QList<QPoint> points; |
596 | for (const auto &point : poly) |
597 | points << point.toPoint(); |
598 | paths << points; |
599 | } |
600 | QTest::newRow(dataTag: "vector of integer point lists" ) << QVariant::fromValue(value: paths); |
601 | } |
602 | { |
603 | QList<QVector<QPoint>> paths; |
604 | for (const auto &poly : m_lowPolyLogo) { |
605 | QVector<QPoint> points; |
606 | for (const auto &point : poly) |
607 | points << point.toPoint(); |
608 | paths << points; |
609 | } |
610 | QTest::newRow(dataTag: "list of integer point vectors" ) << QVariant::fromValue(value: paths); |
611 | } |
612 | } |
613 | |
614 | void tst_QQuickShape::multilineDataTypes() |
615 | { |
616 | QFETCH(QVariant, paths); |
617 | |
618 | QScopedPointer<QQuickView> window(createView()); |
619 | window->setSource(testFileUrl(fileName: "multiline.qml" )); |
620 | QQuickShape *shape = qobject_cast<QQuickShape *>(object: window->rootObject()); |
621 | QVERIFY(shape); |
622 | shape->setProperty(name: "paths" , value: paths); |
623 | window->show(); |
624 | QVERIFY(QTest::qWaitForWindowExposed(window.data())); |
625 | |
626 | if ((QGuiApplication::platformName() == QLatin1String("offscreen" )) |
627 | || (QGuiApplication::platformName() == QLatin1String("minimal" ))) |
628 | QEXPECT_FAIL("" , "Failure due to grabWindow not functional on offscreen/minimal platforms" , Abort); |
629 | |
630 | QImage img = window->grabWindow(); |
631 | QVERIFY(!img.isNull()); |
632 | |
633 | QImage refImg(testFileUrl(fileName: "multiline.png" ).toLocalFile()); |
634 | QVERIFY(!refImg.isNull()); |
635 | |
636 | QString errorMessage; |
637 | const QImage actualImg = img.convertToFormat(f: refImg.format()); |
638 | const bool res = QQuickVisualTestUtil::compareImages(ia: actualImg, ib: refImg, errorMessage: &errorMessage); |
639 | if (!res) { // For visual inspection purposes. |
640 | QTest::qWait(ms: 5000); |
641 | const QString &tempLocation = QStandardPaths::writableLocation(type: QStandardPaths::TempLocation); |
642 | actualImg.save(fileName: tempLocation + QLatin1String("/multiline.png" )); |
643 | } |
644 | QVERIFY2(res, qPrintable(errorMessage)); |
645 | |
646 | QVector<QVector<QPointF>> pointVectors; |
647 | for (auto v : m_lowPolyLogo) |
648 | pointVectors << v; |
649 | QCOMPARE(shape->property("paths" ).value<QVector<QVector<QPointF>>>(), pointVectors); |
650 | // Verify that QML sees it as an array of arrays of points |
651 | int i = 0; |
652 | for (auto pv : m_lowPolyLogo) { |
653 | int j = 0; |
654 | for (QPointF p : pv) { |
655 | QMetaObject::invokeMethod(obj: shape, member: "checkVertexAt" , Q_ARG(QVariant, QVariant::fromValue<int>(i)), Q_ARG(QVariant, QVariant::fromValue<int>(j++))); |
656 | QCOMPARE(shape->property("vertexBeingChecked" ).toPointF(), p); |
657 | } |
658 | ++i; |
659 | } |
660 | } |
661 | |
662 | void tst_QQuickShape::multilineStronglyTyped() |
663 | { |
664 | QScopedPointer<QQuickView> window(createView()); |
665 | window->setSource(testFileUrl(fileName: "multilineStronglyTyped.qml" )); |
666 | QQuickShape *shape = qobject_cast<QQuickShape *>(object: window->rootObject()); |
667 | QVERIFY(shape); |
668 | PolygonProvider *provider = shape->findChild<PolygonProvider*>(aName: "provider" ); |
669 | QVERIFY(provider); |
670 | window->show(); |
671 | QVERIFY(QTest::qWaitForWindowExposed(window.data())); |
672 | provider->setPaths(m_lowPolyLogo); |
673 | |
674 | if ((QGuiApplication::platformName() == QLatin1String("offscreen" )) |
675 | || (QGuiApplication::platformName() == QLatin1String("minimal" ))) |
676 | QEXPECT_FAIL("" , "Failure due to grabWindow not functional on offscreen/minimal platforms" , Abort); |
677 | |
678 | QImage img = window->grabWindow(); |
679 | QVERIFY(!img.isNull()); |
680 | |
681 | QImage refImg(testFileUrl(fileName: "multiline.png" ).toLocalFile()); |
682 | QVERIFY(!refImg.isNull()); |
683 | |
684 | QString errorMessage; |
685 | const QImage actualImg = img.convertToFormat(f: refImg.format()); |
686 | const bool res = QQuickVisualTestUtil::compareImages(ia: actualImg, ib: refImg, errorMessage: &errorMessage); |
687 | if (!res) { // For visual inspection purposes. |
688 | QTest::qWait(ms: 5000); |
689 | const QString &tempLocation = QStandardPaths::writableLocation(type: QStandardPaths::TempLocation); |
690 | actualImg.save(fileName: tempLocation + QLatin1String("/multilineStronglyTyped.png" )); |
691 | } |
692 | QVERIFY2(res, qPrintable(errorMessage)); |
693 | |
694 | QVector<QVector<QPointF>> pointVectors; |
695 | for (auto v : m_lowPolyLogo) |
696 | pointVectors << v; |
697 | QCOMPARE(shape->property("paths" ).value<QVector<QVector<QPointF>>>(), pointVectors); |
698 | // Verify that QML sees it as an array of arrays of points |
699 | int i = 0; |
700 | for (auto pv : m_lowPolyLogo) { |
701 | int j = 0; |
702 | for (QPointF p : pv) { |
703 | QMetaObject::invokeMethod(obj: shape, member: "checkVertexAt" , Q_ARG(QVariant, QVariant::fromValue<int>(i)), Q_ARG(QVariant, QVariant::fromValue<int>(j++))); |
704 | QCOMPARE(shape->property("vertexBeingChecked" ).toPointF(), p); |
705 | } |
706 | ++i; |
707 | } |
708 | } |
709 | |
710 | QTEST_MAIN(tst_QQuickShape) |
711 | |
712 | #include "tst_qquickshape.moc" |
713 | |