// Serialise operator. QDataStream &operator<<(QDataStream &out, const PyQt_PyObject &obj) { PyObject *ser_obj = 0; const char *ser = 0; uint len = 0; if (obj.pyobject) { static PyObject *dumps = 0; SIP_BLOCK_THREADS if (!dumps) { PyObject *pickle = PyImport_ImportModule("pickle"); if (pickle) { dumps = PyObject_GetAttrString(pickle, "dumps"); Py_DECREF(pickle); } } if (dumps) { ser_obj = PyObject_CallFunctionObjArgs(dumps, obj.pyobject, 0); if (ser_obj) { if (SIPBytes_Check(ser_obj)) { ser = SIPBytes_AS_STRING(ser_obj); len = SIPBytes_GET_SIZE(ser_obj); } else { Py_DECREF(ser_obj); ser_obj = 0; } } else { PyErr_Print(); } } SIP_UNBLOCK_THREADS } out.writeBytes(ser, len); if (ser_obj) { SIP_BLOCK_THREADS Py_DECREF(ser_obj); SIP_UNBLOCK_THREADS } return out; }
// 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; #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; 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; }
// Return the current Python context. This should be called with the GIL. int qpycore_current_context(const char **file, const char **function) { static PyObject *currentframe = 0; static PyObject *getframeinfo = 0; static PyObject *saved_file = 0; static PyObject *saved_function = 0; PyObject *frame, *info, *file_obj, *linenr_obj, *function_obj; int linenr; frame = info = NULL; // Make sure we have what we need from the inspect module. if (!currentframe || !getframeinfo) { PyObject *inspect = PyImport_ImportModule("inspect"); if (inspect) { if (!currentframe) currentframe = PyObject_GetAttrString(inspect, "currentframe"); if (!getframeinfo) getframeinfo = PyObject_GetAttrString(inspect, "getframeinfo"); Py_DECREF(inspect); } if (!currentframe || !getframeinfo) goto py_error; } // Get the current frame. if ((frame = PyObject_CallFunctionObjArgs(currentframe, NULL)) == NULL) goto py_error; // Get the frame details. if ((info = PyObject_CallFunctionObjArgs(getframeinfo, frame, NULL)) == NULL) goto py_error;; if ((file_obj = PyTuple_GetItem(info, 0)) == NULL) goto py_error; if ((linenr_obj = PyTuple_GetItem(info, 1)) == NULL) goto py_error; if ((function_obj = PyTuple_GetItem(info, 2)) == NULL) goto py_error; Py_XDECREF(saved_file); #if PY_MAJOR_VERSION >= 3 saved_file = PyUnicode_AsEncodedString(file_obj, "latin_1", "ignore"); #else saved_file = file_obj; Py_INCREF(saved_file); #endif *file = SIPBytes_AS_STRING(saved_file); linenr = SIPLong_AsLong(linenr_obj); Py_XDECREF(saved_function); #if PY_MAJOR_VERSION >= 3 saved_function = PyUnicode_AsEncodedString(function_obj, "latin_1", "ignore"); #else saved_function = function_obj; Py_INCREF(saved_function); #endif *function = SIPBytes_AS_STRING(saved_function); Py_DECREF(info); Py_DECREF(frame); return linenr; py_error: Py_XDECREF(info); Py_XDECREF(frame); pyqt5_err_print(); *file = *function = ""; return 0; }
// Convert a Python object to a QVariant. bool Chimera::fromPyObject(PyObject *py, QVariant *var, bool strict) const { // Deal with the simple case of wrapping the Python object rather than // converting it. if (_type != sipType_QVariant && _metatype == PyQt_PyObject::metatype) { // If the type was specified by a Python type (as opposed to // 'PyQt_PyObject') then check the object is an instance of it. if (_py_type && !PyObject_IsInstance(py, _py_type)) return false; *var = keep_as_pyobject(py); return true; } // Let any registered convertors have a go first. for (int i = 0; i < _registered_QVariant_convertors.count(); ++i) { bool ok; if (_registered_QVariant_convertors.at(i)(py, var, &ok)) return ok; } int iserr = 0, value_class_state; void *ptr_class, *value_class = 0; // Temporary storage for different types. union { bool tmp_bool; int tmp_int; unsigned int tmp_unsigned_int; double tmp_double; void *tmp_void_ptr; long tmp_long; qlonglong tmp_qlonglong; short tmp_short; char tmp_char; unsigned long tmp_unsigned_long; qulonglong tmp_qulonglong; unsigned short tmp_unsigned_short; unsigned char tmp_unsigned_char; float tmp_float; } tmp_storage; void *variant_data = &tmp_storage; PyErr_Clear(); QVariant variant; int metatype_used = _metatype; switch (_metatype) { case QMetaType::Bool: tmp_storage.tmp_bool = PyLong_AsLong(py); break; case QMetaType::Int: if (!_inexact || isEnum() || isFlag()) { // It must fit into a C++ int. #if PY_MAJOR_VERSION >= 3 tmp_storage.tmp_int = PyLong_AsLong(py); #else tmp_storage.tmp_int = PyInt_AsLong(py); #endif } else { // Fit it into the smallest C++ type we can. qlonglong qll = PyLong_AsLongLong(py); if (PyErr_Occurred()) { // Try again in case the value is unsigned and will fit with // the extra bit. PyErr_Clear(); qulonglong qull = static_cast<qulonglong>(PyLong_AsUnsignedLongLong(py)); if (PyErr_Occurred()) { // It won't fit into any C++ type so pass it as a Python // object. PyErr_Clear(); *var = keep_as_pyobject(py); metatype_used = QMetaType::Void; } else { tmp_storage.tmp_qulonglong = qull; metatype_used = QMetaType::ULongLong; } } else if ((qlonglong)(int)qll == qll) { // It fits in a C++ int. tmp_storage.tmp_int = qll; } else if ((qulonglong)(unsigned int)qll == (qulonglong)qll) { // The extra bit is enough for it to fit. tmp_storage.tmp_unsigned_int = qll; metatype_used = QMetaType::UInt; } else { // This fits. tmp_storage.tmp_qlonglong = qll; metatype_used = QMetaType::LongLong; } } break; case QMetaType::UInt: tmp_storage.tmp_unsigned_int = sipLong_AsUnsignedLong(py); break; case QMetaType::Double: tmp_storage.tmp_double = PyFloat_AsDouble(py); break; case QMetaType::VoidStar: tmp_storage.tmp_void_ptr = sipConvertToVoidPtr(py); break; case QMetaType::Long: tmp_storage.tmp_long = PyLong_AsLong(py); break; case QMetaType::LongLong: tmp_storage.tmp_qlonglong = PyLong_AsLongLong(py); break; case QMetaType::Short: tmp_storage.tmp_short = PyLong_AsLong(py); break; case QMetaType::Char: if (SIPBytes_Check(py) && SIPBytes_GET_SIZE(py) == 1) tmp_storage.tmp_char = *SIPBytes_AS_STRING(py); else iserr = 1; break; case QMetaType::ULong: tmp_storage.tmp_unsigned_long = sipLong_AsUnsignedLong(py); break; case QMetaType::ULongLong: tmp_storage.tmp_qulonglong = static_cast<qulonglong>(PyLong_AsUnsignedLongLong(py)); break; case QMetaType::UShort: tmp_storage.tmp_unsigned_short = sipLong_AsUnsignedLong(py); break; case QMetaType::UChar: if (SIPBytes_Check(py) && SIPBytes_GET_SIZE(py) == 1) tmp_storage.tmp_unsigned_char = *SIPBytes_AS_STRING(py); else iserr = 1; break; case QMetaType::Float: tmp_storage.tmp_float = PyFloat_AsDouble(py); break; case QMetaType::QObjectStar: tmp_storage.tmp_void_ptr = sipForceConvertToType(py, sipType_QObject, 0, SIP_NO_CONVERTORS, 0, &iserr); break; case QMetaType::QWidgetStar: if (sipType_QWidget) { tmp_storage.tmp_void_ptr = sipForceConvertToType(py, sipType_QWidget, 0, SIP_NO_CONVERTORS, 0, &iserr); } else { iserr = 1; } break; case QMetaType::QVariantList: { QVariantList ql; if (to_QVariantList(py, ql)) { *var = QVariant(ql); metatype_used = QMetaType::Void; } else { iserr = 1; } break; } case QMetaType::QVariantMap: { QVariantMap qm; if (to_QVariantMap(py, qm)) { *var = QVariant(qm); metatype_used = QMetaType::Void; } else if (strict) { iserr = 1; } else { // Assume the failure is because the key was the wrong type. PyErr_Clear(); *var = keep_as_pyobject(py); metatype_used = QMetaType::Void; } break; } #if QT_VERSION >= 0x040500 case QMetaType::QVariantHash: { QVariantHash qh; if (to_QVariantHash(py, qh)) { *var = QVariant(qh); metatype_used = QMetaType::Void; } else { iserr = 1; } break; } #endif case -1: metatype_used = QMetaType::VoidStar; if (SIPBytes_Check(py)) tmp_storage.tmp_void_ptr = SIPBytes_AS_STRING(py); else if (py == Py_None) tmp_storage.tmp_void_ptr = 0; else iserr = 1; break; default: if (_type) { if (_name.endsWith('*')) { ptr_class = sipForceConvertToType(py, _type, 0, SIP_NO_CONVERTORS, 0, &iserr); variant_data = &ptr_class; } else { value_class = sipForceConvertToType(py, _type, 0, SIP_NOT_NONE, &value_class_state, &iserr); variant_data = value_class; } } else { // This is a class we don't recognise. iserr = 1; } } if (iserr || PyErr_Occurred()) { PyErr_Format(PyExc_TypeError, "unable to convert a Python '%s' object to a C++ '%s' instance", Py_TYPE(py)->tp_name, _name.constData()); iserr = 1; } else if (_type == sipType_QVariant) { *var = QVariant(*reinterpret_cast<QVariant *>(variant_data)); } else if (metatype_used != QMetaType::Void) { *var = QVariant(metatype_used, variant_data); } // Release any temporary value-class instance now that QVariant will have // made a copy. if (value_class) sipReleaseType(value_class, _type, value_class_state); return (iserr == 0); }
// Convert a Python object to C++ at a given address. This has a lot in common // with the method that converts to a QVariant. However, unlike that method, // we have no control over the size of the destination storage and so must // convert exactly as requested. bool Chimera::fromPyObject(PyObject *py, void *cpp) const { // Let any registered convertors have a go first. for (int i = 0; i < _registered_QVariant_data_convertors.count(); ++i) { bool ok; if (_registered_QVariant_data_convertors.at(i)(py, cpp, _metatype, &ok)) return ok; } int iserr = 0; PyErr_Clear(); switch (_metatype) { case QMetaType::Bool: *reinterpret_cast<bool *>(cpp) = PyLong_AsLong(py); break; case QMetaType::Int: // Truncate it if necessary to fit into a C++ int. This will // automatically handle enums and flag types as Python knows how to // convert them to ints. #if PY_MAJOR_VERSION >= 3 *reinterpret_cast<int *>(cpp) = PyLong_AsLong(py); #else *reinterpret_cast<int *>(cpp) = PyInt_AsLong(py); #endif break; case QMetaType::UInt: *reinterpret_cast<unsigned int *>(cpp) = sipLong_AsUnsignedLong(py); break; case QMetaType::Double: *reinterpret_cast<double *>(cpp) = PyFloat_AsDouble(py); break; case QMetaType::VoidStar: *reinterpret_cast<void **>(cpp) = sipConvertToVoidPtr(py); break; case QMetaType::Long: *reinterpret_cast<long *>(cpp) = PyLong_AsLong(py); break; case QMetaType::LongLong: *reinterpret_cast<qlonglong *>(cpp) = PyLong_AsLongLong(py); break; case QMetaType::Short: *reinterpret_cast<short *>(cpp) = PyLong_AsLong(py); break; case QMetaType::Char: if (SIPBytes_Check(py) && SIPBytes_GET_SIZE(py) == 1) *reinterpret_cast<char *>(cpp) = *SIPBytes_AS_STRING(py); else iserr = 1; break; case QMetaType::ULong: *reinterpret_cast<unsigned long *>(cpp) = sipLong_AsUnsignedLong(py); break; case QMetaType::ULongLong: *reinterpret_cast<qulonglong *>(cpp) = static_cast<qulonglong>(PyLong_AsUnsignedLongLong(py)); break; case QMetaType::UShort: *reinterpret_cast<unsigned short *>(cpp) = sipLong_AsUnsignedLong(py); break; case QMetaType::UChar: if (SIPBytes_Check(py) && SIPBytes_GET_SIZE(py) == 1) *reinterpret_cast<unsigned char *>(cpp) = *SIPBytes_AS_STRING(py); else iserr = 1; break; case QMetaType::Float: *reinterpret_cast<float *>(cpp) = PyFloat_AsDouble(py); break; case QMetaType::QObjectStar: *reinterpret_cast<void **>(cpp) = sipForceConvertToType(py, sipType_QObject, 0, SIP_NO_CONVERTORS, 0, &iserr); break; case QMetaType::QWidgetStar: if (sipType_QWidget) { *reinterpret_cast<void **>(cpp) = sipForceConvertToType(py, sipType_QWidget, 0, SIP_NO_CONVERTORS, 0, &iserr); } else { iserr = 1; } break; case QMetaType::QVariantList: { QVariantList ql; if (to_QVariantList(py, ql)) *reinterpret_cast<QVariantList *>(cpp) = ql; else iserr = 1; break; } case QMetaType::QVariantMap: { QVariantMap qm; if (to_QVariantMap(py, qm)) *reinterpret_cast<QVariantMap *>(cpp) = qm; else iserr = 1; break; } #if QT_VERSION >= 0x040500 case QMetaType::QVariantHash: { QVariantHash qh; if (to_QVariantHash(py, qh)) *reinterpret_cast<QVariantHash *>(cpp) = qh; else iserr = 1; break; } #endif case -1: { char **ptr = reinterpret_cast<char **>(cpp); if (SIPBytes_Check(py)) *ptr = SIPBytes_AS_STRING(py); else if (py == Py_None) *ptr = 0; else iserr = 1; break; } default: if (_type) { if (_name.endsWith('*')) { // This must be a pointer-type. *reinterpret_cast<void **>(cpp) = sipForceConvertToType(py, _type, 0, SIP_NO_CONVERTORS, 0, &iserr); } else { // This must be a value-type. sipAssignFunc assign = get_assign_helper(); if (assign) { int state; void *value_class; value_class = sipForceConvertToType(py, _type, 0, SIP_NOT_NONE, &state, &iserr); if (!iserr) assign(cpp, 0, value_class); sipReleaseType(value_class, _type, state); } else { iserr = 1; } } } else { iserr = 1; } } if (iserr || PyErr_Occurred()) { PyErr_Format(PyExc_TypeError, "unable to convert a Python '%s' object to a C++ '%s' instance", Py_TYPE(py)->tp_name, _name.constData()); return false; } return true; }