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
43using namespace QQuickViewTestUtil;
44using namespace QQuickVisualTestUtil;
45
46class PolygonProvider : public QObject
47{
48 Q_OBJECT
49 Q_PROPERTY(QVector<QPolygonF> paths READ paths WRITE setPaths NOTIFY pathsChanged)
50
51public:
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
61signals:
62 void pathsChanged();
63
64private:
65 QVector<QPolygonF> m_paths;
66};
67
68class tst_QQuickShape : public QQmlDataTest
69{
70 Q_OBJECT
71public:
72 tst_QQuickShape();
73
74private 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
91private:
92 QVector<QPolygonF> ;
93};
94
95tst_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
134void 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
151void 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
184void 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
218void 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
276void 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
300void 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
324void 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
348void 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
372void 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
401void 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
430void 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
463void 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
504void 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
614void 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
662void 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
710QTEST_MAIN(tst_QQuickShape)
711
712#include "tst_qquickshape.moc"
713

source code of qtdeclarative/tests/auto/quick/qquickshape/tst_qquickshape.cpp