diff options
author | Shawn Rutledge <shawn.rutledge@qt.io> | 2019-08-16 05:51:02 +0200 |
---|---|---|
committer | Shawn Rutledge <shawn.rutledge@qt.io> | 2019-09-02 22:22:35 +0200 |
commit | 6dc319155a282cd274a93887c5b83c8ed8b82d90 (patch) | |
tree | b7dc01ee5385be81674817e5bef2975876dacf2c | |
parent | 426f3035a3753800ce340a83bdf8db13922f4cae (diff) |
Enable PathPolyline to take a QPolygonF or QVector<QPointF> 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 <paolo.angelelli@qt.io>
-rw-r--r-- | src/quick/util/qquickpath.cpp | 40 | ||||
-rw-r--r-- | src/quick/util/qquickpath_p.h | 6 | ||||
-rw-r--r-- | tests/auto/quick/qquickshape/data/polyline.png | bin | 0 -> 757 bytes | |||
-rw-r--r-- | tests/auto/quick/qquickshape/data/polyline.qml | 52 | ||||
-rw-r--r-- | tests/auto/quick/qquickshape/tst_qquickshape.cpp | 102 |
5 files changed, 182 insertions, 18 deletions
diff --git a/src/quick/util/qquickpath.cpp b/src/quick/util/qquickpath.cpp index d246ee7910..6d4e469e44 100644 --- a/src/quick/util/qquickpath.cpp +++ b/src/quick/util/qquickpath.cpp @@ -2397,32 +2397,42 @@ void QQuickPathPercent::setValue(qreal value) \qmlproperty list<point> QtQuick::PathPolyline::path This property defines the vertices of the polyline. + + It can be a JS array of points constructed with \c Qt.point(), + a QList or QVector of QPointF, or QPolygonF. + If you are binding this to a custom property in some C++ object, + QPolygonF is the most appropriate type to use. */ QQuickPathPolyline::QQuickPathPolyline(QObject *parent) : QQuickCurve(parent) { } -QVariantList QQuickPathPolyline::path() const +QVariant QQuickPathPolyline::path() const { - QVariantList res; - for (int i = 0; i < m_path.length(); ++i) { - const QPointF &c = m_path.at(i); - res.append(QVariant::fromValue(c)); - } - - return res; + return QVariant::fromValue(m_path); } -void QQuickPathPolyline::setPath(const QVariantList &path) +void QQuickPathPolyline::setPath(const QVariant &path) { - QVector<QPointF> pathList; - for (int i = 0; i < path.length(); ++i) { - const QPointF c = path.at(i).toPointF(); - pathList.append(c); + if (path.type() == QVariant::PolygonF) { + setPath(path.value<QPolygonF>()); + } else if (path.canConvert<QVector<QPointF>>()) { + setPath(path.value<QVector<QPointF>>()); + } else if (path.canConvert<QVariantList>()) { + // This handles cases other than QPolygonF or QVector<QPointF>, such as + // QList<QPointF>, QVector<QPoint>, QVariantList of QPointF, QVariantList of QPoint. + QVector<QPointF> pathList; + QVariantList vl = path.value<QVariantList>(); + // If path is a QJSValue, e.g. coming from a JS array of Qt.point() in QML, + // then path.value<QVariantList>() is inefficient. + // TODO We should be able to iterate over path.value<QSequentialIterable>() eventually + for (const QVariant &v : vl) + pathList.append(v.toPointF()); + setPath(pathList); + } else { + qWarning() << "PathPolyline: path of type" << path.type() << "not supported"; } - - setPath(pathList); } void QQuickPathPolyline::setPath(const QVector<QPointF> &path) diff --git a/src/quick/util/qquickpath_p.h b/src/quick/util/qquickpath_p.h index 92607a6c1e..e37a2e2dce 100644 --- a/src/quick/util/qquickpath_p.h +++ b/src/quick/util/qquickpath_p.h @@ -428,12 +428,12 @@ class Q_QUICK_PRIVATE_EXPORT QQuickPathPolyline : public QQuickCurve { Q_OBJECT Q_PROPERTY(QPointF start READ start NOTIFY startChanged) - Q_PROPERTY(QVariantList path READ path WRITE setPath NOTIFY pathChanged) + Q_PROPERTY(QVariant path READ path WRITE setPath NOTIFY pathChanged) public: QQuickPathPolyline(QObject *parent=nullptr); - QVariantList path() const; - void setPath(const QVariantList &path); + QVariant path() const; + void setPath(const QVariant &path); void setPath(const QVector<QPointF> &path); QPointF start() const; void addToPath(QPainterPath &path, const QQuickPathData &data) override; diff --git a/tests/auto/quick/qquickshape/data/polyline.png b/tests/auto/quick/qquickshape/data/polyline.png Binary files differnew file mode 100644 index 0000000000..00123fba44 --- /dev/null +++ b/tests/auto/quick/qquickshape/data/polyline.png 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<QPolygonF> m_lowPolyLogo; }; tst_QQuickShape::tst_QQuickShape() @@ -75,6 +80,29 @@ tst_QQuickShape::tst_QQuickShape() qmlRegisterType<QQuickShapeRadialGradient>(uri, 1, 0, "RadialGradient"); qmlRegisterType<QQuickShapeConicalGradient>(uri, 1, 0, "ConicalGradient"); qmlRegisterType<QQuickPathPolyline>(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<QVariant>("path"); + + QTest::newRow("polygon") << QVariant::fromValue(m_lowPolyLogo.first()); + { + QVector<QPointF> points; + points << m_lowPolyLogo.first(); + QTest::newRow("vector of points") << QVariant::fromValue(points); + } + { + QList<QPointF> 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<QPoint> 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<QPoint>. + // 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<QQuickView> window(createView()); + window->setSource(testFileUrl("polyline.qml")); + QQuickShape *shape = qobject_cast<QQuickShape *>(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<QVector<QPointF>>(), 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<int>(i++))); + QCOMPARE(shape->property("vertexBeingChecked").toPointF(), p); + } +} + QTEST_MAIN(tst_QQuickShape) #include "tst_qquickshape.moc" |