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