summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGerhard Gappmeier <gerhard.gappmeier@ascolab.com>2012-07-02 13:01:32 +0200
committerThe Qt Project <gerrit-noreply@qt-project.org>2013-01-19 17:36:55 +0100
commit9bbebb914422262b7b585b6d1dab9d21c4238c44 (patch)
tree65a87988bb8d2d8bae3e81bbff2dce480f436435
parent6f225b0b5d774828df310948435f1cc3a4720104 (diff)
Add support for defining properties from member variables.
This associates properties with member variables and avoids writing getter and setter methods manually. The metaCall() method directly accesses the member variable, so additional method calls can be avoided. The metaCall() setter code also supports NOTIFY signals, which means the according signal is emitted when the property gets written. Task-number: QTBUG-16852 Change-Id: I88a1f237ea53a1e9cf65fc9ef2e207718eb8b6c3 Reviewed-by: Olivier Goffart <ogoffart@woboq.com>
-rw-r--r--src/corelib/doc/snippets/code/doc_src_properties.cpp21
-rw-r--r--src/corelib/doc/src/objectmodel/properties.qdoc26
-rw-r--r--src/tools/moc/generator.cpp37
-rw-r--r--src/tools/moc/moc.cpp12
-rw-r--r--src/tools/moc/moc.h2
-rw-r--r--tests/auto/tools/moc/tst_moc.cpp123
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