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:
parent
6f225b0b5d
commit
9bbebb9144
@ -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]
|
||||
|
||||
|
@ -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
|
||||
|
@ -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");
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user