From 6dc319155a282cd274a93887c5b83c8ed8b82d90 Mon Sep 17 00:00:00 2001 From: Shawn Rutledge Date: Fri, 16 Aug 2019 05:51:02 +0200 Subject: Enable PathPolyline to take a QPolygonF or QVector path If a C++ model object can make a vector of points available directly, and it is bound to a PathPolyline's path to provide the view layer, it's a waste of time to convert it to a QVariantList and back again. Changing the type of the property to QVariant instead of QVariantList enables an extensible set of supported types. Task-number: QTBUG-77929 Change-Id: I2453b59e047ec3310070e943f6934c9ddcd1ffaa Reviewed-by: Paolo Angelelli --- tests/auto/quick/qquickshape/data/polyline.png | Bin 0 -> 757 bytes tests/auto/quick/qquickshape/data/polyline.qml | 52 ++++++++++++ tests/auto/quick/qquickshape/tst_qquickshape.cpp | 102 +++++++++++++++++++++++ 3 files changed, 154 insertions(+) create mode 100644 tests/auto/quick/qquickshape/data/polyline.png create mode 100644 tests/auto/quick/qquickshape/data/polyline.qml (limited to 'tests/auto/quick/qquickshape') diff --git a/tests/auto/quick/qquickshape/data/polyline.png b/tests/auto/quick/qquickshape/data/polyline.png new file mode 100644 index 0000000000..00123fba44 Binary files /dev/null and b/tests/auto/quick/qquickshape/data/polyline.png differ diff --git a/tests/auto/quick/qquickshape/data/polyline.qml b/tests/auto/quick/qquickshape/data/polyline.qml new file mode 100644 index 0000000000..e62d952ae7 --- /dev/null +++ b/tests/auto/quick/qquickshape/data/polyline.qml @@ -0,0 +1,52 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.14 +import QtQuick.Shapes 1.14 + +Shape { + width: 200 + height: 150 + vendorExtensionsEnabled: false + objectName: "shape" + id: shape + property alias path: polyline.path + property point vertexBeingChecked; + + function checkVertexAt(i) { + vertexBeingChecked = polyline.path[i] + } + + ShapePath { + strokeWidth: 4 + strokeColor: "green" + PathPolyline { + id: polyline + } + } +} diff --git a/tests/auto/quick/qquickshape/tst_qquickshape.cpp b/tests/auto/quick/qquickshape/tst_qquickshape.cpp index b3b8d2d148..69b65f0ec9 100644 --- a/tests/auto/quick/qquickshape/tst_qquickshape.cpp +++ b/tests/auto/quick/qquickshape/tst_qquickshape.cpp @@ -60,6 +60,11 @@ private slots: void conicalGrad(); void renderPolyline(); void renderMultiline(); + void polylineDataTypes_data(); + void polylineDataTypes(); + +private: + QVector m_lowPolyLogo; }; tst_QQuickShape::tst_QQuickShape() @@ -75,6 +80,29 @@ tst_QQuickShape::tst_QQuickShape() qmlRegisterType(uri, 1, 0, "RadialGradient"); qmlRegisterType(uri, 1, 0, "ConicalGradient"); qmlRegisterType(uri, 1, 0, "PathPolyline"); + + m_lowPolyLogo << (QPolygonF() << + QPointF(20, 0) << + QPointF(140, 0) << + QPointF(140, 80) << + QPointF(120, 100) << + QPointF(0, 100) << + QPointF(0, 20) << + QPointF(20, 0) ) + << (QPolygonF() << QPointF(20, 80) << + QPointF(60, 80) << + QPointF(80, 60) << + QPointF(80, 20) << + QPointF(40, 20) << + QPointF(20, 40) << + QPointF(20, 80) ) + << (QPolygonF() << QPointF(80, 80) << + QPointF(70, 70) ) + << (QPolygonF() << QPointF(120, 80) << + QPointF(100, 60) << + QPointF(100, 20) ) + << (QPolygonF() << QPointF(100, 40) << + QPointF(120, 40) ); } void tst_QQuickShape::initValues() @@ -373,6 +401,80 @@ void tst_QQuickShape::renderMultiline() QVERIFY2(res, qPrintable(errorMessage)); } +void tst_QQuickShape::polylineDataTypes_data() +{ + QTest::addColumn("path"); + + QTest::newRow("polygon") << QVariant::fromValue(m_lowPolyLogo.first()); + { + QVector points; + points << m_lowPolyLogo.first(); + QTest::newRow("vector of points") << QVariant::fromValue(points); + } + { + QList points; + for (const auto &point : m_lowPolyLogo.first()) + points << point; + QTest::newRow("list of points") << QVariant::fromValue(points); + } + { + QVariantList points; + for (const auto &point : m_lowPolyLogo.first()) + points << point; + QTest::newRow("QVariantList of points") << QVariant::fromValue(points); + } + { + QVector points; + for (const auto &point : m_lowPolyLogo.first()) + points << point.toPoint(); + QTest::newRow("vector of QPoint (integer points)") << QVariant::fromValue(points); + } + // Oddly, QPolygon is not supported, even though it's really QVector. + // We don't want to have a special case for it in QQuickPathPolyline::setPath(), + // but it could potentially be supported by fixing one of the QVariant conversions. +} + +void tst_QQuickShape::polylineDataTypes() +{ + QFETCH(QVariant, path); + + QScopedPointer window(createView()); + window->setSource(testFileUrl("polyline.qml")); + QQuickShape *shape = qobject_cast(window->rootObject()); + QVERIFY(shape); + shape->setProperty("path", path); + window->show(); + QVERIFY(QTest::qWaitForWindowExposed(window.data())); + + if ((QGuiApplication::platformName() == QLatin1String("offscreen")) + || (QGuiApplication::platformName() == QLatin1String("minimal"))) + QEXPECT_FAIL("", "Failure due to grabWindow not functional on offscreen/minimimal platforms", Abort); + + QImage img = window->grabWindow(); + QVERIFY(!img.isNull()); + + QImage refImg(testFileUrl("polyline.png").toLocalFile()); + QVERIFY(!refImg.isNull()); + + QString errorMessage; + const QImage actualImg = img.convertToFormat(refImg.format()); + const bool res = QQuickVisualTestUtil::compareImages(actualImg, refImg, &errorMessage); + if (!res) { // For visual inspection purposes. + QTest::qWait(5000); + const QString &tempLocation = QStandardPaths::writableLocation(QStandardPaths::TempLocation); + actualImg.save(tempLocation + QLatin1String("/polyline.png")); + } + QVERIFY2(res, qPrintable(errorMessage)); + + QCOMPARE(shape->property("path").value>(), m_lowPolyLogo.first()); + // Verify that QML sees it as an array of points + int i = 0; + for (QPointF p : m_lowPolyLogo.first()) { + QMetaObject::invokeMethod(shape, "checkVertexAt", Q_ARG(QVariant, QVariant::fromValue(i++))); + QCOMPARE(shape->property("vertexBeingChecked").toPointF(), p); + } +} + QTEST_MAIN(tst_QQuickShape) #include "tst_qquickshape.moc" -- cgit v1.2.3 From 811b15bd161d12e5c85e093f9f492a0c4fa278d6 Mon Sep 17 00:00:00 2001 From: Shawn Rutledge Date: Fri, 16 Aug 2019 11:18:38 +0200 Subject: PathMultiline: allow lists/vectors of polygons or point lists/vectors If a C++ model object can make a vector of vectors of points available directly, and it is bound to a PathMultiline's paths property to provide the view layer, it's a waste of time to convert it to a QVariantList of QVariantLists and back again. Changing the type of the property to QVariant instead of QVariantList enables an extensible set of supported types: all those that make sense. Fixes: QTBUG-77929 Change-Id: If749c2171173e7b9933fc9ecdf6d2741dc1c7500 Reviewed-by: Paolo Angelelli --- tests/auto/quick/qquickshape/data/multiline.png | Bin 0 -> 1244 bytes tests/auto/quick/qquickshape/data/multiline.qml | 52 ++++++++ tests/auto/quick/qquickshape/tst_qquickshape.cpp | 160 +++++++++++++++++++++++ 3 files changed, 212 insertions(+) create mode 100644 tests/auto/quick/qquickshape/data/multiline.png create mode 100644 tests/auto/quick/qquickshape/data/multiline.qml (limited to 'tests/auto/quick/qquickshape') diff --git a/tests/auto/quick/qquickshape/data/multiline.png b/tests/auto/quick/qquickshape/data/multiline.png new file mode 100644 index 0000000000..ae25d111df Binary files /dev/null and b/tests/auto/quick/qquickshape/data/multiline.png differ diff --git a/tests/auto/quick/qquickshape/data/multiline.qml b/tests/auto/quick/qquickshape/data/multiline.qml new file mode 100644 index 0000000000..ad07506972 --- /dev/null +++ b/tests/auto/quick/qquickshape/data/multiline.qml @@ -0,0 +1,52 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.14 +import QtQuick.Shapes 1.14 + +Shape { + width: 200 + height: 150 + vendorExtensionsEnabled: false + objectName: "shape" + id: shape + property alias paths: multiline.paths + property point vertexBeingChecked; + + function checkVertexAt(i, j) { + vertexBeingChecked = multiline.paths[i][j] + } + + ShapePath { + strokeWidth: 4 + strokeColor: "green" + PathMultiline { + id: multiline + } + } +} diff --git a/tests/auto/quick/qquickshape/tst_qquickshape.cpp b/tests/auto/quick/qquickshape/tst_qquickshape.cpp index 69b65f0ec9..6bd8562823 100644 --- a/tests/auto/quick/qquickshape/tst_qquickshape.cpp +++ b/tests/auto/quick/qquickshape/tst_qquickshape.cpp @@ -62,6 +62,8 @@ private slots: void renderMultiline(); void polylineDataTypes_data(); void polylineDataTypes(); + void multilineDataTypes_data(); + void multilineDataTypes(); private: QVector m_lowPolyLogo; @@ -475,6 +477,164 @@ void tst_QQuickShape::polylineDataTypes() } } +void tst_QQuickShape::multilineDataTypes_data() +{ + QTest::addColumn("paths"); + + QTest::newRow("vector of polygons") << QVariant::fromValue(m_lowPolyLogo); + { + QVector> paths; + for (const auto &poly : m_lowPolyLogo) { + QVector points; + points << poly; + paths << points; + } + QTest::newRow("vector of point vectors") << QVariant::fromValue(paths); + } + { + QList> paths; + for (const auto &poly : m_lowPolyLogo) { + QVector points; + points << poly; + paths << points; + } + QTest::newRow("list of point vectors") << QVariant::fromValue(paths); + } + { + QList> paths; + for (const auto &poly : m_lowPolyLogo) { + QList points; + for (const auto &point : poly) + points << point; + paths << points; + } + QTest::newRow("list of point lists") << QVariant::fromValue(paths); + } + { + QVariantList paths; + for (const auto &poly : m_lowPolyLogo) { + QVector points; + points << poly; + paths << QVariant::fromValue(points); + } + QTest::newRow("QVariantList of point vectors") << QVariant::fromValue(paths); + } + { + QVariantList paths; + for (const auto &poly : m_lowPolyLogo) { + QList points; + for (const auto &point : poly) + points << point; + paths << QVariant::fromValue(points); + } + QTest::newRow("QVariantList of point lists") << QVariant::fromValue(paths); + } + { + QVariantList paths; + for (const auto &poly : m_lowPolyLogo) { + QVariantList points; + for (const auto &point : poly) + points << point; + paths << QVariant::fromValue(points); + } + QTest::newRow("QVariantList of QVariantLists") << QVariant::fromValue(paths); + } + /* These could be supported if QVariant knew how to convert lists and vectors of QPolygon to QPolygonF. + But they are omitted for now because we don't want to have special cases for them + in QQuickPathMultiline::setPaths(). Floating point is preferred for geometry in Qt Quick. + { + QList paths; + for (const auto &poly : m_lowPolyLogo) + paths << poly.toPolygon(); + QTest::newRow("list of QPolygon (integer points)") << QVariant::fromValue(paths); + } + { + QVector paths; + for (const auto &poly : m_lowPolyLogo) + paths << poly.toPolygon(); + QTest::newRow("vector of QPolygon (integer points)") << QVariant::fromValue(paths); + } + */ + { + QList> paths; + for (const auto &poly : m_lowPolyLogo) { + QList points; + for (const auto &point : poly) + points << point.toPoint(); + paths << points; + } + QTest::newRow("list of integer point lists") << QVariant::fromValue(paths); + } + { + QVector> paths; + for (const auto &poly : m_lowPolyLogo) { + QList points; + for (const auto &point : poly) + points << point.toPoint(); + paths << points; + } + QTest::newRow("vector of integer point lists") << QVariant::fromValue(paths); + } + { + QList> paths; + for (const auto &poly : m_lowPolyLogo) { + QVector points; + for (const auto &point : poly) + points << point.toPoint(); + paths << points; + } + QTest::newRow("list of integer point vectors") << QVariant::fromValue(paths); + } +} + +void tst_QQuickShape::multilineDataTypes() +{ + QFETCH(QVariant, paths); + + QScopedPointer window(createView()); + window->setSource(testFileUrl("multiline.qml")); + QQuickShape *shape = qobject_cast(window->rootObject()); + QVERIFY(shape); + shape->setProperty("paths", paths); + window->show(); + QVERIFY(QTest::qWaitForWindowExposed(window.data())); + + if ((QGuiApplication::platformName() == QLatin1String("offscreen")) + || (QGuiApplication::platformName() == QLatin1String("minimal"))) + QEXPECT_FAIL("", "Failure due to grabWindow not functional on offscreen/minimimal platforms", Abort); + + QImage img = window->grabWindow(); + QVERIFY(!img.isNull()); + + QImage refImg(testFileUrl("multiline.png").toLocalFile()); + QVERIFY(!refImg.isNull()); + + QString errorMessage; + const QImage actualImg = img.convertToFormat(refImg.format()); + const bool res = QQuickVisualTestUtil::compareImages(actualImg, refImg, &errorMessage); + if (!res) { // For visual inspection purposes. + QTest::qWait(5000); + const QString &tempLocation = QStandardPaths::writableLocation(QStandardPaths::TempLocation); + actualImg.save(tempLocation + QLatin1String("/multiline.png")); + } + QVERIFY2(res, qPrintable(errorMessage)); + + QVector> pointVectors; + for (auto v : m_lowPolyLogo) + pointVectors << v; + QCOMPARE(shape->property("paths").value>>(), pointVectors); + // Verify that QML sees it as an array of arrays of points + int i = 0; + for (auto pv : m_lowPolyLogo) { + int j = 0; + for (QPointF p : pv) { + QMetaObject::invokeMethod(shape, "checkVertexAt", Q_ARG(QVariant, QVariant::fromValue(i)), Q_ARG(QVariant, QVariant::fromValue(j++))); + QCOMPARE(shape->property("vertexBeingChecked").toPointF(), p); + } + ++i; + } +} + QTEST_MAIN(tst_QQuickShape) #include "tst_qquickshape.moc" -- cgit v1.2.3 From e81ff5732f7884c5c1d9efb1f63ac7ad0dabbcb5 Mon Sep 17 00:00:00 2001 From: Shawn Rutledge Date: Mon, 2 Sep 2019 22:28:43 +0200 Subject: PathMultiline: handle directly-bound QVector The autotest in 811b15bd161d12e5c85e093f9f492a0c4fa278d6 only tested what happens if the vector of polygons is passed via a QVariant to the PathMultiline paths property. But the intention (as documented) was to literally support an object with Q_PROPERTY(QVector paths ...) and binding that paths property to PathMultiline.paths. In that case it appears in QQuickPathMultiline::setPaths() as a QVariant, canConvert>() returns false, then canConvert() returns true. Nevertheless each variant in the QVariantList is a QPolygonF, as expected. So we need another check to detect this case. Also added a test specifically for that. Fixes: QTBUG-77929 Change-Id: I84d0a45326d5f007b8ba3cc9bb1fbccf0345d812 Reviewed-by: Paolo Angelelli --- .../qquickshape/data/multilineStronglyTyped.qml | 59 ++++++++++++++++++ tests/auto/quick/qquickshape/tst_qquickshape.cpp | 72 ++++++++++++++++++++++ 2 files changed, 131 insertions(+) create mode 100644 tests/auto/quick/qquickshape/data/multilineStronglyTyped.qml (limited to 'tests/auto/quick/qquickshape') diff --git a/tests/auto/quick/qquickshape/data/multilineStronglyTyped.qml b/tests/auto/quick/qquickshape/data/multilineStronglyTyped.qml new file mode 100644 index 0000000000..46d650e053 --- /dev/null +++ b/tests/auto/quick/qquickshape/data/multilineStronglyTyped.qml @@ -0,0 +1,59 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.14 +import QtQuick.Shapes 1.14 +import Qt.test 1.0 + +Shape { + width: 200 + height: 150 + vendorExtensionsEnabled: false + objectName: "shape" + id: shape + property alias paths: multiline.paths + property point vertexBeingChecked; + + function checkVertexAt(i, j) { + vertexBeingChecked = multiline.paths[i][j] + } + + PolygonProvider { + id: provider + objectName: "provider" + } + + ShapePath { + strokeWidth: 4 + strokeColor: "green" + PathMultiline { + id: multiline + paths: provider.paths + } + } +} diff --git a/tests/auto/quick/qquickshape/tst_qquickshape.cpp b/tests/auto/quick/qquickshape/tst_qquickshape.cpp index 6bd8562823..851475e2cd 100644 --- a/tests/auto/quick/qquickshape/tst_qquickshape.cpp +++ b/tests/auto/quick/qquickshape/tst_qquickshape.cpp @@ -43,6 +43,28 @@ using namespace QQuickViewTestUtil; using namespace QQuickVisualTestUtil; +class PolygonProvider : public QObject +{ + Q_OBJECT + Q_PROPERTY(QVector paths READ paths WRITE setPaths NOTIFY pathsChanged) + +public: + QVector paths() const { return m_paths; } + void setPaths(QVector paths) + { + if (m_paths == paths) + return; + m_paths = paths; + emit pathsChanged(); + } + +signals: + void pathsChanged(); + +private: + QVector m_paths; +}; + class tst_QQuickShape : public QQmlDataTest { Q_OBJECT @@ -64,6 +86,7 @@ private slots: void polylineDataTypes(); void multilineDataTypes_data(); void multilineDataTypes(); + void multilineStronglyTyped(); private: QVector m_lowPolyLogo; @@ -82,6 +105,7 @@ tst_QQuickShape::tst_QQuickShape() qmlRegisterType(uri, 1, 0, "RadialGradient"); qmlRegisterType(uri, 1, 0, "ConicalGradient"); qmlRegisterType(uri, 1, 0, "PathPolyline"); + qmlRegisterType("Qt.test", 1, 0, "PolygonProvider"); m_lowPolyLogo << (QPolygonF() << QPointF(20, 0) << @@ -635,6 +659,54 @@ void tst_QQuickShape::multilineDataTypes() } } +void tst_QQuickShape::multilineStronglyTyped() +{ + QScopedPointer window(createView()); + window->setSource(testFileUrl("multilineStronglyTyped.qml")); + QQuickShape *shape = qobject_cast(window->rootObject()); + QVERIFY(shape); + PolygonProvider *provider = shape->findChild("provider"); + QVERIFY(provider); + window->show(); + QVERIFY(QTest::qWaitForWindowExposed(window.data())); + provider->setPaths(m_lowPolyLogo); + + if ((QGuiApplication::platformName() == QLatin1String("offscreen")) + || (QGuiApplication::platformName() == QLatin1String("minimal"))) + QEXPECT_FAIL("", "Failure due to grabWindow not functional on offscreen/minimimal platforms", Abort); + + QImage img = window->grabWindow(); + QVERIFY(!img.isNull()); + + QImage refImg(testFileUrl("multiline.png").toLocalFile()); + QVERIFY(!refImg.isNull()); + + QString errorMessage; + const QImage actualImg = img.convertToFormat(refImg.format()); + const bool res = QQuickVisualTestUtil::compareImages(actualImg, refImg, &errorMessage); + if (!res) { // For visual inspection purposes. + QTest::qWait(5000); + const QString &tempLocation = QStandardPaths::writableLocation(QStandardPaths::TempLocation); + actualImg.save(tempLocation + QLatin1String("/multilineStronglyTyped.png")); + } + QVERIFY2(res, qPrintable(errorMessage)); + + QVector> pointVectors; + for (auto v : m_lowPolyLogo) + pointVectors << v; + QCOMPARE(shape->property("paths").value>>(), pointVectors); + // Verify that QML sees it as an array of arrays of points + int i = 0; + for (auto pv : m_lowPolyLogo) { + int j = 0; + for (QPointF p : pv) { + QMetaObject::invokeMethod(shape, "checkVertexAt", Q_ARG(QVariant, QVariant::fromValue(i)), Q_ARG(QVariant, QVariant::fromValue(j++))); + QCOMPARE(shape->property("vertexBeingChecked").toPointF(), p); + } + ++i; + } +} + QTEST_MAIN(tst_QQuickShape) #include "tst_qquickshape.moc" -- cgit v1.2.3