// Return the QMetaObject for an enum type's scope. static const QMetaObject *get_scope_qmetaobject(const Chimera *ct) { // Check it is an enum. if (!ct->isEnum()) return 0; // Check it has a scope. const sipTypeDef *td = sipTypeScope(ct->typeDef()); if (!td) return 0; // Check the scope is wrapped by PyQt. if (!qpycore_is_pyqt4_class(td)) return 0; return get_qmetaobject((pyqtWrapperType *)sipTypeAsPyTypeObject(td)); }
// Create a dynamic meta-object for a Python type by introspecting its // attributes. Note that it leaks if the type is deleted. static int create_dynamic_metaobject(pyqtWrapperType *pyqt_wt) { PyTypeObject *pytype = (PyTypeObject *)pyqt_wt; qpycore_metaobject *qo = new qpycore_metaobject; #if QT_VERSION >= 0x050000 QMetaObjectBuilder builder; #endif // Get any class info. QList<ClassInfo> class_info_list = qpycore_get_class_info_list(); // Get the super-type's meta-object. #if QT_VERSION >= 0x050000 builder.setSuperClass(get_qmetaobject((pyqtWrapperType *)pytype->tp_base)); #else qo->mo.d.superdata = get_qmetaobject((pyqtWrapperType *)pytype->tp_base); #endif // Get the name of the type. Dynamic types have simple names. #if QT_VERSION >= 0x050000 builder.setClassName(pytype->tp_name); #else qo->str_data = pytype->tp_name; qo->str_data.append('\0'); #endif // Go through the class dictionary getting all PyQt properties, slots, // signals or a (deprecated) sequence of signals. typedef QPair<PyObject *, PyObject *> prop_data; QMap<uint, prop_data> pprops; QList<QByteArray> psigs; SIP_SSIZE_T pos = 0; PyObject *key, *value; #if QT_VERSION < 0x050000 bool has_notify_signal = false; QList<const QMetaObject *> enum_scopes; #endif while (PyDict_Next(pytype->tp_dict, &pos, &key, &value)) { // See if it is a slot, ie. it has been decorated with pyqtSlot(). PyObject *sig_obj = PyObject_GetAttr(value, qpycore_signature_attr_name); if (sig_obj) { // Make sure it is a list and not some legitimate attribute that // happens to use our special name. if (PyList_Check(sig_obj)) { for (SIP_SSIZE_T i = 0; i < PyList_GET_SIZE(sig_obj); ++i) { qpycore_slot slot; // Set up the skeleton slot. PyObject *decoration = PyList_GET_ITEM(sig_obj, i); slot.signature = Chimera::Signature::fromPyObject(decoration); slot.sip_slot.pyobj = 0; slot.sip_slot.name = 0; slot.sip_slot.meth.mfunc = value; slot.sip_slot.meth.mself = 0; #if PY_MAJOR_VERSION < 3 slot.sip_slot.meth.mclass = (PyObject *)pyqt_wt; #endif slot.sip_slot.weakSlot = 0; qo->pslots.append(slot); } } Py_DECREF(sig_obj); } else { PyErr_Clear(); // Make sure the key is an ASCII string. Delay the error checking // until we know we actually need it. const char *ascii_key = sipString_AsASCIIString(&key); // See if the value is of interest. if (PyType_IsSubtype(Py_TYPE(value), &qpycore_pyqtProperty_Type)) { // It is a property. if (!ascii_key) return -1; Py_INCREF(value); qpycore_pyqtProperty *pp = (qpycore_pyqtProperty *)value; pprops.insert(pp->pyqtprop_sequence, prop_data(key, value)); // See if the property has a scope. If so, collect all // QMetaObject pointers that are not in the super-class // hierarchy. const QMetaObject *mo = get_scope_qmetaobject(pp->pyqtprop_parsed_type); #if QT_VERSION >= 0x050000 if (mo) builder.addRelatedMetaObject(mo); #else if (mo && !enum_scopes.contains(mo)) enum_scopes.append(mo); #endif #if QT_VERSION < 0x050000 // See if the property has a notify signal so that we can // allocate space for it in the meta-object. We will check if // it is valid later on. If there is one property with a // notify signal then a signal index is stored for all // properties. if (pp->pyqtprop_notify) has_notify_signal = true; #endif } else if (PyType_IsSubtype(Py_TYPE(value), &qpycore_pyqtSignal_Type)) { // It is a signal. if (!ascii_key) return -1; qpycore_pyqtSignal *ps = (qpycore_pyqtSignal *)value; // Make sure the signal has a name. qpycore_set_signal_name(ps, pytype->tp_name, ascii_key); // Add all the overloads. do { psigs.append(ps->signature->signature); ps = ps->next; } while (ps); Py_DECREF(key); } else if (ascii_key && qstrcmp(ascii_key, "__pyqtSignals__") == 0) { // It is a sequence of signal signatures. This is deprecated // in favour of pyqtSignal(). if (PySequence_Check(value)) { SIP_SSIZE_T seq_sz = PySequence_Size(value); for (SIP_SSIZE_T i = 0; i < seq_sz; ++i) { PyObject *itm = PySequence_ITEM(value, i); if (!itm) { Py_DECREF(key); return -1; } PyObject *ascii_itm = itm; const char *ascii = sipString_AsASCIIString(&ascii_itm); Py_DECREF(itm); if (!ascii) { Py_DECREF(key); return -1; } QByteArray old_sig = QMetaObject::normalizedSignature(ascii); old_sig.prepend('2'); psigs.append(old_sig); Py_DECREF(ascii_itm); } } Py_DECREF(key); } else { PyErr_Clear(); } } } qo->nr_signals = psigs.count(); #if QT_VERSION < 0x050000 // Create and fill the extradata array. if (enum_scopes.isEmpty()) { qo->mo.d.extradata = 0; } else { const QMetaObject **objects = new const QMetaObject *[enum_scopes.size() + 1]; for (int i = 0; i < enum_scopes.size(); ++i) objects[i] = enum_scopes.at(i); objects[enum_scopes.size()] = 0; #if QT_VERSION >= 0x040600 qo->ed.objects = objects; qo->ed.static_metacall = 0; qo->mo.d.extradata = &qo->ed; #else qo->mo.d.extradata = objects; #endif } #endif // Initialise the header section of the data table. Note that Qt v4.5 // introduced revision 2 which added constructors. However the design is // broken in that the static meta-call function doesn't provide enough // information to determine which Python sub-class of a Qt class is to be // created. So we stick with revision 1 (and don't allow pyqtSlot() to // decorate __init__). #if QT_VERSION < 0x050000 #if QT_VERSION >= 0x040600 const int revision = 4; const int header_size = 14; #else const int revision = 1; const int header_size = 10; #endif int data_len = header_size + class_info_list.count() * 2 + qo->nr_signals * 5 + qo->pslots.count() * 5 + pprops.count() * (has_notify_signal ? 4 : 3) + 1; uint *data = new uint[data_len]; int i_offset = header_size; int g_offset = i_offset + class_info_list.count() * 2; int s_offset = g_offset + qo->nr_signals * 5; int p_offset = s_offset + qo->pslots.count() * 5; int n_offset = p_offset + pprops.count() * 3; int empty = 0; for (int i = 0; i < data_len; ++i) data[i] = 0; // The revision number. data[0] = revision; #endif // Set up any class information. if (class_info_list.count() > 0) { #if QT_VERSION < 0x050000 data[2] = class_info_list.count(); data[3] = i_offset; #endif for (int i = 0; i < class_info_list.count(); ++i) { const ClassInfo &ci = class_info_list.at(i); #if QT_VERSION >= 0x050000 builder.addClassInfo(ci.first, ci.second); #else data[i_offset + (i * 2) + 0] = qo->str_data.size(); qo->str_data.append(ci.first.constData()); qo->str_data.append('\0'); data[i_offset + (i * 2) + 1] = qo->str_data.size(); qo->str_data.append(ci.second.constData()); qo->str_data.append('\0'); #endif } } #if QT_VERSION < 0x050000 // Set up the methods count and offset. if (qo->nr_signals || qo->pslots.count()) { data[4] = qo->nr_signals + qo->pslots.count(); data[5] = g_offset; // We might need an empty string. empty = qo->str_data.size(); qo->str_data.append('\0'); } // Set up the properties count and offset. if (pprops.count()) { data[6] = pprops.count(); data[7] = p_offset; } #if QT_VERSION >= 0x040600 data[13] = qo->nr_signals; #endif #endif // Add the signals to the meta-object. for (int g = 0; g < qo->nr_signals; ++g) { const QByteArray &norm = psigs.at(g); #if QT_VERSION >= 0x050000 builder.addSignal(norm.mid(1)); #else // Add the (non-existent) argument names. data[g_offset + (g * 5) + 1] = add_arg_names(qo, norm, empty); // Add the full signature. data[g_offset + (g * 5) + 0] = qo->str_data.size(); qo->str_data.append(norm.constData() + 1); qo->str_data.append('\0'); // Add the type, tag and flags. data[g_offset + (g * 5) + 2] = empty; data[g_offset + (g * 5) + 3] = empty; data[g_offset + (g * 5) + 4] = 0x05; #endif } // Add the slots to the meta-object. for (int s = 0; s < qo->pslots.count(); ++s) { const qpycore_slot &slot = qo->pslots.at(s); const QByteArray &sig = slot.signature->signature; #if QT_VERSION >= 0x050000 QMetaMethodBuilder slot_builder = builder.addSlot(sig); #else // Add the (non-existent) argument names. data[s_offset + (s * 5) + 1] = add_arg_names(qo, sig, empty); // Add the full signature. data[s_offset + (s * 5) + 0] = qo->str_data.size(); qo->str_data.append(sig); qo->str_data.append('\0'); #endif // Add any type. if (slot.signature->result) { #if QT_VERSION >= 0x050000 slot_builder.setReturnType(slot.signature->result->name()); #else data[s_offset + (s * 5) + 2] = qo->str_data.size(); qo->str_data.append(slot.signature->result->name()); qo->str_data.append('\0'); #endif } #if QT_VERSION < 0x050000 else { data[s_offset + (s * 5) + 2] = empty; } // Add the tag and flags. data[s_offset + (s * 5) + 3] = empty; data[s_offset + (s * 5) + 4] = 0x0a; #endif } // Add the properties to the meta-object. #if QT_VERSION < 0x050000 QList<uint> notify_signals; #endif QMapIterator<uint, prop_data> it(pprops); for (int p = 0; it.hasNext(); ++p) { it.next(); const prop_data &pprop = it.value(); const char *prop_name = SIPBytes_AS_STRING(pprop.first); qpycore_pyqtProperty *pp = (qpycore_pyqtProperty *)pprop.second; int notifier_id; #if QT_VERSION < 0x050000 uint flags = 0; #endif if (pp->pyqtprop_notify) { qpycore_pyqtSignal *ps = (qpycore_pyqtSignal *)pp->pyqtprop_notify; const QByteArray &sig = ps->signature->signature; #if QT_VERSION >= 0x050000 notifier_id = builder.indexOfSignal(sig.mid(1)); #else notifier_id = psigs.indexOf(sig); #endif if (notifier_id < 0) { PyErr_Format(PyExc_TypeError, "the notify signal '%s' was not defined in this class", sig.constData() + 1); // Note that we leak the property name. return -1; } #if QT_VERSION < 0x050000 notify_signals.append(notifier_id); flags |= 0x00400000; #endif } else { #if QT_VERSION >= 0x050000 notifier_id = -1; #else notify_signals.append(0); #endif } #if QT_VERSION >= 0x050000 // A Qt v5 revision 7 meta-object holds the QMetaType::Type of the type // or its name if it is unresolved (ie. not known to the type system). // In Qt v4 both are held. For QObject sub-classes Chimera will fall // back to the QMetaType::QObjectStar if there is no specific meta-type // for the sub-class. This means that, for Qt v4, // QMetaProperty::read() can handle the type. However, Qt v5 doesn't // know that the unresolved type is a QObject sub-class. Therefore we // have to tell it that the property is a QObject, rather than the // sub-class. This means that QMetaProperty.typeName() will always // return "QObject*". QByteArray prop_type; if (pp->pyqtprop_parsed_type->metatype() == QMetaType::QObjectStar) prop_type = "QObject*"; else prop_type = pp->pyqtprop_parsed_type->name(); QMetaPropertyBuilder prop_builder = builder.addProperty(prop_name, prop_type, notifier_id); // Reset the defaults. prop_builder.setReadable(false); prop_builder.setWritable(false); #else // Add the property name. data[p_offset + (p * 3) + 0] = qo->str_data.size(); qo->str_data.append(prop_name); qo->str_data.append('\0'); // Add the name of the property type. data[p_offset + (p * 3) + 1] = qo->str_data.size(); qo->str_data.append(pp->pyqtprop_parsed_type->name()); qo->str_data.append('\0'); // There are only 8 bits available for the type so use the special // value if more are required. uint metatype = pp->pyqtprop_parsed_type->metatype(); if (metatype > 0xff) { #if QT_VERSION >= 0x040600 // Qt assumes it is an enum. metatype = 0; flags |= 0x00000008; #else // This is the old PyQt behaviour. It may not be correct, but // nobody has complained, and if it ain't broke... metatype = 0xff; #endif } #endif // Enum or flag. if (pp->pyqtprop_parsed_type->isEnum() || pp->pyqtprop_parsed_type->isFlag()) { #if QT_VERSION >= 0x050000 prop_builder.setEnumOrFlag(true); #else #if QT_VERSION >= 0x040600 metatype = 0; #endif flags |= 0x00000008; #endif } #if QT_VERSION < 0x050000 flags |= metatype << 24; #endif if (pp->pyqtprop_get && PyCallable_Check(pp->pyqtprop_get)) // Readable. #if QT_VERSION >= 0x050000 prop_builder.setReadable(true); #else flags |= 0x00000001; #endif if (pp->pyqtprop_set && PyCallable_Check(pp->pyqtprop_set)) { // Writable. #if QT_VERSION >= 0x050000 prop_builder.setWritable(true); #else flags |= 0x00000002; #endif // See if the name of the setter follows the Designer convention. // If so tell the UI compilers not to use setProperty(). PyObject *setter_name_obj = PyObject_GetAttr(pp->pyqtprop_set, qpycore_name_attr_name); if (setter_name_obj) { PyObject *ascii_obj = setter_name_obj; const char *ascii = sipString_AsASCIIString(&ascii_obj); Py_DECREF(setter_name_obj); if (ascii) { if (qstrlen(ascii) > 3 && ascii[0] == 's' && ascii[1] == 'e' && ascii[2] == 't' && ascii[3] == toupper(prop_name[0]) && qstrcmp(&ascii[4], &prop_name[1]) == 0) #if QT_VERSION >= 0x050000 prop_builder.setStdCppSet(true); #else flags |= 0x00000100; #endif } Py_DECREF(ascii_obj); } PyErr_Clear(); } if (pp->pyqtprop_reset && PyCallable_Check(pp->pyqtprop_reset)) // Resetable. #if QT_VERSION >= 0x050000 prop_builder.setResettable(true); #else flags |= 0x00000004; #endif // Add the property flags. #if QT_VERSION >= 0x050000 // Note that Qt4 always seems to have ResolveEditable set but // QMetaObjectBuilder doesn't provide an API call to do it. prop_builder.setDesignable(pp->pyqtprop_flags & 0x00001000); prop_builder.setScriptable(pp->pyqtprop_flags & 0x00004000); prop_builder.setStored(pp->pyqtprop_flags & 0x00010000); prop_builder.setUser(pp->pyqtprop_flags & 0x00100000); prop_builder.setConstant(pp->pyqtprop_flags & 0x00000400); prop_builder.setFinal(pp->pyqtprop_flags & 0x00000800); #else flags |= pp->pyqtprop_flags; data[p_offset + (p * 3) + 2] = flags; #endif // Save the property data for qt_metacall(). (We already have a // reference.) qo->pprops.append(pp); // We've finished with the property name. Py_DECREF(pprop.first); } #if QT_VERSION < 0x050000 // Add the indices of the notify signals. if (has_notify_signal) { QListIterator<uint> notify_it(notify_signals); while (notify_it.hasNext()) data[n_offset++] = notify_it.next(); } #endif // Initialise the rest of the meta-object. #if QT_VERSION >= 0x050000 qo->mo = builder.toMetaObject(); #else qo->mo.d.stringdata = qo->str_data.constData(); qo->mo.d.data = data; #endif // Save the meta-object. pyqt_wt->metaobject = qo; return 0; }
// Create a dynamic meta-object for a Python type by introspecting its // attributes. Note that it leaks if the type is deleted. static int create_dynamic_metaobject(pyqtWrapperType *pyqt_wt) { PyTypeObject *pytype = (PyTypeObject *)pyqt_wt; qpycore_metaobject *qo = new qpycore_metaobject; // Get the super-type's meta-object. qo->mo.d.superdata = get_qmetaobject((pyqtWrapperType *)pytype->tp_base); // Get the name of the type. Dynamic types have simple names. qo->str_data = pytype->tp_name; qo->str_data.append('\0'); // Go through the class dictionary getting all PyQt properties, slots, // signals or a (deprecated) sequence of signals. typedef QPair<PyObject *, PyObject *> prop_data; QMap<uint, prop_data> pprops; QList<QByteArray> psigs; QList<const QMetaObject *> enum_scopes; SIP_SSIZE_T pos = 0; PyObject *key, *value; while (PyDict_Next(pytype->tp_dict, &pos, &key, &value)) { // See if it is a slot, ie. it has been decorated with pyqtSlot(). PyObject *sig_obj = PyObject_GetAttr(value, qpycore_signature_attr_name); if (sig_obj) { // Make sure it is a list and not some legitimate attribute that // happens to use our special name. if (PyList_Check(sig_obj)) { for (SIP_SSIZE_T i = 0; i < PyList_GET_SIZE(sig_obj); ++i) { qpycore_slot slot; // Set up the skeleton slot. PyObject *decoration = PyList_GET_ITEM(sig_obj, i); slot.signature = Chimera::Signature::fromPyObject(decoration); slot.sip_slot.pyobj = 0; slot.sip_slot.name = 0; slot.sip_slot.meth.mfunc = value; slot.sip_slot.meth.mself = 0; #if PY_MAJOR_VERSION < 3 slot.sip_slot.meth.mclass = (PyObject *)pyqt_wt; #endif slot.sip_slot.weakSlot = 0; qo->pslots.append(slot); } } Py_DECREF(sig_obj); } else { PyErr_Clear(); // Make sure the key is an ASCII string. Delay the error checking // until we know we actually need it. const char *ascii_key = sipString_AsASCIIString(&key); // See if the value is of interest. if (PyType_IsSubtype(Py_TYPE(value), &qpycore_pyqtProperty_Type)) { // It is a property. if (!ascii_key) return -1; Py_INCREF(value); qpycore_pyqtProperty *pp = (qpycore_pyqtProperty *)value; pprops.insert(pp->pyqtprop_sequence, prop_data(key, value)); // See if the property has a scope. If so, collect all // QMetaObject pointers that are not in the super-class // hierarchy. const QMetaObject *mo = get_scope_qmetaobject(pp->pyqtprop_parsed_type); if (mo && !enum_scopes.contains(mo)) enum_scopes.append(mo); } else if (PyType_IsSubtype(Py_TYPE(value), &qpycore_pyqtSignal_Type)) { // It is a signal. if (!ascii_key) return -1; qpycore_pyqtSignal *ps = (qpycore_pyqtSignal *)value; // Make sure the signal has a name. qpycore_set_signal_name(ps, ascii_key); // Add all the overloads. for (int ol = 0; ol < ps->overloads->size(); ++ol) psigs.append(ps->overloads->at(ol)->signature); Py_DECREF(key); } else if (ascii_key && qstrcmp(ascii_key, "__pyqtSignals__") == 0) { // It is a sequence of signal signatures. This is deprecated // in favour of pyqtSignal(). if (PySequence_Check(value)) { SIP_SSIZE_T seq_sz = PySequence_Size(value); for (SIP_SSIZE_T i = 0; i < seq_sz; ++i) { PyObject *itm = PySequence_ITEM(value, i); if (!itm) { Py_DECREF(key); return -1; } PyObject *ascii_itm = itm; const char *ascii = sipString_AsASCIIString(&ascii_itm); Py_DECREF(itm); if (!ascii) { Py_DECREF(key); return -1; } QByteArray old_sig = QMetaObject::normalizedSignature(ascii); old_sig.prepend('2'); psigs.append(old_sig); Py_DECREF(ascii_itm); } } Py_DECREF(key); } } } qo->nr_signals = psigs.count(); // Create and fill the extradata array. if (enum_scopes.isEmpty()) { qo->mo.d.extradata = 0; } else { const QMetaObject **objects = new const QMetaObject *[enum_scopes.size() + 1]; for (int i = 0; i < enum_scopes.size(); ++i) objects[i] = enum_scopes.at(i); objects[enum_scopes.size()] = 0; qo->mo.d.extradata = objects; } // Initialise the header section of the data table. Qt v4.5 introduced // revision 2 which added constructors. However the design is broken in // that the static meta-call function doesn't provide enough information to // determine which Python sub-class of a Qt class is to be created. So we // stick with revision 1 (and don't allow pyqtSlot() to decorate __init__). const int revision = 1; const int header_size = 10; int data_len = header_size + qo->nr_signals * 5 + qo->pslots.count() * 5 + pprops.count() * 3 + 1; uint *data = new uint[data_len]; int g_offset = header_size; int s_offset = g_offset + qo->nr_signals * 5; int p_offset = s_offset + qo->pslots.count() * 5; int empty = 0; for (int i = 0; i < data_len; ++i) data[i] = 0; // The revision number. data[0] = revision; // Set up the methods count and offset. if (qo->nr_signals || qo->pslots.count()) { data[4] = qo->nr_signals + qo->pslots.count(); data[5] = g_offset; // We might need an empty string. empty = qo->str_data.size(); qo->str_data.append('\0'); } // Set up the properties count and offset. if (pprops.count()) { data[6] = pprops.count(); data[7] = p_offset; } // Add the signals to the meta-object. for (int g = 0; g < qo->nr_signals; ++g) { const QByteArray &norm = psigs.at(g); // Add the (non-existent) argument names. data[g_offset + (g * 5) + 1] = add_arg_names(qo, norm, empty); // Add the full signature. data[g_offset + (g * 5) + 0] = qo->str_data.size(); qo->str_data.append(norm.constData() + 1); qo->str_data.append('\0'); // Add the type, tag and flags. data[g_offset + (g * 5) + 2] = empty; data[g_offset + (g * 5) + 3] = empty; data[g_offset + (g * 5) + 4] = 0x05; } // Add the slots to the meta-object. for (int s = 0; s < qo->pslots.count(); ++s) { const qpycore_slot &slot = qo->pslots.at(s); const QByteArray &sig = slot.signature->signature; // Add the (non-existent) argument names. data[s_offset + (s * 5) + 1] = add_arg_names(qo, sig, empty); // Add the full signature. data[s_offset + (s * 5) + 0] = qo->str_data.size(); qo->str_data.append(sig); qo->str_data.append('\0'); // Add any type. if (slot.signature->result) { data[s_offset + (s * 5) + 2] = qo->str_data.size(); qo->str_data.append(slot.signature->result->name()); qo->str_data.append('\0'); } else { data[s_offset + (s * 5) + 2] = empty; } // Add the tag and flags. data[s_offset + (s * 5) + 3] = empty; data[s_offset + (s * 5) + 4] = 0x0a; } // Add the properties to the meta-object. QMapIterator<uint, prop_data> it(pprops); for (int p = 0; it.hasNext(); ++p) { it.next(); const prop_data &pprop = it.value(); const char *prop_name = SIPBytes_AS_STRING(pprop.first); qpycore_pyqtProperty *pp = (qpycore_pyqtProperty *)pprop.second; // Add the property name. data[p_offset + (p * 3) + 0] = qo->str_data.size(); qo->str_data.append(prop_name); qo->str_data.append('\0'); // Add the name of the property type. data[p_offset + (p * 3) + 1] = qo->str_data.size(); qo->str_data.append(pp->pyqtprop_parsed_type->name()); qo->str_data.append('\0'); // Add the property flags. uint flags = pp->pyqtprop_flags; // Enum or flag. if (pp->pyqtprop_parsed_type->isEnum() || pp->pyqtprop_parsed_type->isFlag()) flags |= 0x00000008; if (pp->prop_get && PyCallable_Check(pp->prop_get)) // Readable. flags |= 0x00000001; if (pp->prop_set && PyCallable_Check(pp->prop_set)) { // Writable. flags |= 0x00000002; // See if the name of the setter follows the Designer convention. // If so tell the UI compilers not to use setProperty(). PyObject *setter_name_obj = PyObject_GetAttr(pp->prop_set, qpycore_name_attr_name); if (setter_name_obj) { PyObject *ascii_obj = setter_name_obj; const char *ascii = sipString_AsASCIIString(&ascii_obj); Py_DECREF(setter_name_obj); if (ascii) { if (qstrlen(ascii) > 3 && ascii[0] == 's' && ascii[1] == 'e' && ascii[2] == 't' && ascii[3] == toupper(prop_name[0]) && qstrcmp(&ascii[4], &prop_name[1]) == 0) flags |= 0x00000100; } Py_DECREF(ascii_obj); } PyErr_Clear(); } if (pp->pyqtprop_reset && PyCallable_Check(pp->pyqtprop_reset)) // Resetable. flags |= 0x00000004; // There are only 8 bits available for the type so use the special // value if more are required. uint metatype = pp->pyqtprop_parsed_type->metatype(); if (metatype > 0xff) metatype = 0xff; flags |= metatype << 24; data[p_offset + (p * 3) + 2] = flags; // Save the property data for qt_metacall(). (We already have a // reference.) qo->pprops.append(pp); // We've finished with the property name. Py_DECREF(pprop.first); } // Initialise the rest of the meta-object. qo->mo.d.stringdata = qo->str_data.constData(); qo->mo.d.data = data; // Save the meta-object. pyqt_wt->metaobject = qo; return 0; }
// Create a dynamic meta-object for a Python type by introspecting its // attributes. Note that it leaks if the type is deleted. static int create_dynamic_metaobject(pyqtWrapperType *pyqt_wt) { PyTypeObject *pytype = (PyTypeObject *)pyqt_wt; qpycore_metaobject *qo = new qpycore_metaobject; QMetaObjectBuilder builder; // Get any class info. QList<ClassInfo> class_info_list = qpycore_get_class_info_list(); // Get any enums/flags. QList<EnumsFlags> enums_flags_list = qpycore_get_enums_flags_list(); // Get the super-type's meta-object. builder.setSuperClass(get_qmetaobject((pyqtWrapperType *)pytype->tp_base)); // Get the name of the type. Dynamic types have simple names. builder.setClassName(pytype->tp_name); // Go through the class hierarchy getting all PyQt properties, slots and // signals. QList<const qpycore_pyqtSignal *> psigs; QMap<uint, PropertyData> pprops; if (trawl_hierarchy(pytype, qo, builder, psigs, pprops) < 0) return -1; qo->nr_signals = psigs.count(); // Initialise the header section of the data table. Note that Qt v4.5 // introduced revision 2 which added constructors. However the design is // broken in that the static meta-call function doesn't provide enough // information to determine which Python sub-class of a Qt class is to be // created. So we stick with revision 1 (and don't allow pyqtSlot() to // decorate __init__). // Set up any class information. for (int i = 0; i < class_info_list.count(); ++i) { const ClassInfo &ci = class_info_list.at(i); builder.addClassInfo(ci.first, ci.second); } // Set up any enums/flags. for (int i = 0; i < enums_flags_list.count(); ++i) { const EnumsFlags &ef = enums_flags_list.at(i); QByteArray scoped_name(pytype->tp_name); scoped_name.append("::"); scoped_name.append(ef.name); QMetaEnumBuilder enum_builder = builder.addEnumerator(scoped_name); enum_builder.setIsFlag(ef.isFlag); QHash<QByteArray, int>::const_iterator it = ef.keys.constBegin(); while (it != ef.keys.constEnd()) { enum_builder.addKey(it.key(), it.value()); ++it; } } // Add the signals to the meta-object. for (int g = 0; g < qo->nr_signals; ++g) { const qpycore_pyqtSignal *ps = psigs.at(g); QMetaMethodBuilder signal_builder = builder.addSignal( ps->parsed_signature->signature.mid(1)); if (ps->parameter_names) signal_builder.setParameterNames(*ps->parameter_names); signal_builder.setRevision(ps->revision); } // Add the slots to the meta-object. for (int s = 0; s < qo->pslots.count(); ++s) { const Chimera::Signature *slot_signature = qo->pslots.at(s)->slotSignature(); const QByteArray &sig = slot_signature->signature; QMetaMethodBuilder slot_builder = builder.addSlot(sig); // Add any type. if (slot_signature->result) slot_builder.setReturnType(slot_signature->result->name()); slot_builder.setRevision(slot_signature->revision); } // Add the properties to the meta-object. QMapIterator<uint, PropertyData> it(pprops); for (int p = 0; it.hasNext(); ++p) { it.next(); const PropertyData &pprop = it.value(); const char *prop_name = SIPBytes_AS_STRING(pprop.first); qpycore_pyqtProperty *pp = (qpycore_pyqtProperty *)pprop.second; int notifier_id; if (pp->pyqtprop_notify) { qpycore_pyqtSignal *ps = (qpycore_pyqtSignal *)pp->pyqtprop_notify; const QByteArray &sig = ps->parsed_signature->signature; notifier_id = builder.indexOfSignal(sig.mid(1)); if (notifier_id < 0) { PyErr_Format(PyExc_TypeError, "the notify signal '%s' was not defined in this class", sig.constData() + 1); // Note that we leak the property name. return -1; } } else { notifier_id = -1; } // A Qt v5 revision 7 meta-object holds the QMetaType::Type of the type // or its name if it is unresolved (ie. not known to the type system). // In Qt v4 both are held. For QObject sub-classes Chimera will fall // back to the QMetaType::QObjectStar if there is no specific meta-type // for the sub-class. This means that, for Qt v4, // QMetaProperty::read() can handle the type. However, Qt v5 doesn't // know that the unresolved type is a QObject sub-class. Therefore we // have to tell it that the property is a QObject, rather than the // sub-class. This means that QMetaProperty.typeName() will always // return "QObject*". QByteArray prop_type; if (pp->pyqtprop_parsed_type->metatype() == QMetaType::QObjectStar) { // However, if the type is a Python sub-class of QObject then we // use the name of the Python type. This anticipates that the type // is one that will be proxied by QML at some point. if (pp->pyqtprop_parsed_type->typeDef() == sipType_QObject) { prop_type = ((PyTypeObject *)pp->pyqtprop_parsed_type->py_type())->tp_name; prop_type.append('*'); } else { prop_type = "QObject*"; } } else { prop_type = pp->pyqtprop_parsed_type->name(); } QMetaPropertyBuilder prop_builder = builder.addProperty(prop_name, prop_type, notifier_id); // Reset the defaults. prop_builder.setReadable(false); prop_builder.setWritable(false); // Enum or flag. if (pp->pyqtprop_parsed_type->isEnum() || pp->pyqtprop_parsed_type->isFlag()) { prop_builder.setEnumOrFlag(true); } if (pp->pyqtprop_get && PyCallable_Check(pp->pyqtprop_get)) { // Readable. prop_builder.setReadable(true); } if (pp->pyqtprop_set && PyCallable_Check(pp->pyqtprop_set)) { // Writable. prop_builder.setWritable(true); // See if the name of the setter follows the Designer convention. // If so tell the UI compilers not to use setProperty(). PyObject *setter_name_obj = PyObject_GetAttr(pp->pyqtprop_set, qpycore_dunder_name); if (setter_name_obj) { PyObject *ascii_obj = setter_name_obj; const char *ascii = sipString_AsASCIIString(&ascii_obj); Py_DECREF(setter_name_obj); if (ascii) { if (qstrlen(ascii) > 3 && ascii[0] == 's' && ascii[1] == 'e' && ascii[2] == 't' && ascii[3] == toupper(prop_name[0]) && qstrcmp(&ascii[4], &prop_name[1]) == 0) prop_builder.setStdCppSet(true); } Py_DECREF(ascii_obj); } PyErr_Clear(); } if (pp->pyqtprop_reset && PyCallable_Check(pp->pyqtprop_reset)) { // Resetable. prop_builder.setResettable(true); } // Add the property flags. Note that Qt4 always seems to have // ResolveEditable set but QMetaObjectBuilder doesn't provide an API // call to do it. prop_builder.setDesignable(pp->pyqtprop_flags & 0x00001000); prop_builder.setScriptable(pp->pyqtprop_flags & 0x00004000); prop_builder.setStored(pp->pyqtprop_flags & 0x00010000); prop_builder.setUser(pp->pyqtprop_flags & 0x00100000); prop_builder.setConstant(pp->pyqtprop_flags & 0x00000400); prop_builder.setFinal(pp->pyqtprop_flags & 0x00000800); prop_builder.setRevision(pp->pyqtprop_revision); // Save the property data for qt_metacall(). (We already have a // reference.) qo->pprops.append(pp); // We've finished with the property name. Py_DECREF(pprop.first); } // Initialise the rest of the meta-object. qo->mo = builder.toMetaObject(); // Save the meta-object. pyqt_wt->metaobject = qo; return 0; }