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>
This commit is contained in:
Gerhard Gappmeier 2012-07-02 13:01:32 +02:00 committed by The Qt Project
parent 6f225b0b5d
commit 9bbebb9144
6 changed files with 197 additions and 24 deletions

View File

@ -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]

View File

@ -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

View File

@ -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");

View File

@ -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);

View File

@ -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;

View File

@ -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