forked from woboq/verdigris
-
Notifications
You must be signed in to change notification settings - Fork 1
/
main.cpp
366 lines (280 loc) · 12 KB
/
main.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
/**
This file presents Verdigris, a "fork" of CopperSpice.
CopperSpice is a fork of Qt 4 whose specificity is to get rid of moc (Qt Meta-object compiler)
In order to get rid of moc, they changed the Qt macros to be less optimal.
They made a complete, incompatible fork of Qt.
Verdigris is not a fork of CopperSpice, but rather a re-implementation of their macro in a way
that is binary compatible with Qt. You can write your application without needing moc, and still
use it with existing Qt 5 releases.
CopperSpice generates the metaobjects at run-time. But moc generates them at compile time.
Verdigris uses constexpr to generate QMetaObject at compile time.
It is using C++14 for simplicity because it is much more easy to handle constexpr.
The code is originally taken from my previous work
https://woboq.com/blog/reflection-in-cpp-and-qt-moc.html
In that experiment, I was trying to use same macros that Qt does (keeping source compatibility).
When using more complex but uglier macros (CopperSpice style) we can do it without the moc.
*/
/** ******************************************************************************************** **/
/** INTRODUCTION **/
// In a header file you would include the wobjectdefs.h header
// This is equivalent to the qobjectdefs.h header
#include <wobjectdefs.h>
// And because you will inherit from QObject you also need to QObject header
#include <QObject>
// Now declare your class:
class MyObject : public QObject
{
/** The W_OBJECT macro is equivalent to the Q_OBJECT macro. The difference is that it must
contains the class name as a parameter and need to be put before any other W_ macro in the
class. So it's the same as the CS_OBJECT macro from CopperSpice. */
W_OBJECT(MyObject)
public /* slots */:
// Here we declare a slot:
void mySlot(const QString &name) { qDebug("hello %s", qPrintable(name)); }
/* If you're going to use the new connection syntax, no need to do anything else for slots.
But if you want to use the other connection syntax, or QML, you need to register the slot
just like so: */
W_SLOT(mySlot);
/* The W_SLOT has optional arguments that we will see later. It is already much simpler than
the two CopperSpice macros: CS_SLOT_1 and CS_SLOT_2. Also, CopperSpice slots cannot be
declared inline in the class definition. */
public /* signals */:
// Now a signal:
void mySignal(const QString &name)
W_SIGNAL(mySignal, name);
/* Note the absence of semi colon after the signal declaration */
};
/* Here is what would go in the C++ .cpp file: */
#include <wobjectimpl.h>
// And now this is the macro you need to instantiate the meta object.
// It's an additional macro that basically does the same as the code generated by moc.
W_OBJECT_IMPL(MyObject)
// That's it! MyObject is a QObject that can be used in QML or connected.
void aaa(MyObject *obj1) {
bool ok = true;
// new syntax
ok = ok && QObject::connect(obj1, &MyObject::mySignal, obj1, &MyObject::mySlot);
// old syntax
ok = ok && QObject::connect(obj1, SIGNAL(mySignal(QString)), obj1, SLOT(mySlot(QString)));
Q_ASSERT(ok);
}
/** ******************************************************************************************** **/
/** SLOTS **/
class SlotTutorial : public QObject {
W_OBJECT(SlotTutorial)
/**
W_SLOT( <slot name> [, (<parameters types>) ] [, <flags>]* )
The W_SLOT macro needs to be put after the slot declaration.
The W_SLOT macro can have the W_Compat for deprecated methods
(equivalent of Q_MOC_COMPAT
The W_SLOT macro can optionally have a list of parameter types as second
argument to disambiguate or declare types.
*/
/* Examples: */
protected:
// Declares a protected slot
void protectedSlot() {}
W_SLOT(protectedSlot);
private:
// and a private slot
void privateSlot() {}
W_SLOT(privateSlot);
public:
// Overloaded function needs a parameter list as second argument of the macro
// to disambiguate
void overload() {}
W_SLOT(overload, ());
void overload(int) {}
W_SLOT(overload, (int));
private:
void overload(double) {}
W_SLOT(overload, (double));
void overload(int, int) {}
W_SLOT(overload, (int, int));
// Note: for custom type that are not const reference, one must use the normalized signature
};
W_OBJECT_IMPL(SlotTutorial)
/** ******************************************************************************************** **/
/** SIGNALS **/
class SignalTutorial : public QObject {
W_OBJECT(SignalTutorial)
/**
<signal signature>
W_SIGNAL( <signal name> [, (<parameter types>) ] , <parameter names> )
Unlike W_SLOT, W_SIGNAL must be placed directly after the signal signature declaration.
There should not be a semi colon after the signal signature.
*/
public:
// Example:
void sig1(int a , int b)
W_SIGNAL(sig1, a, b);
// Or on the same line
void sig2(int a, int b) W_SIGNAL(sig2, a, b);
// For overloaded signals:
void overload(int a, int b)
W_SIGNAL(overload, (int, int), a, b);
};
W_OBJECT_IMPL(SignalTutorial)
/** ******************************************************************************************** **/
/** Gadgets, invokable, constructor **/
class InvokableTutorial {
// Just like Qt has Q_GADGET, here we have W_GADGET
W_GADGET(InvokableTutorial)
public:
/** W_INVOKABLE is the same as W_SLOT.
* It can take another flag (W_Scriptable) which corresponds to Q_SCRIPTABLE */
void myInvokable() {}
W_INVOKABLE(myInvokable);
/** W_CONSTRUCTOR(<parameter types>)
for Q_INVOKABLE constructor, just pass the parameter types to this macro.
one can have W_CONSTRUCTOR() for the default constructor even if it is implicit
*/
InvokableTutorial(int, int) {}
W_CONSTRUCTOR(int, int);
InvokableTutorial(void*, void* =nullptr) {}
W_CONSTRUCTOR(void*, void*);
// Because of the default argument we can also do that: (only in this macro)
W_CONSTRUCTOR(void*);
};
// For gadget there is also a different IMPL macro
W_GADGET_IMPL(InvokableTutorial)
/** ******************************************************************************************** **/
/** PROPERTY **/
#include <QtCore/QMap>
class PropertyTutorial : public QObject {
W_OBJECT(PropertyTutorial)
public:
/** W_PROPERTY(<type>, <name> [, <flags>]*)
There are the macro READ WRITE MEMBER and so on which have been defined so
you can just add a comma after the type, just like in a Q_PROPERTY.
W_PROPERTY need to be put after all the setters, getters, signals and members
have been declared.
*/
QString m_value;
QString value() const { return m_value; }
void setValue(const QString &value) {
m_value = value;
emit valueChanged();
}
void valueChanged()
W_SIGNAL(valueChanged);
// Just like in Qt only with one additional comma after the type
W_PROPERTY(QString, prop1 READ value WRITE setValue NOTIFY valueChanged);
// Is equivalent to:
W_PROPERTY(QString, prop2, &PropertyTutorial::value, &PropertyTutorial::setValue,
w_internal::Notify{}, &PropertyTutorial::valueChanged);
// The setter and getter are matched by signature. add W_Notify before the notify signal
// By member:
W_PROPERTY(QString, prop3 MEMBER m_value NOTIFY valueChanged);
//equivalent to
W_PROPERTY(QString, prop4, &PropertyTutorial::m_value, w_internal::Notify{}, &PropertyTutorial::valueChanged);
// Optionally, you can put parentheses around the type, useful if it contains a comma
QMap<int, int> m_map;
W_PROPERTY((QMap<int,int>), map MEMBER m_map);
};
W_OBJECT_IMPL(PropertyTutorial)
/** ******************************************************************************************** **/
/** Enums **/
class EnumTutorial {
W_GADGET(EnumTutorial)
public:
/** W_ENUM(<name>, <values>)
Similar to Q_ENUM, but we also have to manually write all the values.
Maybe in the future it could be made nicer that declares the enum in the same macro
but now that's all we have */
enum MyEnum { Blue, Red, Green, Yellow = 45, Violet = Blue + Green*3 };
W_ENUM(MyEnum, Blue, Red, Green, Yellow);
// CS_ENUM is a bit better, but i don't believe CopperSpice works with complex expressions
// such as "Blue + Green*3".
// W_ENUM is currently limited to 16 enum values.
// enum class are not yet supported
// There is a W_FLAG which is the same as Q_FLAG
};
W_GADGET_IMPL(EnumTutorial)
/** ******************************************************************************************** **/
/** TYPE REGISTRATION **/
/* Here is where the thing gets a bit more awkward:
The types of parameters of signals and slots need to be registered so that we can generate the
function signature
To use a type as a return type or signal slot parameter, it needs to:
- be a builtin QMetaType; or
- be registered with W_REGISTER_ARGTYPE; or
- use the overload syntax, but not with const reference.
*/
struct CustomType1 {};
struct CustomType2 {};
struct CustomType3 {};
/** W_REGISTER_ARGTYPE(TYPE)
register TYPE so it can be used as a parameter of a signal/slot or return value
One must use the normalized signature.
Note: This does not imply Q_DECLARE_METATYPE, and Q_DECLARE_METATYPE does not imply this.
*/
W_REGISTER_ARGTYPE(CustomType1)
W_REGISTER_ARGTYPE(CustomType1*)
W_REGISTER_ARGTYPE(CustomType2)
class ArgTypes : public QObject {
W_OBJECT(ArgTypes)
public:
void slot1(CustomType1, CustomType2) {}
W_SLOT(slot1); // OK, all arguments register with W_REGISTER_ARGTYPE
void slot2(CustomType1 *, CustomType2 *) {}
W_SLOT(slot2, (CustomType1*,CustomType2*)); // Need to use the overload syntax because
// CustomType2* is not registered
typedef int MyInt;
typedef CustomType1 MyCustomType1;
void slot3(ArgTypes::MyInt, ArgTypes::MyCustomType1) {}
W_SLOT(slot3, (ArgTypes::MyInt,ArgTypes::MyCustomType1)); // Need to use the overload syntax to use
// different type name (typedefs)
};
W_OBJECT_IMPL(ArgTypes)
/** ******************************************************************************************** **/
/** TEMPLATES **/
#include <QtCore/QDebug>
// We can have templated class:
template<typename T>
class MyTemplate : public QObject {
W_OBJECT(MyTemplate)
public:
// Template class can have slots and signals that depends on the parameter:
void slot(T t) { qDebug() << "templated slot" << t; }
W_SLOT(slot);
void signal(T t)
W_SIGNAL(signal, t);
};
//The syntax of W_OBJECT_IMPL changes a bit: as a second parameter you need to specify the template
//prefix:
W_OBJECT_IMPL(MyTemplate<T>, template <typename T>)
// When you have several template arguments:
template<typename A, typename B> class MyTemplate2 : public QObject {
W_OBJECT(MyTemplate2)
};
// The first argument of W_OBJECT_IMPL need to be within parentheses:
W_OBJECT_IMPL((MyTemplate2<A,B>), template<typename A, typename B>)
void templ() {
// This shows that it is possible;
bool ok = true;
MyTemplate<QString> obj;
// old syntax
ok = ok && QObject::connect(&obj, SIGNAL(signal(QString)), &obj, SLOT(slot(QString)));
// new syntax
ok = ok && QObject::connect(&obj, &MyTemplate<QString>::signal, &obj, &MyTemplate<QString>::slot);
Q_ASSERT(ok);
emit obj.signal("Hallo"); // Will show the qDebug twice
}
/** ******************************************************************************************** **/
// Nested classes are possible:
struct MyStruct {
class Nested : public QObject {
W_OBJECT(Nested)
public:
int foobar() const { return 0; }
W_INVOKABLE(foobar);
};
};
W_OBJECT_IMPL(MyStruct::Nested)
/** ******************************************************************************************** **/
int main() {
MyObject o;
aaa(&o);
templ();
}