/**************************************************************************** ** ** Copyright (C) 2020 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$ ** ****************************************************************************/ #include #include #include #include using namespace QtPrivate; class tst_QProperty : public QObject { Q_OBJECT private slots: void functorBinding(); void basicDependencies(); void multipleDependencies(); void bindingWithDeletedDependency(); void recursiveDependency(); void bindingAfterUse(); void switchBinding(); void avoidDependencyAllocationAfterFirstEval(); void propertyArrays(); void boolProperty(); void takeBinding(); void replaceBinding(); void swap(); void moveNotifies(); void moveCtor(); void changeHandler(); void propertyChangeHandlerApi(); void subscribe(); void changeHandlerThroughBindings(); void dontTriggerDependenciesIfUnchangedValue(); void bindingSourceLocation(); void bindingError(); void bindingLoop(); void changePropertyFromWithinChangeHandler(); void changePropertyFromWithinChangeHandlerThroughDependency(); void changePropertyFromWithinChangeHandler2(); void settingPropertyValueDoesRemoveBinding(); void genericPropertyBinding(); void genericPropertyBindingBool(); void setBindingFunctor(); void multipleObservers(); void propertyAlias(); void arrowAndStarOperator(); void notifiedProperty(); void typeNoOperatorEqual(); void bindingValueReplacement(); void testNewStuff(); void qobjectObservers(); }; void tst_QProperty::functorBinding() { QProperty property([]() { return 42; }); QCOMPARE(property.value(), int(42)); property.setBinding([]() { return 100; }); QCOMPARE(property.value(), int(100)); property.setBinding([]() { return 50; }); QCOMPARE(property.value(), int(50)); } void tst_QProperty::basicDependencies() { QProperty right(100); QProperty left(Qt::makePropertyBinding(right)); QCOMPARE(left.value(), int(100)); right = 42; QCOMPARE(left.value(), int(42)); } void tst_QProperty::multipleDependencies() { QProperty firstDependency(1); QProperty secondDependency(2); QProperty sum; sum.setBinding([&]() { return firstDependency + secondDependency; }); QCOMPARE(QPropertyBindingDataPointer::get(firstDependency).observerCount(), 0); QCOMPARE(QPropertyBindingDataPointer::get(secondDependency).observerCount(), 0); QCOMPARE(sum.value(), int(3)); QCOMPARE(QPropertyBindingDataPointer::get(firstDependency).observerCount(), 1); QCOMPARE(QPropertyBindingDataPointer::get(secondDependency).observerCount(), 1); firstDependency = 10; QCOMPARE(sum.value(), int(12)); QCOMPARE(QPropertyBindingDataPointer::get(firstDependency).observerCount(), 1); QCOMPARE(QPropertyBindingDataPointer::get(secondDependency).observerCount(), 1); secondDependency = 20; QCOMPARE(sum.value(), int(30)); QCOMPARE(QPropertyBindingDataPointer::get(firstDependency).observerCount(), 1); QCOMPARE(QPropertyBindingDataPointer::get(secondDependency).observerCount(), 1); firstDependency = 1; secondDependency = 1; QCOMPARE(sum.value(), int(2)); QCOMPARE(QPropertyBindingDataPointer::get(firstDependency).observerCount(), 1); QCOMPARE(QPropertyBindingDataPointer::get(secondDependency).observerCount(), 1); } void tst_QProperty::bindingWithDeletedDependency() { QScopedPointer> dynamicProperty(new QProperty(100)); QProperty staticProperty(1000); QProperty bindingReturnsDynamicProperty(false); QProperty propertySelector([&]() { if (bindingReturnsDynamicProperty && !dynamicProperty.isNull()) return dynamicProperty->value(); else return staticProperty.value(); }); QCOMPARE(propertySelector.value(), staticProperty.value()); bindingReturnsDynamicProperty = true; QCOMPARE(propertySelector.value(), dynamicProperty->value()); dynamicProperty.reset(); QCOMPARE(propertySelector.value(), 100); bindingReturnsDynamicProperty = false; QCOMPARE(propertySelector.value(), staticProperty.value()); } void tst_QProperty::recursiveDependency() { QProperty first(1); QProperty second; second.setBinding(Qt::makePropertyBinding(first)); QProperty third; third.setBinding(Qt::makePropertyBinding(second)); QCOMPARE(third.value(), int(1)); first = 2; QCOMPARE(third.value(), int(2)); } void tst_QProperty::bindingAfterUse() { QProperty propWithBindingLater(1); QProperty propThatUsesFirstProp; propThatUsesFirstProp.setBinding(Qt::makePropertyBinding(propWithBindingLater)); QCOMPARE(propThatUsesFirstProp.value(), int(1)); QCOMPARE(QPropertyBindingDataPointer::get(propWithBindingLater).observerCount(), 1); QProperty injectedValue(42); propWithBindingLater.setBinding(Qt::makePropertyBinding(injectedValue)); QCOMPARE(propThatUsesFirstProp.value(), int(42)); QCOMPARE(QPropertyBindingDataPointer::get(propWithBindingLater).observerCount(), 1); } void tst_QProperty::switchBinding() { QProperty first(1); QProperty propWithChangingBinding; propWithChangingBinding.setBinding(Qt::makePropertyBinding(first)); QCOMPARE(propWithChangingBinding.value(), 1); QProperty output; output.setBinding(Qt::makePropertyBinding(propWithChangingBinding)); QCOMPARE(output.value(), 1); QProperty second(2); propWithChangingBinding.setBinding(Qt::makePropertyBinding(second)); QCOMPARE(output.value(), 2); } void tst_QProperty::avoidDependencyAllocationAfterFirstEval() { QProperty firstDependency(1); QProperty secondDependency(10); QProperty propWithBinding([&]() { return firstDependency + secondDependency; }); QCOMPARE(propWithBinding.value(), int(11)); QVERIFY(QPropertyBindingDataPointer::get(propWithBinding).bindingPtr()); QCOMPARE(QPropertyBindingDataPointer::get(propWithBinding).bindingPtr()->dependencyObserverCount, 2u); firstDependency = 100; QCOMPARE(propWithBinding.value(), int(110)); QCOMPARE(QPropertyBindingDataPointer::get(propWithBinding).bindingPtr()->dependencyObserverCount, 2u); } void tst_QProperty::propertyArrays() { std::vector> properties; int expectedSum = 0; for (int i = 0; i < 10; ++i) { properties.emplace_back(i); expectedSum += i; } QProperty sum([&]() { return std::accumulate(properties.begin(), properties.end(), 0); }); QCOMPARE(sum.value(), expectedSum); properties[4] = properties[4] + 42; expectedSum += 42; QCOMPARE(sum.value(), expectedSum); } void tst_QProperty::boolProperty() { QProperty first(true); QProperty second(false); QProperty all([&]() { return first && second; }); QCOMPARE(all.value(), false); second = true; QCOMPARE(all.value(), true); } void tst_QProperty::takeBinding() { QPropertyBinding existingBinding; QVERIFY(existingBinding.isNull()); QProperty first(100); QProperty second(Qt::makePropertyBinding(first)); QCOMPARE(second.value(), int(100)); existingBinding = second.takeBinding(); QVERIFY(!existingBinding.isNull()); first = 10; QCOMPARE(second.value(), int(100)); second = 25; QCOMPARE(second.value(), int(25)); second.setBinding(existingBinding); QCOMPARE(second.value(), int(10)); QVERIFY(!existingBinding.isNull()); } void tst_QProperty::replaceBinding() { QProperty first(100); QProperty second(Qt::makePropertyBinding(first)); QCOMPARE(second.value(), 100); auto constantBinding = Qt::makePropertyBinding([]() { return 42; }); auto oldBinding = second.setBinding(constantBinding); QCOMPARE(second.value(), 42); second.setBinding(oldBinding); QCOMPARE(second.value(), 100); } void tst_QProperty::swap() { QProperty firstDependency(1); QProperty secondDependency(2); QProperty first(Qt::makePropertyBinding(firstDependency)); QProperty second(Qt::makePropertyBinding(secondDependency)); QCOMPARE(first.value(), 1); QCOMPARE(second.value(), 2); std::swap(first, second); QCOMPARE(first.value(), 2); QCOMPARE(second.value(), 1); secondDependency = 20; QCOMPARE(first.value(), 20); QCOMPARE(second.value(), 1); firstDependency = 100; QCOMPARE(first.value(), 20); QCOMPARE(second.value(), 100); } void tst_QProperty::moveNotifies() { QProperty first(1); QProperty second(2); QProperty propertyInTheMiddle(Qt::makePropertyBinding(first)); QProperty finalProp1(Qt::makePropertyBinding(propertyInTheMiddle)); QProperty finalProp2(Qt::makePropertyBinding(propertyInTheMiddle)); QCOMPARE(finalProp1.value(), 1); QCOMPARE(finalProp2.value(), 1); QCOMPARE(QPropertyBindingDataPointer::get(propertyInTheMiddle).observerCount(), 2); QProperty other(Qt::makePropertyBinding(second)); QCOMPARE(other.value(), 2); QProperty otherDep(Qt::makePropertyBinding(other)); QCOMPARE(otherDep.value(), 2); QCOMPARE(QPropertyBindingDataPointer::get(other).observerCount(), 1); propertyInTheMiddle = std::move(other); QCOMPARE(QPropertyBindingDataPointer::get(other).observerCount(), 0); QCOMPARE(finalProp1.value(), 2); QCOMPARE(finalProp2.value(), 2); } void tst_QProperty::moveCtor() { QProperty first(1); QProperty intermediate(Qt::makePropertyBinding(first)); QCOMPARE(intermediate.value(), 1); QCOMPARE(QPropertyBindingDataPointer::get(first).observerCount(), 1); QProperty targetProp(std::move(first)); QCOMPARE(QPropertyBindingDataPointer::get(targetProp).observerCount(), 0); } void tst_QProperty::changeHandler() { QProperty testProperty(0); QList recordedValues; { auto handler = testProperty.onValueChanged([&]() { recordedValues << testProperty; }); testProperty = 1; testProperty = 2; } testProperty = 3; QCOMPARE(recordedValues.count(), 2); QCOMPARE(recordedValues.at(0), 1); QCOMPARE(recordedValues.at(1), 2); } void tst_QProperty::propertyChangeHandlerApi() { int changeHandlerCallCount = 0; QPropertyChangeHandler handler([&changeHandlerCallCount]() { ++changeHandlerCallCount; }); QProperty source1; QProperty source2; handler.setSource(source1); source1 = 100; QCOMPARE(changeHandlerCallCount, 1); handler.setSource(source2); source1 = 101; QCOMPARE(changeHandlerCallCount, 1); source2 = 200; QCOMPARE(changeHandlerCallCount, 2); } void tst_QProperty::subscribe() { QProperty testProperty(42); QList recordedValues; { auto handler = testProperty.subscribe([&]() { recordedValues << testProperty; }); testProperty = 1; testProperty = 2; } testProperty = 3; QCOMPARE(recordedValues.count(), 3); QCOMPARE(recordedValues.at(0), 42); QCOMPARE(recordedValues.at(1), 1); QCOMPARE(recordedValues.at(2), 2); } void tst_QProperty::changeHandlerThroughBindings() { QProperty trigger(false); QProperty blockTrigger(false); QProperty condition([&]() { bool triggerValue = trigger; bool blockTriggerValue = blockTrigger; return triggerValue && !blockTriggerValue; }); bool changeHandlerCalled = false; auto handler = condition.onValueChanged([&]() { changeHandlerCalled = true; }); QVERIFY(!condition); QVERIFY(!changeHandlerCalled); trigger = true; QVERIFY(condition); QVERIFY(changeHandlerCalled); changeHandlerCalled = false; trigger = false; QVERIFY(!condition); QVERIFY(changeHandlerCalled); changeHandlerCalled = false; blockTrigger = true; QVERIFY(!condition); QVERIFY(!changeHandlerCalled); } void tst_QProperty::dontTriggerDependenciesIfUnchangedValue() { QProperty property(42); bool triggered = false; QProperty observer([&]() { triggered = true; return property.value(); }); QCOMPARE(observer.value(), 42); QVERIFY(triggered); triggered = false; property = 42; QCOMPARE(observer.value(), 42); QVERIFY(!triggered); } void tst_QProperty::bindingSourceLocation() { #if defined(QT_PROPERTY_COLLECT_BINDING_LOCATION) auto bindingLine = std::experimental::source_location::current().line() + 1; auto binding = Qt::makePropertyBinding([]() { return 42; }); QCOMPARE(QPropertyBindingPrivate::get(binding)->sourceLocation().line, bindingLine); #else QSKIP("Skipping this in the light of missing binding source location support"); #endif } void tst_QProperty::bindingError() { QProperty prop([]() -> int { QPropertyBindingError error(QPropertyBindingError::UnknownError, QLatin1String("my error")); QPropertyBindingPrivate::currentlyEvaluatingBinding()->setError(std::move(error)); return 0; }); QCOMPARE(prop.value(), 0); QCOMPARE(prop.binding().error().description(), QString("my error")); } void tst_QProperty::bindingLoop() { QScopedPointer> firstProp; QProperty secondProp([&]() -> int { return firstProp ? firstProp->value() : 0; }); QProperty thirdProp([&]() -> int { return secondProp.value(); }); firstProp.reset(new QProperty()); firstProp->setBinding([&]() -> int { return secondProp.value(); }); QCOMPARE(thirdProp.value(), 0); QCOMPARE(secondProp.binding().error().type(), QPropertyBindingError::BindingLoop); } void tst_QProperty::changePropertyFromWithinChangeHandler() { QProperty property(100); bool resetPropertyOnChange = false; int changeHandlerCallCount = 0; auto handler = property.onValueChanged([&]() { ++changeHandlerCallCount; if (resetPropertyOnChange) property = 100; }); QCOMPARE(property.value(), 100); resetPropertyOnChange = true; property = 42; QCOMPARE(property.value(), 100); // changing the property value inside the change handler won't result in the change // handler being called again. QCOMPARE(changeHandlerCallCount, 1); changeHandlerCallCount = 0; } void tst_QProperty::changePropertyFromWithinChangeHandlerThroughDependency() { QProperty sourceProperty(100); QProperty property(Qt::makePropertyBinding(sourceProperty)); bool resetPropertyOnChange = false; int changeHandlerCallCount = 0; auto handler = property.onValueChanged([&]() { ++changeHandlerCallCount; if (resetPropertyOnChange) sourceProperty = 100; }); QCOMPARE(property.value(), 100); resetPropertyOnChange = true; sourceProperty = 42; QCOMPARE(property.value(), 100); // changing the property value inside the change handler won't result in the change // handler being called again. QCOMPARE(changeHandlerCallCount, 1); changeHandlerCallCount = 0; } void tst_QProperty::changePropertyFromWithinChangeHandler2() { QProperty property(100); int changeHandlerCallCount = 0; auto handler = property.onValueChanged([&]() { ++changeHandlerCallCount; property = property.value() + 1; }); QCOMPARE(property.value(), 100); property = 42; QCOMPARE(property.value(), 43); } void tst_QProperty::settingPropertyValueDoesRemoveBinding() { QProperty source(42); QProperty property(Qt::makePropertyBinding(source)); QCOMPARE(property.value(), 42); QVERIFY(!property.binding().isNull()); property = 100; QCOMPARE(property.value(), 100); QVERIFY(property.binding().isNull()); source = 1; QCOMPARE(property.value(), 100); QVERIFY(property.binding().isNull()); } void tst_QProperty::genericPropertyBinding() { QProperty property; { QUntypedPropertyBinding doubleBinding(QMetaType::fromType(), [](const QMetaType &, void *) -> bool { Q_ASSERT(false); return true; }, QPropertyBindingSourceLocation()); QVERIFY(!property.setBinding(doubleBinding)); } QUntypedPropertyBinding intBinding(QMetaType::fromType(), [](const QMetaType &metaType, void *dataPtr) -> bool { Q_ASSERT(metaType.id() == qMetaTypeId()); int *intPtr = reinterpret_cast(dataPtr); *intPtr = 100; return true; }, QPropertyBindingSourceLocation()); QVERIFY(property.setBinding(intBinding)); QCOMPARE(property.value(), 100); } void tst_QProperty::genericPropertyBindingBool() { QProperty property; QVERIFY(!property.value()); QUntypedPropertyBinding boolBinding(QMetaType::fromType(), [](const QMetaType &, void *dataPtr) -> bool { auto boolPtr = reinterpret_cast(dataPtr); *boolPtr = true; return true; }, QPropertyBindingSourceLocation()); QVERIFY(property.setBinding(boolBinding)); QVERIFY(property.value()); } void tst_QProperty::setBindingFunctor() { QProperty property; QProperty injectedValue(100); // Make sure that this picks the setBinding overload that takes a functor and // moves it correctly. property.setBinding([&injectedValue]() { return injectedValue.value(); }); injectedValue = 200; QCOMPARE(property.value(), 200); } void tst_QProperty::multipleObservers() { QProperty property; property.setValue(5); QCOMPARE(property.value(), 5); int value1 = 1; auto changeHandler = property.onValueChanged([&]() { value1 = property.value(); }); QCOMPARE(value1, 1); int value2 = 2; auto subscribeHandler = property.subscribe([&]() { value2 = property.value(); }); QCOMPARE(value2, 5); property.setValue(6); QCOMPARE(property.value(), 6); QCOMPARE(value1, 6); QCOMPARE(value2, 6); property.setBinding([]() { return 12; }); QCOMPARE(value1, 12); QCOMPARE(value2, 12); QCOMPARE(property.value(), 12); property.setBinding(QPropertyBinding()); QCOMPARE(value1, 12); QCOMPARE(value2, 12); QCOMPARE(property.value(), 12); property.setValue(22); QCOMPARE(value1, 22); QCOMPARE(value2, 22); QCOMPARE(property.value(), 22); } void tst_QProperty::propertyAlias() { QScopedPointer> property(new QProperty); property->setValue(5); QPropertyAlias alias(property.get()); QVERIFY(alias.isValid()); QCOMPARE(alias.value(), 5); int value1 = 1; auto changeHandler = alias.onValueChanged([&]() { value1 = alias.value(); }); QCOMPARE(value1, 1); int value2 = 2; auto subscribeHandler = alias.subscribe([&]() { value2 = alias.value(); }); QCOMPARE(value2, 5); alias.setValue(6); QVERIFY(alias.isValid()); QCOMPARE(alias.value(), 6); QCOMPARE(value1, 6); QCOMPARE(value2, 6); alias.setBinding([]() { return 12; }); QCOMPARE(value1, 12); QCOMPARE(value2, 12); QCOMPARE(alias.value(), 12); alias.setValue(22); QCOMPARE(value1, 22); QCOMPARE(value2, 22); QCOMPARE(alias.value(), 22); property.reset(); QVERIFY(!alias.isValid()); QCOMPARE(alias.value(), int()); QCOMPARE(value1, 22); QCOMPARE(value2, 22); // Does not crash alias.setValue(25); QCOMPARE(alias.value(), int()); QCOMPARE(value1, 22); QCOMPARE(value2, 22); } void tst_QProperty::arrowAndStarOperator() { QString str("Hello"); QProperty prop(&str); QCOMPARE(prop->size(), str.size()); QCOMPARE(**prop, str); struct Dereferenceable { QString x; QString *operator->() { return &x; } const QString *operator->() const { return &x; } }; static_assert(QTypeTraits::is_dereferenceable_v); QProperty prop2(Dereferenceable{str}); QCOMPARE(prop2->size(), str.size()); QCOMPARE(**prop, str); QObject *object = new QObject; object->setObjectName("Hello"); QProperty> prop3(QSharedPointer{object}); QCOMPARE(prop3->objectName(), str); QCOMPARE(*prop3, object); } struct ClassWithNotifiedProperty : public QObject { QList recordedValues; void callback() { recordedValues << property.value(); } int getProp() { return 0; } Q_OBJECT_BINDABLE_PROPERTY(ClassWithNotifiedProperty, int, property, &ClassWithNotifiedProperty::callback); }; void tst_QProperty::notifiedProperty() { ClassWithNotifiedProperty instance; std::array, 5> otherProperties = { QProperty([&]() { return instance.property + 1; }), QProperty([&]() { return instance.property + 2; }), QProperty([&]() { return instance.property + 3; }), QProperty([&]() { return instance.property + 4; }), QProperty([&]() { return instance.property + 5; }), }; auto check = [&] { const int val = instance.property.value(); for (int i = 0; i < int(otherProperties.size()); ++i) QCOMPARE(otherProperties[i].value(), val + i + 1); }; QVERIFY(instance.recordedValues.isEmpty()); check(); instance.property.setValue(42); QCOMPARE(instance.recordedValues.count(), 1); QCOMPARE(instance.recordedValues.at(0), 42); instance.recordedValues.clear(); check(); instance.property.setValue(42); QVERIFY(instance.recordedValues.isEmpty()); check(); int subscribedCount = 0; QProperty injectedValue(100); instance.property.setBinding([&injectedValue]() { return injectedValue.value(); }); auto subscriber = [&] { ++subscribedCount; }; std::array, 10> subscribers = { instance.property.subscribe(subscriber), instance.property.subscribe(subscriber), instance.property.subscribe(subscriber), instance.property.subscribe(subscriber), instance.property.subscribe(subscriber), instance.property.subscribe(subscriber), instance.property.subscribe(subscriber), instance.property.subscribe(subscriber), instance.property.subscribe(subscriber), instance.property.subscribe(subscriber) }; QCOMPARE(subscribedCount, 10); subscribedCount = 0; QCOMPARE(instance.property.value(), 100); QCOMPARE(instance.recordedValues.count(), 1); QCOMPARE(instance.recordedValues.at(0), 100); instance.recordedValues.clear(); check(); QCOMPARE(subscribedCount, 0); injectedValue = 200; QCOMPARE(instance.property.value(), 200); QCOMPARE(instance.recordedValues.count(), 1); QCOMPARE(instance.recordedValues.at(0), 200); instance.recordedValues.clear(); check(); QCOMPARE(subscribedCount, 10); subscribedCount = 0; injectedValue = 400; QCOMPARE(instance.property.value(), 400); QCOMPARE(instance.recordedValues.count(), 1); QCOMPARE(instance.recordedValues.at(0), 400); instance.recordedValues.clear(); check(); QCOMPARE(subscribedCount, 10); } void tst_QProperty::typeNoOperatorEqual() { struct Uncomparable { int data = -1; bool changedCalled = false; Uncomparable(int value = 0) : data(value) {} Uncomparable(const Uncomparable &other) { data = other.data; changedCalled = false; } Uncomparable(Uncomparable &&other) { data = other.data; changedCalled = false; other.data = -1; other.changedCalled = false; } Uncomparable &operator=(const Uncomparable &other) { data = other.data; return *this; } Uncomparable &operator=(Uncomparable &&other) { data = other.data; changedCalled = false; other.data = -1; other.changedCalled = false; return *this; } bool operator==(const Uncomparable&) = delete; bool operator!=(const Uncomparable&) = delete; void changed() { changedCalled = true; } }; Uncomparable u1 = { 13 }; Uncomparable u2 = { 27 }; QProperty p1; QProperty p2(Qt::makePropertyBinding(p1)); QCOMPARE(p1.value().data, p2.value().data); p1.setValue(u1); QCOMPARE(p1.value().data, u1.data); QCOMPARE(p1.value().data, p2.value().data); p2.setValue(u2); QCOMPARE(p1.value().data, u1.data); QCOMPARE(p2.value().data, u2.data); QProperty p3(Qt::makePropertyBinding(p1)); p1.setValue(u1); QCOMPARE(p1.value().data, p3.value().data); // QNotifiedProperty np; // QVERIFY(np.value().data != u1.data); // np.setValue(&u1, u1); // QVERIFY(u1.changedCalled); // u1.changedCalled = false; // QCOMPARE(np.value().data, u1.data); // np.setValue(&u1, u1); // QVERIFY(u1.changedCalled); } //struct Test { // void notify() {}; // bool bindText(int); // bool bindIconText(int); // QProperty text; // QNotifiedProperty iconText; //}; //bool Test::bindIconText(int) { // Q_UNUSED(iconText.value()); // force read // if (!iconText.hasBinding()) { // iconText.setBinding(this, [=]() { return 0; }); // } // return true; //} void tst_QProperty::bindingValueReplacement() { // Test test; // test.text = 0; // test.bindIconText(0); // test.iconText.setValue(&test, 42); // should not crash // QCOMPARE(test.iconText.value(), 42); // test.text = 1; // QCOMPARE(test.iconText.value(), 42); } class MyQObject : public QObject { Q_OBJECT Q_PROPERTY(int foo READ foo WRITE setFoo NOTIFY fooChanged) // Use Q_BINDABLE_PROPERTY and generate iface API Q_PROPERTY(int bar READ bar WRITE setBar NOTIFY barChanged) Q_PROPERTY(int read READ read NOTIFY readChanged) Q_PROPERTY(int computed READ computed STORED false) signals: void fooChanged(); void barChanged(); void readChanged(); public slots: void fooHasChanged() { fooChangedCount++; } void barHasChanged() { barChangedCount++; } void readHasChanged() { readChangedCount++; } public: int foo() const { return fooData.value(); } void setFoo(int i) { fooData.setValue(i); } int bar() const { return barData.value(); } void setBar(int i) { barData.setValue(i); } int read() const { return readData.value(); } int computed() const { return readData.value(); } QBindable bindableFoo() { return QBindable(&fooData); } QBindable bindableBar() { return QBindable(&barData); } QBindable bindableRead() { return QBindable(&readData); } QBindable bindableComputed() { return QBindable(&computedData); } public: int fooChangedCount = 0; int barChangedCount = 0; int readChangedCount = 0; Q_OBJECT_BINDABLE_PROPERTY(MyQObject, int, fooData, &MyQObject::fooChanged); Q_OBJECT_BINDABLE_PROPERTY(MyQObject, int, barData, &MyQObject::barChanged); Q_OBJECT_BINDABLE_PROPERTY(MyQObject, int, readData, &MyQObject::readChanged); Q_OBJECT_COMPUTED_PROPERTY(MyQObject, int, computedData, &MyQObject::computed); }; void tst_QProperty::testNewStuff() { MyQObject object; QObject::connect(&object, &MyQObject::fooChanged, &object, &MyQObject::fooHasChanged); QObject::connect(&object, &MyQObject::barChanged, &object, &MyQObject::barHasChanged); QObject::connect(&object, &MyQObject::readChanged, &object, &MyQObject::readHasChanged); QCOMPARE(object.fooChangedCount, 0); object.setFoo(10); QCOMPARE(object.fooChangedCount, 1); QCOMPARE(object.foo(), 10); auto f = [&object]() -> int { return object.barData; }; QCOMPARE(object.barChangedCount, 0); object.setBar(42); QCOMPARE(object.barChangedCount, 1); QCOMPARE(object.fooChangedCount, 1); object.fooData.setBinding(f); QCOMPARE(object.fooChangedCount, 2); QCOMPARE(object.fooData.value(), 42); object.setBar(666); QCOMPARE(object.fooChangedCount, 3); QCOMPARE(object.barChangedCount, 2); QCOMPARE(object.fooData.value(), 666); QCOMPARE(object.fooChangedCount, 3); auto f2 = [&object]() -> int { return object.barData / 2; }; object.bindableFoo().setBinding(Qt::makePropertyBinding(f2)); QVERIFY(object.bindableFoo().hasBinding()); QCOMPARE(object.foo(), 333); auto oldBinding = object.bindableFoo().setBinding(QPropertyBinding()); QVERIFY(!object.bindableFoo().hasBinding()); QVERIFY(!oldBinding.isNull()); QCOMPARE(object.foo(), 333); object.setBar(222); QCOMPARE(object.foo(), 333); object.bindableFoo().setBinding(oldBinding); QCOMPARE(object.foo(), 111); auto b = object.bindableRead().makeBinding(); object.bindableFoo().setBinding(b); QCOMPARE(object.foo(), 0); object.readData.setValue(10); QCOMPARE(object.foo(), 10); QCOMPARE(object.computed(), 10); object.readData.setValue(42); QCOMPARE(object.computed(), 42); object.bindableBar().setBinding(object.bindableComputed().makeBinding()); QCOMPARE(object.computed(), 42); object.readData.setValue(111); QCOMPARE(object.computed(), 111); } void tst_QProperty::qobjectObservers() { MyQObject object; int onValueChangedCalled = 0; { auto handler = object.bindableFoo().onValueChanged([&onValueChangedCalled]() { ++onValueChangedCalled;}); QCOMPARE(onValueChangedCalled, 0); object.setFoo(10); QCOMPARE(onValueChangedCalled, 1); object.bindableFoo().setBinding(object.bindableBar().makeBinding()); QCOMPARE(onValueChangedCalled, 2); object.setBar(42); QCOMPARE(onValueChangedCalled, 3); } object.setBar(0); QCOMPARE(onValueChangedCalled, 3); } QTEST_MAIN(tst_QProperty); #include "tst_qproperty.moc"