From 4fe529cc793305c24f58c4c32d57f2923ff7750a Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Mon, 2 Dec 2019 15:00:16 +0100 Subject: Fix QML API for sensor ranges Previously the QQmlListProperties were constructed on stack-allocated lists, which immediately turned into dangling pointers on return. Also, none of the sensor ranges was ever deleted. Keep the sensor ranges in persistent lists in the private object, parent them to the main QmlSensor object, and add some tests for them. Fixes: QTBUG-79224 Change-Id: Ie61d0aaeaaaf998c1d1f46f60c81848ae48659d6 Reviewed-by: Fabian Kosmale Reviewed-by: Alex Blasche Reviewed-by: Timur Pocheptsov (cherry picked from commit f6b550ea5eae80124576766fe1a9609ae5aba1e3) --- src/imports/sensors/qmlsensor.cpp | 81 +++++++++++----- src/imports/sensors/qmlsensor.h | 3 + src/imports/sensors/sensors.pro | 2 +- tests/auto/sensors2qmlapi/sensors2qmlapi.pro | 4 + tests/auto/sensors2qmlapi/tst_sensors2qmlapi.cpp | 113 +++++++++++++++++++++++ 5 files changed, 177 insertions(+), 26 deletions(-) diff --git a/src/imports/sensors/qmlsensor.cpp b/src/imports/sensors/qmlsensor.cpp index 84eea6f..e0a6958 100644 --- a/src/imports/sensors/qmlsensor.cpp +++ b/src/imports/sensors/qmlsensor.cpp @@ -40,9 +40,39 @@ #include "qmlsensor.h" #include #include +#include QT_BEGIN_NAMESPACE +class QmlSensorPrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QmlSensor) +public: + + QList availableRanges; + QList outputRanges; +}; + +template +int readonlyListCount(QQmlListProperty *p) +{ + return static_cast *>(p->data)->count(); +} + +template +Item *readonlyListAt(QQmlListProperty *p, int idx) +{ + return static_cast *>(p->data)->at(idx); +}; + +template +QQmlListProperty readonlyListProperty(const QObject *o, const QList *list) +{ + // Unfortunately QQmlListProperty won't accept a const object, even on the readonly ctor. + return QQmlListProperty(const_cast(o), const_cast *>(list), + readonlyListCount, readonlyListAt); +} + /*! \qmltype Sensor \instantiates QmlSensor @@ -59,7 +89,7 @@ QT_BEGIN_NAMESPACE */ QmlSensor::QmlSensor(QObject *parent) - : QObject(parent) + : QObject(*(new QmlSensorPrivate), parent) , m_parsed(false) , m_active(false) , m_reading(0) @@ -193,19 +223,10 @@ void QmlSensor::setSkipDuplicates(bool skipDuplicates) Please see QSensor::availableDataRates for information about this property. */ - QQmlListProperty QmlSensor::availableDataRates() const { - QList ret; - ret.reserve(sensor()->availableDataRates().size()); - foreach (const qrange &r, sensor()->availableDataRates()) { - QmlSensorRange *range = new QmlSensorRange; - //QQmlEngine::setObjectOwnership(range, QQmlEngine::JavaScriptOwnership); - range->setMinumum(r.first); - range->setMaximum(r.second); - ret << range; - } - return QQmlListProperty(const_cast(this), ret); + Q_D(const QmlSensor); + return readonlyListProperty(this, &d->availableRanges); } /*! @@ -237,17 +258,8 @@ void QmlSensor::setDataRate(int rate) QQmlListProperty QmlSensor::outputRanges() const { - QList ret; - ret.reserve(sensor()->outputRanges().size()); - foreach (const qoutputrange &r, sensor()->outputRanges()) { - QmlSensorOutputRange *range = new QmlSensorOutputRange; - //QQmlEngine::setObjectOwnership(range, QQmlEngine::JavaScriptOwnership); - range->setMinimum(r.minimum); - range->setMaximum(r.maximum); - range->setAccuracy(r.accuracy); - ret << range; - } - return QQmlListProperty(const_cast(this), ret); + Q_D(const QmlSensor); + return readonlyListProperty(this, &d->outputRanges); } /*! @@ -466,12 +478,31 @@ void QmlSensor::componentComplete() if (oldOutputRange != outputRange()) Q_EMIT outputRangeChanged(); + Q_D(QmlSensor); + const auto available = sensor()->availableDataRates(); + d->availableRanges.reserve(available.size()); + for (const qrange &r : available) { + auto *range = new QmlSensorRange(this); + range->setMinumum(r.first); + range->setMaximum(r.second); + d->availableRanges.append(range); + } + const auto output = sensor()->outputRanges(); + d->outputRanges.reserve(output.size()); + for (const qoutputrange &r : output) { + auto *range = new QmlSensorOutputRange(this); + range->setMinimum(r.minimum); + range->setMaximum(r.maximum); + range->setAccuracy(r.accuracy); + d->outputRanges.append(range); + } + // meta-data should become non-empty if (!description().isEmpty()) Q_EMIT descriptionChanged(); - if (sensor()->availableDataRates().count()) + if (available.count()) Q_EMIT availableDataRatesChanged(); - if (sensor()->outputRanges().count()) + if (output.count()) Q_EMIT outputRangesChanged(); _update(); diff --git a/src/imports/sensors/qmlsensor.h b/src/imports/sensors/qmlsensor.h index e46278b..3ac8269 100644 --- a/src/imports/sensors/qmlsensor.h +++ b/src/imports/sensors/qmlsensor.h @@ -40,6 +40,7 @@ #ifndef QMLSENSOR_H #define QMLSENSOR_H +#include #include #include #include "qmlsensorrange.h" @@ -51,9 +52,11 @@ class QSensorReading; class QmlSensorReading; +class QmlSensorPrivate; class QmlSensor : public QObject, public QQmlParserStatus { Q_OBJECT + Q_DECLARE_PRIVATE(QmlSensor) Q_ENUMS(AxesOrientationMode) Q_INTERFACES(QQmlParserStatus) Q_PROPERTY(QString identifier READ identifier WRITE setIdentifier NOTIFY identifierChanged) diff --git a/src/imports/sensors/sensors.pro b/src/imports/sensors/sensors.pro index d627916..7d15ec9 100644 --- a/src/imports/sensors/sensors.pro +++ b/src/imports/sensors/sensors.pro @@ -1,4 +1,4 @@ -QT += qml sensors sensors-private +QT += core-private qml sensors sensors-private HEADERS += \ qmlsensor.h \ diff --git a/tests/auto/sensors2qmlapi/sensors2qmlapi.pro b/tests/auto/sensors2qmlapi/sensors2qmlapi.pro index 3d5e237..7438866 100644 --- a/tests/auto/sensors2qmlapi/sensors2qmlapi.pro +++ b/tests/auto/sensors2qmlapi/sensors2qmlapi.pro @@ -5,12 +5,16 @@ CONFIG += testcase QT = core testlib sensors-private qml SOURCES += tst_sensors2qmlapi.cpp \ + ./../../../src/imports/sensors/qmlsensor.cpp \ ./../../../src/imports/sensors/qmlsensorgesture.cpp \ + ./../../../src/imports/sensors/qmlsensorrange.cpp \ qtemplategestureplugin.cpp \ qtemplaterecognizer.cpp HEADERS += \ + ./../../../src/imports/sensors/qmlsensor.h \ ./../../../src/imports/sensors/qmlsensorgesture.h \ + ./../../../src/imports/sensors/qmlsensorrange.h \ qtemplategestureplugin.h \ qtemplaterecognizer.h diff --git a/tests/auto/sensors2qmlapi/tst_sensors2qmlapi.cpp b/tests/auto/sensors2qmlapi/tst_sensors2qmlapi.cpp index d007414..51007fc 100644 --- a/tests/auto/sensors2qmlapi/tst_sensors2qmlapi.cpp +++ b/tests/auto/sensors2qmlapi/tst_sensors2qmlapi.cpp @@ -29,10 +29,14 @@ #include #include #include + +#include "../../../src/imports/sensors/qmlsensor.h" #include "../../../src/imports/sensors/qmlsensorgesture.h" + #include "qtemplategestureplugin.h" #include "qtemplaterecognizer.h" #include +#include #include "qsensormanager.h" QT_USE_NAMESPACE @@ -46,6 +50,7 @@ class tst_Sensors2QMLAPI : public QObject private slots: void initTestCase(); void testGesture(); + void testSensorRanges(); }; void tst_Sensors2QMLAPI::initTestCase() @@ -169,6 +174,114 @@ void tst_Sensors2QMLAPI::testGesture() QCOMPARE(spy2_detected.count(), 1); } +class QDummySensorBackend : public QSensorBackend +{ + Q_OBJECT +public: + QDummySensorBackend(QSensor *sensor) : QSensorBackend(sensor) + { + addDataRate(2, 3); + addDataRate(5, 7); + addOutputRange(100, 200, 1); + addOutputRange(600, 700, 10); + addOutputRange(0, 1, 2); + } + + void start() override {} + void stop() override {} +}; + +class QDummySensorReading : public QSensorReading +{ + Q_OBJECT +public: + QDummySensorReading(QObject *parent) : QSensorReading(parent, nullptr) {} +}; + +class QmlDummySensorReading : public QmlSensorReading +{ + Q_OBJECT +public: + QmlDummySensorReading(QSensor *sensor) : + QmlSensorReading(sensor), + m_reading(new QDummySensorReading(this)) + {} + + QSensorReading *reading() const override { return m_reading; } + void readingUpdate() override {} + +private: + QSensorReading *m_reading = nullptr; +}; + +class QmlDummySensor : public QmlSensor +{ + Q_OBJECT +public: + QmlDummySensor(QObject *parent = nullptr) : + QmlSensor(parent), + m_sensor(new QSensor("dummy", this)) + { + QDummySensorBackend b(m_sensor); + Q_UNUSED(b); + } + + QSensor *sensor() const override { return m_sensor; } + QmlSensorReading *createReading() const override { return new QmlDummySensorReading(m_sensor); } + + void componentComplete() override { QmlSensor::componentComplete(); } + +private: + QSensor *m_sensor = nullptr; +}; + +void tst_Sensors2QMLAPI::testSensorRanges() +{ + QScopedPointer qmlSensor(new QmlDummySensor); + qmlSensor->componentComplete(); + + auto ranges = qmlSensor->availableDataRates(); + QCOMPARE(ranges.count(&ranges), 2); + + const auto range0 = ranges.at(&ranges, 0); + QCOMPARE(range0->minimum(), 2); + QCOMPARE(range0->maximum(), 3); + QSignalSpy range0Spy(range0, SIGNAL(destroyed())); + + const auto range1 = ranges.at(&ranges, 1); + QCOMPARE(range1->minimum(), 5); + QCOMPARE(range1->maximum(), 7); + QSignalSpy range1Spy(range1, SIGNAL(destroyed())); + + auto outputs = qmlSensor->outputRanges(); + QCOMPARE(outputs.count(&outputs), 3); + + const auto output0 = outputs.at(&outputs, 0); + QCOMPARE(output0->minimum(), 100); + QCOMPARE(output0->maximum(), 200); + QCOMPARE(output0->accuracy(), 1); + QSignalSpy output0Spy(output0, SIGNAL(destroyed())); + + const auto output1 = outputs.at(&outputs, 1); + QCOMPARE(output1->minimum(), 600); + QCOMPARE(output1->maximum(), 700); + QCOMPARE(output1->accuracy(), 10); + QSignalSpy output1Spy(output1, SIGNAL(destroyed())); + + const auto output2 = outputs.at(&outputs, 2); + QCOMPARE(output2->minimum(), 0); + QCOMPARE(output2->maximum(), 1); + QCOMPARE(output2->accuracy(), 2); + QSignalSpy output2Spy(output2, SIGNAL(destroyed())); + + qmlSensor.reset(); + QCOMPARE(range0Spy.count(), 1); + QCOMPARE(range1Spy.count(), 1); + QCOMPARE(output0Spy.count(), 1); + QCOMPARE(output1Spy.count(), 1); + QCOMPARE(output2Spy.count(), 1); +} + QT_END_NAMESPACE QTEST_MAIN(tst_Sensors2QMLAPI) -- cgit v1.2.1