1 // This contains the meta-type used by PyQt.
2 //
3 // Copyright (c) 2021 Riverbank Computing Limited <info@riverbankcomputing.com>
4 //
5 // This file is part of PyQt5.
6 //
7 // This file may be used under the terms of the GNU General Public License
8 // version 3.0 as published by the Free Software Foundation and appearing in
9 // the file LICENSE included in the packaging of this file. Please review the
10 // following information to ensure the GNU General Public License version 3.0
11 // requirements will be met: http://www.gnu.org/copyleft/gpl.html.
12 //
13 // If you do not wish to use this file under the terms of the GPL version 3.0
14 // then you may purchase a commercial license. For more information contact
15 // info@riverbankcomputing.com.
16 //
17 // This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
18 // WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
19
20
21 #include <Python.h>
22
23 #include <QByteArray>
24 #include <QList>
25 #include <QMap>
26 #include <QMetaObject>
27 #include <QMetaType>
28 #include <QPair>
29
30 #include "qpycore_chimera.h"
31 #include "qpycore_classinfo.h"
32 #include "qpycore_enums_flags.h"
33 #include "qpycore_misc.h"
34 #include "qpycore_objectified_strings.h"
35 #include "qpycore_pyqtproperty.h"
36 #include "qpycore_pyqtsignal.h"
37 #include "qpycore_pyqtslot.h"
38 #include "qpycore_qmetaobjectbuilder.h"
39 #include "qpycore_types.h"
40
41 #include "sipAPIQtCore.h"
42
43
44 // A tuple of the property name and definition.
45 typedef QPair<PyObject *, PyObject *> PropertyData;
46
47
48 // Forward declarations.
49 static int trawl_hierarchy(PyTypeObject *pytype, qpycore_metaobject *qo,
50 QMetaObjectBuilder &builder, QList<const qpycore_pyqtSignal *> &psigs,
51 QMap<uint, PropertyData> &pprops);
52 static int trawl_type(PyTypeObject *pytype, qpycore_metaobject *qo,
53 QMetaObjectBuilder &builder, QList<const qpycore_pyqtSignal *> &psigs,
54 QMap<uint, PropertyData> &pprops);
55 static qpycore_metaobject *create_dynamic_metaobject(sipWrapperType *wt);
56 static const QMetaObject *get_scope_qmetaobject(const Chimera *ct);
57
58
59 // The handler invoked whenever a Python sub-class of QObject is defined.
qpycore_new_user_type_handler(sipWrapperType * wt)60 int qpycore_new_user_type_handler(sipWrapperType *wt)
61 {
62 const pyqt5ClassPluginDef *cpd = reinterpret_cast<const pyqt5ClassPluginDef *>(sipTypePluginData(sipTypeFromPyTypeObject((PyTypeObject *)wt)));
63
64 Q_ASSERT(cpd);
65
66 // Create a dynamic meta-object as its base wrapped type has a static Qt
67 // meta-object.
68 if (cpd->static_metaobject)
69 {
70 qpycore_metaobject *qo = create_dynamic_metaobject(wt);
71
72 if (!qo)
73 return -1;
74
75 sipSetTypeUserData(wt, qo);
76 }
77
78 return 0;
79 }
80
81
82 // Create a dynamic meta-object for a Python type by introspecting its
83 // attributes. Note that it leaks if the type is deleted.
create_dynamic_metaobject(sipWrapperType * wt)84 static qpycore_metaobject *create_dynamic_metaobject(sipWrapperType *wt)
85 {
86 PyTypeObject *pytype = (PyTypeObject *)wt;
87 qpycore_metaobject *qo = new qpycore_metaobject;
88 QMetaObjectBuilder builder;
89
90 // Get any class info.
91 QList<ClassInfo> class_info_list = qpycore_get_class_info_list();
92
93 // Get any enums/flags.
94 QList<EnumFlag> enums_flags_list = qpycore_get_enums_flags_list();
95
96 // Get the super-type's meta-object.
97 PyTypeObject *tp_base;
98
99 #if PY_VERSION_HEX >= 0x03040000
100 tp_base = reinterpret_cast<PyTypeObject *>(
101 PyType_GetSlot(pytype, Py_tp_base));
102 #else
103 tp_base = pytype->tp_base;
104 #endif
105
106 builder.setSuperClass(qpycore_get_qmetaobject((sipWrapperType *)tp_base));
107
108 // Get the name of the type. Dynamic types have simple names.
109 builder.setClassName(sipPyTypeName(pytype));
110
111 // Go through the class hierarchy getting all PyQt properties, slots and
112 // signals.
113
114 QList<const qpycore_pyqtSignal *> psigs;
115 QMap<uint, PropertyData> pprops;
116
117 if (trawl_hierarchy(pytype, qo, builder, psigs, pprops) < 0)
118 return 0;
119
120 qo->nr_signals = psigs.count();
121
122 // Initialise the header section of the data table. Note that Qt v4.5
123 // introduced revision 2 which added constructors. However the design is
124 // broken in that the static meta-call function doesn't provide enough
125 // information to determine which Python sub-class of a Qt class is to be
126 // created. So we stick with revision 1 (and don't allow pyqtSlot() to
127 // decorate __init__).
128
129 // Set up any class information.
130 for (int i = 0; i < class_info_list.count(); ++i)
131 {
132 const ClassInfo &ci = class_info_list.at(i);
133
134 builder.addClassInfo(ci.first, ci.second);
135 }
136
137 // Set up any enums/flags.
138 for (int i = 0; i < enums_flags_list.count(); ++i)
139 {
140 const EnumFlag &ef = enums_flags_list.at(i);
141
142 QMetaEnumBuilder enum_builder = builder.addEnumerator(ef.name);
143 enum_builder.setIsFlag(ef.isFlag);
144 #if QT_VERSION >= 0x050a00
145 enum_builder.setIsScoped(ef.isScoped);
146 #endif
147
148 QHash<QByteArray, int>::const_iterator it = ef.keys.constBegin();
149
150 while (it != ef.keys.constEnd())
151 {
152 enum_builder.addKey(it.key(), it.value());
153 ++it;
154 }
155 }
156
157 // Add the signals to the meta-object.
158 for (int g = 0; g < qo->nr_signals; ++g)
159 {
160 const qpycore_pyqtSignal *ps = psigs.at(g);
161
162 QMetaMethodBuilder signal_builder = builder.addSignal(
163 ps->parsed_signature->signature.mid(1));
164
165 if (ps->parameter_names)
166 signal_builder.setParameterNames(*ps->parameter_names);
167
168 signal_builder.setRevision(ps->revision);
169 }
170
171 // Add the slots to the meta-object.
172 for (int s = 0; s < qo->pslots.count(); ++s)
173 {
174 const Chimera::Signature *slot_signature = qo->pslots.at(s)->slotSignature();
175 const QByteArray &sig = slot_signature->signature;
176
177 QMetaMethodBuilder slot_builder = builder.addSlot(sig);
178
179 // Add any type.
180 if (slot_signature->result)
181 slot_builder.setReturnType(slot_signature->result->name());
182
183 slot_builder.setRevision(slot_signature->revision);
184 }
185
186 // Add the properties to the meta-object.
187 QMapIterator<uint, PropertyData> it(pprops);
188
189 for (int p = 0; it.hasNext(); ++p)
190 {
191 it.next();
192
193 const PropertyData &pprop = it.value();
194 const char *prop_name = SIPBytes_AsString(pprop.first);
195 qpycore_pyqtProperty *pp = (qpycore_pyqtProperty *)pprop.second;
196 int notifier_id;
197
198 if (pp->pyqtprop_notify)
199 {
200 qpycore_pyqtSignal *ps = (qpycore_pyqtSignal *)pp->pyqtprop_notify;
201 const QByteArray &sig = ps->parsed_signature->signature;
202
203 notifier_id = builder.indexOfSignal(sig.mid(1));
204
205 if (notifier_id < 0)
206 {
207 PyErr_Format(PyExc_TypeError,
208 "the notify signal '%s' was not defined in this class",
209 sig.constData() + 1);
210
211 // Note that we leak the property name.
212 return 0;
213 }
214 }
215 else
216 {
217 notifier_id = -1;
218 }
219
220 // A Qt v5 revision 7 meta-object holds the QMetaType::Type of the type
221 // or its name if it is unresolved (ie. not known to the type system).
222 // In Qt v4 both are held. For QObject sub-classes Chimera will fall
223 // back to the QMetaType::QObjectStar if there is no specific meta-type
224 // for the sub-class. This means that, for Qt v4,
225 // QMetaProperty::read() can handle the type. However, Qt v5 doesn't
226 // know that the unresolved type is a QObject sub-class. Therefore we
227 // have to tell it that the property is a QObject, rather than the
228 // sub-class. This means that QMetaProperty.typeName() will always
229 // return "QObject*".
230 QByteArray prop_type;
231
232 if (pp->pyqtprop_parsed_type->metatype() == QMetaType::QObjectStar)
233 {
234 // However, if the type is a Python sub-class of QObject then we
235 // use the name of the Python type. This anticipates that the type
236 // is one that will be proxied by QML at some point.
237 if (pp->pyqtprop_parsed_type->typeDef() == sipType_QObject)
238 {
239 prop_type = sipPyTypeName(
240 (PyTypeObject *)pp->pyqtprop_parsed_type->py_type());
241 prop_type.append('*');
242 }
243 else
244 {
245 prop_type = "QObject*";
246 }
247 }
248 else
249 {
250 prop_type = pp->pyqtprop_parsed_type->name();
251 }
252
253 QMetaPropertyBuilder prop_builder = builder.addProperty(
254 QByteArray(prop_name), prop_type, notifier_id);
255
256 // Reset the defaults.
257 prop_builder.setReadable(false);
258 prop_builder.setWritable(false);
259
260 // Enum or flag.
261 if (pp->pyqtprop_parsed_type->isEnum())
262 {
263 prop_builder.setEnumOrFlag(true);
264 }
265
266 if (pp->pyqtprop_get && PyCallable_Check(pp->pyqtprop_get))
267 {
268 // Readable.
269 prop_builder.setReadable(true);
270 }
271
272 if (pp->pyqtprop_set && PyCallable_Check(pp->pyqtprop_set))
273 {
274 // Writable.
275 prop_builder.setWritable(true);
276
277 // See if the name of the setter follows the Designer convention.
278 // If so tell the UI compilers not to use setProperty().
279 PyObject *setter_name_obj = PyObject_GetAttr(pp->pyqtprop_set,
280 qpycore_dunder_name);
281
282 if (setter_name_obj)
283 {
284 PyObject *ascii_obj = setter_name_obj;
285 const char *ascii = sipString_AsASCIIString(&ascii_obj);
286 Py_DECREF(setter_name_obj);
287
288 if (ascii)
289 {
290 if (qstrlen(ascii) > 3 && ascii[0] == 's' &&
291 ascii[1] == 'e' && ascii[2] == 't' &&
292 ascii[3] == toupper(prop_name[0]) &&
293 qstrcmp(&ascii[4], &prop_name[1]) == 0)
294 prop_builder.setStdCppSet(true);
295 }
296
297 Py_DECREF(ascii_obj);
298 }
299
300 PyErr_Clear();
301 }
302
303 if (pp->pyqtprop_reset && PyCallable_Check(pp->pyqtprop_reset))
304 {
305 // Resetable.
306 prop_builder.setResettable(true);
307 }
308
309 // Add the property flags. Note that Qt4 always seems to have
310 // ResolveEditable set but QMetaObjectBuilder doesn't provide an API
311 // call to do it.
312 prop_builder.setDesignable(pp->pyqtprop_flags & 0x00001000);
313 prop_builder.setScriptable(pp->pyqtprop_flags & 0x00004000);
314 prop_builder.setStored(pp->pyqtprop_flags & 0x00010000);
315 prop_builder.setUser(pp->pyqtprop_flags & 0x00100000);
316 prop_builder.setConstant(pp->pyqtprop_flags & 0x00000400);
317 prop_builder.setFinal(pp->pyqtprop_flags & 0x00000800);
318
319 prop_builder.setRevision(pp->pyqtprop_revision);
320
321 // Save the property data for qt_metacall(). (We already have a
322 // reference.)
323 qo->pprops.append(pp);
324
325 // We've finished with the property name.
326 Py_DECREF(pprop.first);
327 }
328
329 // Initialise the rest of the meta-object.
330 qo->mo = builder.toMetaObject();
331
332 return qo;
333 }
334
335
336 // Trawl a type's hierarchy looking for any slots, signals or properties.
trawl_hierarchy(PyTypeObject * pytype,qpycore_metaobject * qo,QMetaObjectBuilder & builder,QList<const qpycore_pyqtSignal * > & psigs,QMap<uint,PropertyData> & pprops)337 static int trawl_hierarchy(PyTypeObject *pytype, qpycore_metaobject *qo,
338 QMetaObjectBuilder &builder, QList<const qpycore_pyqtSignal *> &psigs,
339 QMap<uint, PropertyData> &pprops)
340 {
341 if (trawl_type(pytype, qo, builder, psigs, pprops) < 0)
342 return -1;
343
344 PyObject *tp_bases;
345
346 #if PY_VERSION_HEX >= 0x03040000
347 if (PyType_HasFeature(pytype, Py_TPFLAGS_HEAPTYPE))
348 tp_bases = reinterpret_cast<PyObject *>(
349 PyType_GetSlot(pytype, Py_tp_bases));
350 else
351 tp_bases = 0;
352 #else
353 tp_bases = pytype->tp_bases;
354 #endif
355
356 if (!tp_bases)
357 return 0;
358
359 Q_ASSERT(PyTuple_Check(tp_bases));
360
361 for (Py_ssize_t i = 0; i < PyTuple_Size(tp_bases); ++i)
362 {
363 PyTypeObject *sup = (PyTypeObject *)PyTuple_GetItem(tp_bases, i);
364
365 #if PY_MAJOR_VERSION < 3
366 /* Ignore classic classes as mixins. */
367 if (PyClass_Check((PyObject *)sup))
368 continue;
369 #endif
370
371 if (PyType_IsSubtype(sup, sipTypeAsPyTypeObject(sipType_QObject)))
372 continue;
373
374 if (trawl_hierarchy(sup, qo, builder, psigs, pprops) < 0)
375 return -1;
376 }
377
378 return 0;
379 }
380
381
382 // Trawl a type's dict looking for any slots, signals or properties.
trawl_type(PyTypeObject * pytype,qpycore_metaobject * qo,QMetaObjectBuilder & builder,QList<const qpycore_pyqtSignal * > & psigs,QMap<uint,PropertyData> & pprops)383 static int trawl_type(PyTypeObject *pytype, qpycore_metaobject *qo,
384 QMetaObjectBuilder &builder, QList<const qpycore_pyqtSignal *> &psigs,
385 QMap<uint, PropertyData> &pprops)
386 {
387 Py_ssize_t pos = 0;
388 PyObject *key, *value, *dict;
389
390 dict = sipPyTypeDict(pytype);
391
392 while (PyDict_Next(dict, &pos, &key, &value))
393 {
394 // See if it is a slot, ie. it has been decorated with pyqtSlot().
395 PyObject *sig_obj = PyObject_GetAttr(value,
396 qpycore_dunder_pyqtsignature);
397
398 if (sig_obj)
399 {
400 // Make sure it is a list and not some legitimate attribute that
401 // happens to use our special name.
402 if (PyList_Check(sig_obj))
403 {
404 for (Py_ssize_t i = 0; i < PyList_Size(sig_obj); ++i)
405 {
406 // Set up the skeleton slot.
407 PyObject *decoration = PyList_GetItem(sig_obj, i);
408 Chimera::Signature *slot_signature = Chimera::Signature::fromPyObject(decoration);
409
410 // Check if a slot of the same signature has already been
411 // defined. This typically happens with sub-classed
412 // mixins.
413 bool overridden = false;
414
415 for (int i = 0; i < qo->pslots.size(); ++i)
416 if (qo->pslots.at(i)->slotSignature()->signature == slot_signature->signature)
417 {
418 overridden = true;
419 break;
420 }
421
422 if (!overridden)
423 {
424 PyQtSlot *slot = new PyQtSlot(value,
425 (PyObject *)pytype, slot_signature);
426
427 qo->pslots.append(slot);
428 }
429 }
430 }
431
432 Py_DECREF(sig_obj);
433 }
434 else
435 {
436 PyErr_Clear();
437
438 // Make sure the key is an ASCII string. Delay the error checking
439 // until we know we actually need it.
440 const char *ascii_key = sipString_AsASCIIString(&key);
441
442 // See if the value is of interest.
443 if (PyObject_TypeCheck(value, qpycore_pyqtProperty_TypeObject))
444 {
445 // It is a property.
446
447 if (!ascii_key)
448 return -1;
449
450 Py_INCREF(value);
451
452 qpycore_pyqtProperty *pp = (qpycore_pyqtProperty *)value;
453
454 pprops.insert(pp->pyqtprop_sequence, PropertyData(key, value));
455
456 // See if the property has a scope. If so, collect all
457 // QMetaObject pointers that are not in the super-class
458 // hierarchy.
459 const QMetaObject *mo = get_scope_qmetaobject(pp->pyqtprop_parsed_type);
460
461 if (mo)
462 builder.addRelatedMetaObject(mo);
463 }
464 else if (PyObject_TypeCheck(value, qpycore_pyqtSignal_TypeObject))
465 {
466 // It is a signal.
467
468 if (!ascii_key)
469 return -1;
470
471 qpycore_pyqtSignal *ps = (qpycore_pyqtSignal *)value;
472
473 // Make sure the signal has a name.
474 qpycore_set_signal_name(ps, sipPyTypeName(pytype), ascii_key);
475
476 // Add all the overloads.
477 do
478 {
479 psigs.append(ps);
480 ps = ps->next;
481 }
482 while (ps);
483
484 Py_DECREF(key);
485 }
486 else
487 {
488 PyErr_Clear();
489 }
490 }
491 }
492
493 return 0;
494 }
495
496
497 // Return the QMetaObject for an enum type's scope.
get_scope_qmetaobject(const Chimera * ct)498 static const QMetaObject *get_scope_qmetaobject(const Chimera *ct)
499 {
500 // Check it is an enum.
501 if (!ct->isEnum())
502 return 0;
503
504 // Check it has a scope.
505 if (!ct->typeDef())
506 return 0;
507
508 const sipTypeDef *td = sipTypeScope(ct->typeDef());
509
510 if (!td)
511 return 0;
512
513 // Check the scope is wrapped by PyQt.
514 if (!qpycore_is_pyqt_class(td))
515 return 0;
516
517 return qpycore_get_qmetaobject((sipWrapperType *)sipTypeAsPyTypeObject(td));
518 }
519
520
521 // Return the QMetaObject for a type.
qpycore_get_qmetaobject(sipWrapperType * wt,const sipTypeDef * base_td)522 const QMetaObject *qpycore_get_qmetaobject(sipWrapperType *wt,
523 const sipTypeDef *base_td)
524 {
525 if (wt)
526 {
527 qpycore_metaobject *qo = reinterpret_cast<qpycore_metaobject *>(
528 sipGetTypeUserData(wt));
529
530 // See if it has a dynamic meta-object.
531 if (qo)
532 return qo->mo;
533 }
534
535 // Get the static meta-object if there is one.
536 if (!base_td)
537 {
538 if (!wt)
539 return 0;
540
541 base_td = sipTypeFromPyTypeObject((PyTypeObject *)wt);
542
543 if (!base_td)
544 return 0;
545 }
546
547 const pyqt5ClassPluginDef *cpd = reinterpret_cast<const pyqt5ClassPluginDef *>(sipTypePluginData(base_td));
548
549 return reinterpret_cast<const QMetaObject *>(cpd->static_metaobject);
550 }
551