| 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 | |