diff options
-rw-r--r-- | src/corelib/doc/snippets/code/doc_src_properties.cpp | 21 | ||||
-rw-r--r-- | src/corelib/doc/src/objectmodel/properties.qdoc | 26 | ||||
-rw-r--r-- | src/tools/moc/generator.cpp | 37 | ||||
-rw-r--r-- | src/tools/moc/moc.cpp | 12 | ||||
-rw-r--r-- | src/tools/moc/moc.h | 2 | ||||
-rw-r--r-- | tests/auto/tools/moc/tst_moc.cpp | 123 |
6 files changed, 197 insertions, 24 deletions
diff --git a/src/corelib/doc/snippets/code/doc_src_properties.cpp b/src/corelib/doc/snippets/code/doc_src_properties.cpp index 7ee414e00e..a67cbb68aa 100644 --- a/src/corelib/doc/snippets/code/doc_src_properties.cpp +++ b/src/corelib/doc/snippets/code/doc_src_properties.cpp @@ -40,8 +40,8 @@ //! [0] Q_PROPERTY(type name - READ getFunction - [WRITE setFunction] + (READ getFunction [WRITE setFunction] | + MEMBER memberName [(READ getFunction | WRITE setFunction)]) [RESET resetFunction] [NOTIFY notifySignal] [REVISION int] @@ -130,3 +130,20 @@ object->setProperty("priority", "VeryHigh"); //! [7] Q_CLASSINFO("Version", "3.0.0") //! [7] + +//! [8] + Q_PROPERTY(QColor color MEMBER m_color NOTIFY colorChanged) + Q_PROPERTY(qreal spacing MEMBER m_spacing NOTIFY spacingChanged) + Q_PROPERTY(QString text MEMBER m_text NOTIFY textChanged) + ... +signals: + void colorChanged(); + void spacingChanged(); + void textChanged(const QString &newText); + +private: + QColor m_color; + qreal m_spacing; + QString m_text; +//! [8] + diff --git a/src/corelib/doc/src/objectmodel/properties.qdoc b/src/corelib/doc/src/objectmodel/properties.qdoc index 1e88a67a90..e262adf886 100644 --- a/src/corelib/doc/src/objectmodel/properties.qdoc +++ b/src/corelib/doc/src/objectmodel/properties.qdoc @@ -53,16 +53,22 @@ \snippet code/doc_src_properties.cpp 1 + Here is an example showing how to export member variables as Qt + properties using the \c MEMBER keyword. + Note that a \c NOTIFY signal must be specified to allow QML property bindings. + + \snippet code/doc_src_properties.cpp 8 + A property behaves like a class data member, but it has additional features accessible through the \l {Meta-Object System}. \list - \li A \c READ accessor function is required. It is for reading the - property value. Ideally, a const function is used for this purpose, - and it must return either the property's type or a pointer or - reference to that type. e.g., QWidget::focus is a read-only property - with \c READ function, QWidget::hasFocus(). + \li A \c READ accessor function is required if no \c MEMBER variable was + specified. It is for reading the property value. Ideally, a const function + is used for this purpose, and it must return either the property's type or a + pointer or reference to that type. e.g., QWidget::focus is a read-only + property with \c READ function, QWidget::hasFocus(). \li A \c WRITE accessor function is optional. It is for setting the property value. It must return void and must take exactly one @@ -71,6 +77,13 @@ QWidget::setEnabled(). Read-only properties do not need \c WRITE functions. e.g., QWidget::focus has no \c WRITE function. + \li A \c MEMBER variable association is required if no \c READ accessor + function is specified. This makes the given member variable + readable and writable without the need of creating \c READ and \c WRITE accessor + functions. It's still possible to use \c READ or \c WRITE accessor functions in + addition to \c MEMBER variable association (but not both), if you need to + control the variable access. + \li A \c RESET function is optional. It is for setting the property back to its context specific default value. e.g., QWidget::cursor has the typical \c READ and \c WRITE functions, QWidget::cursor() @@ -82,6 +95,9 @@ \li A \c NOTIFY signal is optional. If defined, it should specify one existing signal in that class that is emitted whenever the value of the property changes. + \c NOTIFY signals for \c MEMBER variables must take zero or one parameter, + which must be of the same type as the property. The parameter will take the + new value of the property. \li A \c REVISION number is optional. If included, it defines the property and its notifier signal to be used in a particular diff --git a/src/tools/moc/generator.cpp b/src/tools/moc/generator.cpp index 06efd77adc..083b095094 100644 --- a/src/tools/moc/generator.cpp +++ b/src/tools/moc/generator.cpp @@ -719,7 +719,9 @@ void Generator::generateProperties() uint flags = Invalid; if (!isBuiltinType(p.type)) flags |= EnumOrFlag; - if (!p.read.isEmpty()) + if (!p.member.isEmpty() && !p.constant) + flags |= Writable; + if (!p.read.isEmpty() || !p.member.isEmpty()) flags |= Readable; if (!p.write.isEmpty()) { flags |= Writable; @@ -893,12 +895,12 @@ void Generator::generateMetacall() bool needUser = false; for (int i = 0; i < cdef->propertyList.size(); ++i) { const PropertyDef &p = cdef->propertyList.at(i); - needGet |= !p.read.isEmpty(); + needGet |= !p.read.isEmpty() || !p.member.isEmpty(); if (!p.read.isEmpty()) needTempVarForGet |= (p.gspec != PropertyDef::PointerSpec && p.gspec != PropertyDef::ReferenceSpec); - needSet |= !p.write.isEmpty(); + needSet |= !p.write.isEmpty() || (!p.member.isEmpty() && !p.constant); needReset |= !p.reset.isEmpty(); needDesignable |= p.designable.endsWith(')'); needScriptable |= p.scriptable.endsWith(')'); @@ -917,7 +919,7 @@ void Generator::generateMetacall() fprintf(out, " switch (_id) {\n"); for (int propindex = 0; propindex < cdef->propertyList.size(); ++propindex) { const PropertyDef &p = cdef->propertyList.at(propindex); - if (p.read.isEmpty()) + if (p.read.isEmpty() && p.member.isEmpty()) continue; QByteArray prefix; if (p.inPrivateClass.size()) { @@ -933,9 +935,12 @@ void Generator::generateMetacall() else if (cdef->enumDeclarations.value(p.type, false)) fprintf(out, " case %d: *reinterpret_cast<int*>(_v) = QFlag(%s%s()); break;\n", propindex, prefix.constData(), p.read.constData()); - else + else if (!p.read.isEmpty()) fprintf(out, " case %d: *reinterpret_cast< %s*>(_v) = %s%s(); break;\n", propindex, p.type.constData(), prefix.constData(), p.read.constData()); + else + fprintf(out, " case %d: *reinterpret_cast< %s*>(_v) = %s%s; break;\n", + propindex, p.type.constData(), prefix.constData(), p.member.constData()); } fprintf(out, " }\n"); } @@ -952,7 +957,9 @@ void Generator::generateMetacall() fprintf(out, " switch (_id) {\n"); for (int propindex = 0; propindex < cdef->propertyList.size(); ++propindex) { const PropertyDef &p = cdef->propertyList.at(propindex); - if (p.write.isEmpty()) + if (p.constant) + continue; + if (p.write.isEmpty() && p.member.isEmpty()) continue; QByteArray prefix; if (p.inPrivateClass.size()) { @@ -962,9 +969,25 @@ void Generator::generateMetacall() if (cdef->enumDeclarations.value(p.type, false)) { fprintf(out, " case %d: %s%s(QFlag(*reinterpret_cast<int*>(_v))); break;\n", propindex, prefix.constData(), p.write.constData()); - } else { + } else if (!p.write.isEmpty()) { fprintf(out, " case %d: %s%s(*reinterpret_cast< %s*>(_v)); break;\n", propindex, prefix.constData(), p.write.constData(), p.type.constData()); + } else { + fprintf(out, " case %d:\n", propindex); + fprintf(out, " if (%s%s != *reinterpret_cast< %s*>(_v)) {\n", + prefix.constData(), p.member.constData(), p.type.constData()); + fprintf(out, " %s%s = *reinterpret_cast< %s*>(_v);\n", + prefix.constData(), p.member.constData(), p.type.constData()); + if (!p.notify.isEmpty() && p.notifyId != -1) { + const FunctionDef &f = cdef->signalList.at(p.notifyId); + if (f.arguments.size() == 0) + fprintf(out, " emit %s();\n", p.notify.constData()); + else if (f.arguments.size() == 1 && f.arguments.at(0).normalizedType == p.type) + fprintf(out, " emit %s(%s%s);\n", + p.notify.constData(), prefix.constData(), p.member.constData()); + } + fprintf(out, " }\n"); + fprintf(out, " break;\n"); } } fprintf(out, " }\n"); diff --git a/src/tools/moc/moc.cpp b/src/tools/moc/moc.cpp index 5fbbd57c22..788033800f 100644 --- a/src/tools/moc/moc.cpp +++ b/src/tools/moc/moc.cpp @@ -1059,6 +1059,12 @@ void Moc::createPropertyDef(PropertyDef &propDef) v2 = "()"; } switch (l[0]) { + case 'M': + if (l == "MEMBER") + propDef.member = v; + else + error(2); + break; case 'R': if (l == "READ") propDef.read = v; @@ -1099,11 +1105,11 @@ void Moc::createPropertyDef(PropertyDef &propDef) error(2); } } - if (propDef.read.isNull()) { + if (propDef.read.isNull() && propDef.member.isNull()) { QByteArray msg; msg += "Property declaration "; msg += propDef.name; - msg += " has no READ accessor function. The property will be invalid."; + msg += " has no READ accessor function or associated MEMBER variable. The property will be invalid."; warning(msg.constData()); } if (propDef.constant && !propDef.write.isNull()) { @@ -1515,7 +1521,7 @@ void Moc::checkProperties(ClassDef *cdef) // for (int i = 0; i < cdef->propertyList.count(); ++i) { PropertyDef &p = cdef->propertyList[i]; - if (p.read.isEmpty()) + if (p.read.isEmpty() && p.member.isEmpty()) continue; for (int j = 0; j < cdef->publicList.count(); ++j) { const FunctionDef &f = cdef->publicList.at(j); diff --git a/src/tools/moc/moc.h b/src/tools/moc/moc.h index 07f8c7c620..cfa56c4da2 100644 --- a/src/tools/moc/moc.h +++ b/src/tools/moc/moc.h @@ -127,7 +127,7 @@ struct FunctionDef struct PropertyDef { PropertyDef():notifyId(-1), constant(false), final(false), gspec(ValueSpec), revision(0){} - QByteArray name, type, read, write, reset, designable, scriptable, editable, stored, user, notify, inPrivateClass; + QByteArray name, type, member, read, write, reset, designable, scriptable, editable, stored, user, notify, inPrivateClass; int notifyId; bool constant; bool final; diff --git a/tests/auto/tools/moc/tst_moc.cpp b/tests/auto/tools/moc/tst_moc.cpp index 76607ccf4e..3ee0246d7f 100644 --- a/tests/auto/tools/moc/tst_moc.cpp +++ b/tests/auto/tools/moc/tst_moc.cpp @@ -480,6 +480,8 @@ CtorTestClass::CtorTestClass(QObject *parent) CtorTestClass::CtorTestClass(int, int, int) {} +class PrivatePropertyTest; + class tst_Moc : public QObject { Q_OBJECT @@ -487,9 +489,15 @@ class tst_Moc : public QObject Q_PROPERTY(bool user1 READ user1 USER true ) Q_PROPERTY(bool user2 READ user2 USER false) Q_PROPERTY(bool user3 READ user3 USER userFunction()) + Q_PROPERTY(QString member1 MEMBER sMember) + Q_PROPERTY(QString member2 MEMBER sMember READ member2) + Q_PROPERTY(QString member3 MEMBER sMember WRITE setMember3) + Q_PROPERTY(QString member4 MEMBER sMember NOTIFY member4Changed) + Q_PROPERTY(QString member5 MEMBER sMember NOTIFY member5Changed) + Q_PROPERTY(QString member6 MEMBER sConst CONSTANT) public: - inline tst_Moc() {} + inline tst_Moc() : sConst("const") {} private slots: void initTestCase(); @@ -546,6 +554,9 @@ private slots: void cxx11Enums_data(); void cxx11Enums(); void returnRefs(); + void memberProperties_data(); + void memberProperties(); + void privateSignalConnection(); void finalClasses_data(); void finalClasses(); @@ -565,6 +576,8 @@ signals: void sigWithCustomType(const MyStruct); void constSignal1() const; void constSignal2(int arg) const; + void member4Changed(); + void member5Changed(const QString &newVal); private: bool user1() { return true; }; @@ -572,10 +585,15 @@ private: bool user3() { return false; }; bool userFunction(){ return false; }; template <class T> void revisions_T(); + QString member2() const { return sMember; } + void setMember3( const QString &sVal ) { sMember = sVal; } private: QString qtIncludePath; class PrivateClass; + QString sMember; + const QString sConst; + PrivatePropertyTest *pPPTest; }; void tst_Moc::initTestCase() @@ -1164,25 +1182,38 @@ class PrivatePropertyTest : public QObject Q_PRIVATE_PROPERTY(d, int bar READ bar WRITE setBar) Q_PRIVATE_PROPERTY(PrivatePropertyTest::d, int plop READ plop WRITE setPlop) Q_PRIVATE_PROPERTY(PrivatePropertyTest::d_func(), int baz READ baz WRITE setBaz) + Q_PRIVATE_PROPERTY(PrivatePropertyTest::d, QString blub MEMBER mBlub) + Q_PRIVATE_PROPERTY(PrivatePropertyTest::d, QString blub2 MEMBER mBlub READ blub) + Q_PRIVATE_PROPERTY(PrivatePropertyTest::d, QString blub3 MEMBER mBlub WRITE setBlub) + Q_PRIVATE_PROPERTY(PrivatePropertyTest::d, QString blub4 MEMBER mBlub NOTIFY blub4Changed) + Q_PRIVATE_PROPERTY(PrivatePropertyTest::d, QString blub5 MEMBER mBlub NOTIFY blub5Changed) + Q_PRIVATE_PROPERTY(PrivatePropertyTest::d, QString blub6 MEMBER mConst CONSTANT) class MyDPointer { public: - MyDPointer() : mBar(0), mPlop(0) {} + MyDPointer() : mConst("const"), mBar(0), mPlop(0) {} int bar() { return mBar ; } void setBar(int value) { mBar = value; } int plop() { return mPlop ; } void setPlop(int value) { mPlop = value; } int baz() { return mBaz ; } void setBaz(int value) { mBaz = value; } + QString blub() const { return mBlub; } + void setBlub(const QString &value) { mBlub = value; } + QString mBlub; + const QString mConst; private: int mBar; int mPlop; int mBaz; }; public: - PrivatePropertyTest() : mFoo(0), d (new MyDPointer) {} + PrivatePropertyTest(QObject *parent = 0) : QObject(parent), mFoo(0), d (new MyDPointer) {} int foo() { return mFoo ; } void setFoo(int value) { mFoo = value; } MyDPointer *d_func() {return d;} +signals: + void blub4Changed(); + void blub5Changed(const QString &newBlub); private: int mFoo; MyDPointer *d; @@ -1236,7 +1267,7 @@ void tst_Moc::warnOnPropertyWithoutREAD() QVERIFY(!mocOut.isEmpty()); QString mocWarning = QString::fromLocal8Bit(proc.readAllStandardError()); QCOMPARE(mocWarning, QString(SRCDIR) + - QString("/warn-on-property-without-read.h:46: Warning: Property declaration foo has no READ accessor function. The property will be invalid.\n")); + QString("/warn-on-property-without-read.h:46: Warning: Property declaration foo has no READ accessor function or associated MEMBER variable. The property will be invalid.\n")); #else QSKIP("Only tested on linux/gcc"); #endif @@ -1640,7 +1671,7 @@ void tst_Moc::warnings_data() << QStringList() << 0 << QString("IGNORE_ALL_STDOUT") - << QString("standard input:1: Warning: Property declaration x has no READ accessor function. The property will be invalid."); + << QString("standard input:1: Warning: Property declaration x has no READ accessor function or associated MEMBER variable. The property will be invalid."); // Passing "-nn" should NOT suppress the warning QTest::newRow("Invalid property warning with -nn") @@ -1648,7 +1679,7 @@ void tst_Moc::warnings_data() << (QStringList() << "-nn") << 0 << QString("IGNORE_ALL_STDOUT") - << QString("standard input:1: Warning: Property declaration x has no READ accessor function. The property will be invalid."); + << QString("standard input:1: Warning: Property declaration x has no READ accessor function or associated MEMBER variable. The property will be invalid."); // Passing "-nw" should suppress the warning QTest::newRow("Invalid property warning with -nw") @@ -1782,6 +1813,86 @@ void tst_Moc::returnRefs() // they used to cause miscompilation of the moc generated file. } +void tst_Moc::memberProperties_data() +{ + QTest::addColumn<int>("object"); + QTest::addColumn<QString>("property"); + QTest::addColumn<QString>("signal"); + QTest::addColumn<QString>("writeValue"); + QTest::addColumn<bool>("expectedWriteResult"); + QTest::addColumn<QString>("expectedReadResult"); + + pPPTest = new PrivatePropertyTest( this ); + + QTest::newRow("MEMBER property") + << 0 << "member1" << "" << "abc" << true << "abc"; + QTest::newRow("MEMBER property with READ function") + << 0 << "member2" << "" << "def" << true << "def"; + QTest::newRow("MEMBER property with WRITE function") + << 0 << "member3" << "" << "ghi" << true << "ghi"; + QTest::newRow("MEMBER property with NOTIFY") + << 0 << "member4" << "member4Changed()" << "lmn" << true << "lmn"; + QTest::newRow("MEMBER property with NOTIFY(value)") + << 0 << "member5" << "member5Changed(const QString&)" << "opq" << true << "opq"; + QTest::newRow("MEMBER property with CONSTANT") + << 0 << "member6" << "" << "test" << false << "const"; + QTest::newRow("private MEMBER property") + << 1 << "blub" << "" << "abc" << true << "abc"; + QTest::newRow("private MEMBER property with READ function") + << 1 << "blub2" << "" << "def" << true << "def"; + QTest::newRow("private MEMBER property with WRITE function") + << 1 << "blub3" << "" << "ghi" << true << "ghi"; + QTest::newRow("private MEMBER property with NOTIFY") + << 1 << "blub4" << "blub4Changed()" << "jkl" << true << "jkl"; + QTest::newRow("private MEMBER property with NOTIFY(value)") + << 1 << "blub5" << "blub5Changed(const QString&)" << "mno" << true << "mno"; + QTest::newRow("private MEMBER property with CONSTANT") + << 1 << "blub6" << "" << "test" << false << "const"; +} + +void tst_Moc::memberProperties() +{ + QFETCH(int, object); + QFETCH(QString, property); + QFETCH(QString, signal); + QFETCH(QString, writeValue); + QFETCH(bool, expectedWriteResult); + QFETCH(QString, expectedReadResult); + + QObject *pObj = (object == 0) ? this : static_cast<QObject*>(pPPTest); + + QString sSignalDeclaration; + if (!signal.isEmpty()) + sSignalDeclaration = QString(SIGNAL(%1)).arg(signal); + else + QTest::ignoreMessage(QtWarningMsg, "QSignalSpy: Not a valid signal, use the SIGNAL macro"); + QSignalSpy notifySpy(pObj, sSignalDeclaration.toLatin1().constData()); + + int index = pObj->metaObject()->indexOfProperty(property.toLatin1().constData()); + QVERIFY(index != -1); + QMetaProperty prop = pObj->metaObject()->property(index); + + QCOMPARE(prop.write(pObj, writeValue), expectedWriteResult); + + QVariant readValue = prop.read(pObj); + QCOMPARE(readValue.toString(), expectedReadResult); + + if (!signal.isEmpty()) + { + QCOMPARE(notifySpy.count(), 1); + if (prop.notifySignal().parameterNames().size() > 0) { + QList<QVariant> arguments = notifySpy.takeFirst(); + QCOMPARE(arguments.size(), 1); + QCOMPARE(arguments.at(0).toString(), expectedReadResult); + } + + notifySpy.clear(); + // a second write with the same value should not cause the signal to be emitted again + QCOMPARE(prop.write(pObj, writeValue), expectedWriteResult); + QCOMPARE(notifySpy.count(), 0); + } +} + class SignalConnectionTester : public QObject { Q_OBJECT |