diff options
Diffstat (limited to 'sources/pyside6/libpysideqml/pysideqmlregistertype.cpp')
-rw-r--r-- | sources/pyside6/libpysideqml/pysideqmlregistertype.cpp | 707 |
1 files changed, 489 insertions, 218 deletions
diff --git a/sources/pyside6/libpysideqml/pysideqmlregistertype.cpp b/sources/pyside6/libpysideqml/pysideqmlregistertype.cpp index 26398ae76..4ccd459d5 100644 --- a/sources/pyside6/libpysideqml/pysideqmlregistertype.cpp +++ b/sources/pyside6/libpysideqml/pysideqmlregistertype.cpp @@ -6,8 +6,10 @@ #include "pysideqmltypeinfo_p.h" #include "pysideqmlattached_p.h" #include "pysideqmlextended_p.h" +#include "pysideqmluncreatable.h" #include <limits> +#include <optional> // shiboken #include <shiboken.h> @@ -16,6 +18,7 @@ // pyside #include <pyside.h> #include <pysideqobject.h> +#include <pysideclassinfo.h> #include <pyside_p.h> #include <QtCore/QMutex> @@ -25,9 +28,16 @@ #include <QtQml/QJSValue> #include <QtQml/QQmlListProperty> #include <private/qqmlmetatype_p.h> +#include <private/qmetaobjectbuilder_p.h> + +#include <memory> + +using namespace Qt::StringLiterals; static PySide::Qml::QuickRegisterItemFunction quickRegisterItemFunction = nullptr; +static const auto qmlElementKey = "QML.Element"_ba; + static void createInto(void *memory, void *type) { QMutexLocker locker(&PySide::nextQObjectMemoryAddrMutex()); @@ -85,49 +95,154 @@ static inline bool isQmlParserStatus(const QMetaObject *o) return inheritsFrom(o, "QPyQmlParserStatus"); } -namespace PySide::Qml { +static QByteArray getGlobalString(const char *name) +{ + PyObject *globalVar = PyDict_GetItemString(PyEval_GetGlobals(), name); -int qmlRegisterType(PyObject *pyObj, const char *uri, int versionMajor, - int versionMinor, const char *qmlName, const char *noCreationReason, - bool creatable) + if (globalVar == nullptr || PyUnicode_Check(globalVar) == 0) + return {}; + + const char *stringValue = _PepUnicode_AsString(globalVar); + return stringValue != nullptr ? QByteArray(stringValue) : QByteArray{}; +} + +static int getGlobalInt(const char *name) { - using namespace Shiboken; + PyObject *globalVar = PyDict_GetItemString(PyEval_GetGlobals(), name); - PyTypeObject *qobjectType = qObjectType(); + if (globalVar == nullptr || PyLong_Check(globalVar) == 0) + return -1; - PyTypeObject *pyObjType = reinterpret_cast<PyTypeObject *>(pyObj); - if (!PySequence_Contains(pyObjType->tp_mro, reinterpret_cast<PyObject *>(qobjectType))) { - PyErr_Format(PyExc_TypeError, "A type inherited from %s expected, got %s.", - qobjectType->tp_name, pyObjType->tp_name); + long value = PyLong_AsLong(globalVar); + + if (value > std::numeric_limits<int>::max() || value < std::numeric_limits<int>::min()) return -1; + + return value; +} + +struct ImportData +{ + QByteArray importName; + int majorVersion = 0; + int minorVersion = 0; + + QTypeRevision toTypeRevision() const; +}; + +QTypeRevision ImportData::toTypeRevision() const +{ + return QTypeRevision::fromVersion(majorVersion, minorVersion); +} + +std::optional<ImportData> getGlobalImportData(const char *decoratorName) +{ + ImportData result{getGlobalString("QML_IMPORT_NAME"), + getGlobalInt("QML_IMPORT_MAJOR_VERSION"), + getGlobalInt("QML_IMPORT_MINOR_VERSION")}; + + if (result.importName.isEmpty()) { + PyErr_Format(PyExc_TypeError, "You need specify QML_IMPORT_NAME in order to use %s.", + decoratorName); + return {}; } - const QMetaObject *metaObject = PySide::retrieveMetaObject(pyObjType); - Q_ASSERT(metaObject); + if (result.majorVersion == -1) { + PyErr_Format(PyExc_TypeError, "You need specify QML_IMPORT_MAJOR_VERSION in order to use %s.", + decoratorName); + return {}; + } - QQmlPrivate::RegisterType type; + // Specifying a minor version is optional + if (result.minorVersion == -1) + result.minorVersion = 0; + return result; +} - // Allow registering Qt Quick items. - const bool isQuickType = quickRegisterItemFunction && quickRegisterItemFunction(pyObj, &type); +static PyTypeObject *checkTypeObject(PyObject *pyObj, const char *what) +{ + if (PyType_Check(pyObj) == 0) { + PyErr_Format(PyExc_TypeError, "%s can only be used for classes.", what); + return nullptr; + } + return reinterpret_cast<PyTypeObject *>(pyObj); +} + +static bool setClassInfo(PyTypeObject *type, const QByteArray &key, const QByteArray &value) +{ + if (!PySide::ClassInfo::setClassInfo(type, key, value)) { + PyErr_Format(PyExc_TypeError, "Setting class info \"%s\" to \"%s\" on \"%s\" failed.", + key.constData(), value.constData(), type->tp_name); + return false; + } + return true; +} + +static inline bool setSingletonClassInfo(PyTypeObject *type) +{ + return setClassInfo(type, "QML.Singleton"_ba, "true"_ba); +} + +static QQmlCustomParser *defaultCustomParserFactory() +{ + return nullptr; +} + +namespace PySide::Qml { + +// Modern (6.7) type registration using RegisterTypeAndRevisions +// and information set to QMetaClassInfo. +static int qmlRegisterType(PyObject *pyObj, + const ImportData &importData, + const QMetaObject *metaObject, + const QMetaObject *classInfoMetaObject = nullptr) +{ + PyTypeObject *pyObjType = reinterpret_cast<PyTypeObject *>(pyObj); + + if (classInfoMetaObject == nullptr) + classInfoMetaObject = metaObject; // Register as simple QObject rather than Qt Quick item. // Incref the type object, don't worry about decref'ing it because // there's no way to unregister a QML type. Py_INCREF(pyObj); - type.structVersion = 0; - const QByteArray typeName(pyObjType->tp_name); QByteArray ptrType = typeName + '*'; QByteArray listType = QByteArrayLiteral("QQmlListProperty<") + typeName + '>'; + const auto typeId = QMetaType(new QQmlMetaTypeInterface(ptrType)); + const auto listId = QMetaType(new QQmlListMetaTypeInterface(listType, typeId.iface())); + const int objectSize = static_cast<int>(PySide::getSizeOfQObject(reinterpret_cast<PyTypeObject *>(pyObj))); - type.typeId = QMetaType(new QQmlMetaTypeInterface(ptrType)); - type.listId = QMetaType(new QQmlListMetaTypeInterface(listType, - type.typeId.iface())); const auto typeInfo = qmlTypeInfo(pyObj); - auto info = qmlAttachedInfo(pyObjType, typeInfo); - type.attachedPropertiesFunction = info.factory; - type.attachedPropertiesMetaObject = info.metaObject; + const auto attachedInfo = qmlAttachedInfo(pyObjType, typeInfo); + const auto extendedInfo = qmlExtendedInfo(pyObj, typeInfo); + + QList<int> ids; + QQmlPrivate::RegisterTypeAndRevisions type { + QQmlPrivate::RegisterType::StructVersion::Base, // structVersion + typeId, listId, objectSize, + createInto, // create + pyObj, // userdata + nullptr, // createValueType (Remove in Qt 7) + importData.importName.constData(), + importData.toTypeRevision(), // version + metaObject, + classInfoMetaObject, + attachedInfo.factory, // attachedPropertiesFunction + attachedInfo.metaObject, // attachedPropertiesMetaObject + 0, 0, 0, // parserStatusCast, valueSourceCast, valueInterceptorCast + extendedInfo.factory, // extensionObjectCreate + extendedInfo.metaObject, // extensionMetaObject + defaultCustomParserFactory, // customParser + &ids, // qmlTypeIds + 0, // finalizerCast + false, // forceAnonymous + {} // listMetaSequence + }; + + // Allow registering Qt Quick items. + const bool isQuickType = quickRegisterItemFunction && quickRegisterItemFunction(pyObj, &type); if (!isQuickType) { // values filled by the Quick registration // QPyQmlParserStatus inherits QObject, QQmlParserStatus, so, @@ -143,55 +258,245 @@ int qmlRegisterType(PyObject *pyObj, const char *uri, int versionMajor, QQmlPrivate::StaticCastSelector<QObject, QQmlPropertyValueInterceptor>::cast(); } - int objectSize = static_cast<int>(PySide::getSizeOfQObject( - reinterpret_cast<PyTypeObject *>(pyObj))); - type.objectSize = objectSize; - type.create = creatable ? createInto : nullptr; - type.noCreationReason = QString::fromUtf8(noCreationReason); - type.userdata = pyObj; - type.uri = uri; - type.version = QTypeRevision::fromVersion(versionMajor, versionMinor); - type.elementName = qmlName; - - info = qmlExtendedInfo(pyObj, typeInfo); - type.extensionObjectCreate = info.factory; - type.extensionMetaObject = info.metaObject; - type.customParser = 0; - type.metaObject = metaObject; // Snapshot may have changed. - - int qmlTypeId = QQmlPrivate::qmlregister(QQmlPrivate::TypeRegistration, &type); + QQmlPrivate::qmlregister(QQmlPrivate::TypeAndRevisionsRegistration, &type); + const int qmlTypeId = ids.value(0, -1); if (qmlTypeId == -1) { PyErr_Format(PyExc_TypeError, "QML meta type registration of \"%s\" failed.", - qmlName); + typeName.constData()); } return qmlTypeId; } -int qmlRegisterSingletonType(PyObject *pyObj, const char *uri, int versionMajor, - int versionMinor, const char *qmlName, PyObject *callback, - bool isQObject, bool hasCallback) +static int qmlRegisterType(PyObject *pyObj, PyObject *pyClassInfoObj, + const ImportData &importData) { - using namespace Shiboken; + PyTypeObject *pyObjType = reinterpret_cast<PyTypeObject *>(pyObj); + if (!isQObjectDerived(pyObjType, true)) + return -1; - if (hasCallback) { - if (!PyCallable_Check(callback)) { - PyErr_Format(PyExc_TypeError, "Invalid callback specified."); - return -1; - } + const QMetaObject *metaObject = PySide::retrieveMetaObject(pyObjType); + Q_ASSERT(metaObject); + const QMetaObject *classInfoMetaObject = pyObj == pyClassInfoObj + ? metaObject : PySide::retrieveMetaObject(pyClassInfoObj); + return qmlRegisterType(pyObj, importData, metaObject, classInfoMetaObject); +} - AutoDecRef funcCode(PyObject_GetAttrString(callback, "__code__")); - AutoDecRef argCount(PyObject_GetAttrString(funcCode, "co_argcount")); +// Legacy (pre 6.7) compatibility helper for the free register functions. +int qmlRegisterType(PyObject *pyObj, const char *uri, int versionMajor, int versionMinor, + const char *qmlName, const char *noCreationReason, + bool creatable) +{ + auto *type = checkTypeObject(pyObj, "qmlRegisterType()"); + if (type == nullptr || !PySide::isQObjectDerived(type, true)) + return -1; - int count = PyLong_AsLong(argCount); + const QMetaObject *metaObject = PySide::retrieveMetaObject(type); + Q_ASSERT(metaObject); - if (count != 1) { - PyErr_Format(PyExc_TypeError, "Callback has a bad parameter count."); - return -1; + // PYSIDE-2709: Use a separate QMetaObject for the class information + // as modifying metaObject breaks inheritance. + QMetaObjectBuilder classInfobuilder(&QObject::staticMetaObject); + classInfobuilder.addClassInfo(qmlElementKey, qmlName); + if (!creatable) + setUncreatableClassInfo(&classInfobuilder, noCreationReason); + auto *classInfoMetaObject = classInfobuilder.toMetaObject(); + + const int qmlTypeId = qmlRegisterType(pyObj, {uri, versionMajor, versionMinor}, + metaObject, classInfoMetaObject); + free(classInfoMetaObject); + return qmlTypeId; +} + +// Singleton helpers + +// Check the arguments of a singleton callback (C++: "QJSValue cb(QQmlEngine *, QJSEngine *)", +// but we drop the QJSEngine since it will be the same as QQmlEngine when the latter exists. +static bool checkSingletonCallback(PyObject *callback) +{ + if (callback == nullptr) { + PyErr_SetString(PyExc_TypeError, "No callback specified."); + return false; + } + if (PyCallable_Check(callback) == 0) { + PyErr_Format(PyExc_TypeError, "Invalid callback specified (%S).", callback); + return false; + } + Shiboken::AutoDecRef funcCode(PyObject_GetAttrString(callback, "__code__")); + if (funcCode.isNull()) { + PyErr_Format(PyExc_TypeError, "Cannot retrieve code of callback (%S).", callback); + return false; + } + Shiboken::AutoDecRef argCountAttr(PyObject_GetAttrString(funcCode, "co_argcount")); + const int argCount = PyLong_AsLong(argCountAttr.object()); + if (argCount != 1) { + PyErr_Format(PyExc_TypeError, "Callback (%S) has %d parameter(s), expected one.", + callback, argCount); + return false; + } + + return true; +} + +// Shared data of a singleton creation callback which dereferences an object on +// destruction. +class SingletonQObjectCreationSharedData +{ +public: + Q_DISABLE_COPY_MOVE(SingletonQObjectCreationSharedData) + + SingletonQObjectCreationSharedData(PyObject *cb, PyObject *ref = nullptr) noexcept : + callable(cb), reference(ref) + { + Py_XINCREF(ref); + } + + // FIXME: Currently, the QML registration data are in global static variables + // and thus cleaned up after Python terminates. Once they are cleaned up + // by the QML engine, the code can be activated for proper cleanup of the references. + ~SingletonQObjectCreationSharedData() +#if 0 // + ~SingletonQObjectCreationSharedData() + { + if (reference != nullptr) { + Shiboken::GilState gil; + Py_DECREF(reference); } + } +#else + = default; +#endif + + PyObject *callable{}; // Callback, static method or type object to be invoked. + PyObject *reference{}; // Object to dereference when going out scope +}; + +// Base class for QML singleton creation callbacks with helper for error checking. +class SingletonQObjectCreationBase +{ +protected: + explicit SingletonQObjectCreationBase(PyObject *cb, PyObject *ref = nullptr) : + m_data(std::make_shared<SingletonQObjectCreationSharedData>(cb, ref)) + { + } + + static QObject *handleReturnValue(PyObject *retVal); - // Make sure the callback never gets deallocated - Py_INCREF(callback); + std::shared_ptr<SingletonQObjectCreationSharedData> data() const { return m_data; } + +private: + std::shared_ptr<SingletonQObjectCreationSharedData> m_data; +}; + +QObject *SingletonQObjectCreationBase::handleReturnValue(PyObject *retVal) +{ + using Shiboken::Conversions::isPythonToCppPointerConvertible; + // Make sure the callback returns something we can convert, else the entire application will crash. + if (retVal == nullptr) { + PyErr_Format(PyExc_TypeError, "Callback returns 0 value."); + return nullptr; } + if (isPythonToCppPointerConvertible(qObjectType(), retVal) == nullptr) { + PyErr_Format(PyExc_TypeError, "Callback returns invalid value (%S).", retVal); + return nullptr; + } + + QObject *obj = nullptr; + Shiboken::Conversions::pythonToCppPointer(qObjectType(), retVal, &obj); + return obj; +} + +// QML singleton creation callback by invoking a type object +class SingletonQObjectFromTypeCreation : public SingletonQObjectCreationBase +{ +public: + explicit SingletonQObjectFromTypeCreation(PyObject *typeObj) : + SingletonQObjectCreationBase(typeObj, typeObj) {} + + QObject *operator ()(QQmlEngine *, QJSEngine *) const + { + Shiboken::GilState gil; + Shiboken::AutoDecRef args(PyTuple_New(0)); + PyObject *retVal = PyObject_CallObject(data()->callable, args); + QObject *result = handleReturnValue(retVal); + if (result == nullptr) + Py_XDECREF(retVal); + return result; + } +}; + +// QML singleton creation by invoking a callback, passing QQmlEngine. Keeps a +// references to the the callback. +class SingletonQObjectCallbackCreation : public SingletonQObjectCreationBase +{ +public: + explicit SingletonQObjectCallbackCreation(PyObject *callback) : + SingletonQObjectCreationBase(callback, callback) {} + explicit SingletonQObjectCallbackCreation(PyObject *callback, PyObject *ref) : + SingletonQObjectCreationBase(callback, ref) {} + + QObject *operator ()(QQmlEngine *engine, QJSEngine *) const + { + Shiboken::GilState gil; + Shiboken::AutoDecRef args(PyTuple_New(1)); + PyTuple_SET_ITEM(args, 0, + Shiboken::Conversions::pointerToPython(qQmlEngineType(), engine)); + PyObject *retVal = PyObject_CallObject(data()->callable, args); + QObject *result = handleReturnValue(retVal); + if (result == nullptr) + Py_XDECREF(retVal); + return result; + } +}; + +using SingletonQObjectCreation = std::function<QObject*(QQmlEngine *, QJSEngine *)>; + +// Modern (6.7) singleton type registration using RegisterSingletonTypeAndRevisions +// and information set to QMetaClassInfo (QObject only pending QTBUG-110467). +static int qmlRegisterSingletonTypeV2(PyObject *pyObj, PyObject *pyClassInfoObj, + const ImportData &importData, + const SingletonQObjectCreation &callback) +{ + PyTypeObject *pyObjType = reinterpret_cast<PyTypeObject *>(pyObj); + if (!isQObjectDerived(pyObjType, true)) + return -1; + + const QMetaObject *metaObject = PySide::retrieveMetaObject(pyObjType); + Q_ASSERT(metaObject); + const QMetaObject *classInfoMetaObject = pyObj == pyClassInfoObj + ? metaObject : PySide::retrieveMetaObject(pyClassInfoObj); + + QList<int> ids; + QQmlPrivate::RegisterSingletonTypeAndRevisions type { + QQmlPrivate::RegisterType::StructVersion::Base, // structVersion + importData.importName.constData(), + importData.toTypeRevision(), // version + callback, // qObjectApi, + metaObject, + classInfoMetaObject, + QMetaType(QMetaType::QObjectStar), // typeId + nullptr, // extensionMetaObject + nullptr, // extensionObjectCreate + &ids + }; + + QQmlPrivate::qmlregister(QQmlPrivate::SingletonAndRevisionsRegistration, &type); + const int qmlTypeId = ids.value(0, -1); + if (qmlTypeId == -1) { + PyErr_Format(PyExc_TypeError, "Singleton QML meta type registration of \"%s\" failed.", + pyObjType->tp_name); + } + return qmlTypeId; +} + +// Legacy (pre 6.7) singleton type registration using RegisterSingletonType +// for QObject and value types. Still used by qmlRegisterSingletonType() +// for the hypothetical case of a value type. +static int qmlRegisterSingletonType(PyObject *pyObj, const ImportData &importData, + const char *qmlName, PyObject *callback, + bool isQObject, bool hasCallback) +{ + if (hasCallback && !checkSingletonCallback(callback)) + return -1; const QMetaObject *metaObject = nullptr; @@ -201,56 +506,37 @@ int qmlRegisterSingletonType(PyObject *pyObj, const char *uri, int versionMajor, if (!isQObjectDerived(pyObjType, true)) return -1; - // If we don't have a callback we'll need the pyObj to stay allocated indefinitely - if (!hasCallback) - Py_INCREF(pyObj); - metaObject = PySide::retrieveMetaObject(pyObjType); Q_ASSERT(metaObject); } - QQmlPrivate::RegisterSingletonType type; - type.structVersion = 0; - - type.uri = uri; - type.version = QTypeRevision::fromVersion(versionMajor, versionMinor); - type.typeName = qmlName; - type.instanceMetaObject = metaObject; + QQmlPrivate::RegisterSingletonType type { + QQmlPrivate::RegisterType::StructVersion::Base, // structVersion + importData.importName.constData(), + importData.toTypeRevision(), // version + qmlName, // typeName + {}, // scriptApi + {}, // qObjectApi + metaObject, // instanceMetaObject + {}, // typeId + nullptr, // extensionMetaObject + nullptr, // extensionObjectCreate + {} // revision + }; if (isQObject) { // FIXME: Fix this to assign new type ids each time. type.typeId = QMetaType(QMetaType::QObjectStar); - type.qObjectApi = - [callback, pyObj, hasCallback](QQmlEngine *engine, QJSEngine *) -> QObject * { - Shiboken::GilState gil; - AutoDecRef args(PyTuple_New(hasCallback ? 1 : 0)); - - if (hasCallback) { - PyTuple_SET_ITEM(args, 0, Conversions::pointerToPython( - qQmlEngineType(), engine)); - } - - AutoDecRef retVal(PyObject_CallObject(hasCallback ? callback : pyObj, args)); - - // Make sure the callback returns something we can convert, else the entire application will crash. - if (retVal.isNull() || - Conversions::isPythonToCppPointerConvertible(qObjectType(), retVal) == nullptr) { - PyErr_Format(PyExc_TypeError, "Callback returns invalid value."); - return nullptr; - } - - QObject *obj = nullptr; - Conversions::pythonToCppPointer(qObjectType(), retVal, &obj); - - if (obj != nullptr) - Py_INCREF(retVal); - - return obj; - }; + if (hasCallback) + type.qObjectApi = SingletonQObjectCallbackCreation(callback); + else + type.qObjectApi = SingletonQObjectFromTypeCreation(pyObj); } else { type.scriptApi = [callback](QQmlEngine *engine, QJSEngine *) -> QJSValue { + using namespace Shiboken; + Shiboken::GilState gil; AutoDecRef args(PyTuple_New(1)); @@ -280,9 +566,19 @@ int qmlRegisterSingletonType(PyObject *pyObj, const char *uri, int versionMajor, return QQmlPrivate::qmlregister(QQmlPrivate::SingletonRegistration, &type); } -int qmlRegisterSingletonInstance(PyObject *pyObj, const char *uri, int versionMajor, - int versionMinor, const char *qmlName, - PyObject *instanceObject) +// Legacy (pre 6.7) compatibility helper for the free register functions. +int qmlRegisterSingletonType(PyObject *pyObj,const char *uri, + int versionMajor, int versionMinor, const char *qmlName, + PyObject *callback, bool isQObject, bool hasCallback) +{ + return qmlRegisterSingletonType(pyObj, {uri, versionMajor, versionMinor}, qmlName, + callback, isQObject, hasCallback); +} + +// Modern (6.7) singleton instance registration using RegisterSingletonTypeAndRevisions +// and information set to QMetaClassInfo (QObject only). +static int qmlRegisterSingletonInstance(PyObject *pyObj, const ImportData &importData, + PyObject *instanceObject) { using namespace Shiboken; @@ -305,165 +601,140 @@ int qmlRegisterSingletonInstance(PyObject *pyObj, const char *uri, int versionMa const QMetaObject *metaObject = PySide::retrieveMetaObject(pyObjType); Q_ASSERT(metaObject); - QQmlPrivate::RegisterSingletonType type; - type.structVersion = 0; - - type.uri = uri; - type.version = QTypeRevision::fromVersion(versionMajor, versionMinor); - type.typeName = qmlName; - type.instanceMetaObject = metaObject; - - // FIXME: Fix this to assign new type ids each time. - type.typeId = QMetaType(QMetaType::QObjectStar); - type.qObjectApi = registrationFunctor; - - - return QQmlPrivate::qmlregister(QQmlPrivate::SingletonRegistration, &type); + QList<int> ids; + QQmlPrivate::RegisterSingletonTypeAndRevisions type { + QQmlPrivate::RegisterType::StructVersion::Base, // structVersion + importData.importName.constData(), + importData.toTypeRevision(), // version + registrationFunctor, // qObjectApi, + metaObject, + metaObject, // classInfoMetaObject + QMetaType(QMetaType::QObjectStar), // typeId + nullptr, // extensionMetaObject + nullptr, // extensionObjectCreate + &ids + }; + + QQmlPrivate::qmlregister(QQmlPrivate::SingletonAndRevisionsRegistration, &type); + return ids.value(0, -1); } -} // namespace PySide::Qml - -static std::string getGlobalString(const char *name) -{ - using Shiboken::AutoDecRef; - - PyObject *globals = PyEval_GetGlobals(); - - AutoDecRef pyName(Py_BuildValue("s", name)); - - PyObject *globalVar = PyDict_GetItem(globals, pyName); - - if (globalVar == nullptr || !PyUnicode_Check(globalVar)) - return ""; - - const char *stringValue = _PepUnicode_AsString(globalVar); - return stringValue != nullptr ? stringValue : ""; -} - -static int getGlobalInt(const char *name) +// Legacy (pre 6.7) compatibility helper for the free register functions. +int qmlRegisterSingletonInstance(PyObject *pyObj, const char *uri, int versionMajor, + int versionMinor, const char *qmlName, + PyObject *instanceObject) { - using Shiboken::AutoDecRef; - - PyObject *globals = PyEval_GetGlobals(); - - AutoDecRef pyName(Py_BuildValue("s", name)); - - PyObject *globalVar = PyDict_GetItem(globals, pyName); - - if (globalVar == nullptr || !PyLong_Check(globalVar)) - return -1; - - long value = PyLong_AsLong(globalVar); - - if (value > std::numeric_limits<int>::max() || value < std::numeric_limits<int>::min()) + auto *type = checkTypeObject(pyObj, "qmlRegisterSingletonInstance()"); + if (type == nullptr || !setClassInfo(type, qmlElementKey, qmlName) + || !setSingletonClassInfo(type)) { return -1; - - return value; + } + return qmlRegisterSingletonInstance(pyObj, {uri, versionMajor, versionMinor}, + instanceObject); } +} // namespace PySide::Qml + enum class RegisterMode { Normal, - Anonymous, - Uncreatable, Singleton }; -static PyObject *qmlElementMacroHelper(PyObject *pyObj, - const char *decoratorName, - const char *typeName = nullptr, - RegisterMode mode = RegisterMode::Normal, - const char *noCreationReason = nullptr) +namespace PySide::Qml { + +// Check for a static create() method on a decorated singleton. +// Might set a Python error if the check fails. +static std::optional<SingletonQObjectCreation> + singletonCreateMethod(PyTypeObject *pyObjType) { - if (!PyType_Check(pyObj)) { - PyErr_Format(PyExc_TypeError, "This decorator can only be used on classes."); - return nullptr; + Shiboken::AutoDecRef tpDict(PepType_GetDict(pyObjType)); + auto *create = PyDict_GetItemString(tpDict.object(), "create"); + // Method decorated by "@staticmethod" + if (create == nullptr || std::strcmp(Py_TYPE(create)->tp_name, "staticmethod") != 0) + return std::nullopt; + // 3.10: "__wrapped__" + Shiboken::AutoDecRef function(PyObject_GetAttrString(create, "__func__")); + if (function.isNull()) { + PyErr_Format(PyExc_TypeError, "Cannot retrieve function of callback (%S).", + create); + return std::nullopt; } + if (!checkSingletonCallback(function.object())) + return std::nullopt; + // Reference to the type needs to be kept. + return SingletonQObjectCallbackCreation(function.object(), + reinterpret_cast<PyObject *>(pyObjType)); +} - PyTypeObject *pyObjType = reinterpret_cast<PyTypeObject *>(pyObj); - if (typeName == nullptr) - typeName = pyObjType->tp_name; - if (!PySequence_Contains(pyObjType->tp_mro, reinterpret_cast<PyObject *>(qObjectType()))) { - PyErr_Format(PyExc_TypeError, "This decorator can only be used with classes inherited from QObject, got %s.", - typeName); +PyObject *qmlElementMacro(PyObject *pyObj, const char *decoratorName, + const QByteArray &typeName) +{ + auto *pyObjType = checkTypeObject(pyObj, decoratorName); + if (pyObjType == nullptr) return nullptr; - } - std::string importName = getGlobalString("QML_IMPORT_NAME"); - int majorVersion = getGlobalInt("QML_IMPORT_MAJOR_VERSION"); - int minorVersion = getGlobalInt("QML_IMPORT_MINOR_VERSION"); - - if (importName.empty()) { - PyErr_Format(PyExc_TypeError, "You need specify QML_IMPORT_NAME in order to use %s.", - decoratorName); + if (!PySide::isQObjectDerived(pyObjType, false)) { + PyErr_Format(PyExc_TypeError, + "%s can only be used with classes inherited from QObject, got %s.", + decoratorName, pyObjType->tp_name); return nullptr; } - if (majorVersion == -1) { - PyErr_Format(PyExc_TypeError, "You need specify QML_IMPORT_MAJOR_VERSION in order to use %s.", - decoratorName); - return nullptr; - } - - // Specifying a minor version is optional - if (minorVersion == -1) - minorVersion = 0; - - const char *uri = importName.c_str(); - const int result = mode == RegisterMode::Singleton - ? PySide::Qml::qmlRegisterSingletonType(pyObj, uri, majorVersion, minorVersion, - typeName, nullptr, - PySide::isQObjectDerived(pyObjType, false), - false) - : PySide::Qml::qmlRegisterType(pyObj, uri, majorVersion, minorVersion, - mode != RegisterMode::Anonymous ? typeName : nullptr, - noCreationReason, - mode == RegisterMode::Normal); - - if (result == -1) { - PyErr_Format(PyExc_TypeError, "%s: Failed to register type %s.", - decoratorName, typeName); - } - - return pyObj; -} - -namespace PySide::Qml { + if (!setClassInfo(pyObjType, qmlElementKey, typeName)) + return nullptr; -PyObject *qmlElementMacro(PyObject *pyObj, const char *decoratorName, - const char *typeName = nullptr) -{ RegisterMode mode = RegisterMode::Normal; - const char *noCreationReason = nullptr; const auto info = PySide::Qml::qmlTypeInfo(pyObj); auto *registerObject = pyObj; - if (!info.isNull()) { - if (info->flags.testFlag(PySide::Qml::QmlTypeFlag::Singleton)) + if (info) { + if (info->flags.testFlag(PySide::Qml::QmlTypeFlag::Singleton)) { mode = RegisterMode::Singleton; - else if (info->flags.testFlag(PySide::Qml::QmlTypeFlag::Uncreatable)) - mode = RegisterMode::Uncreatable; - noCreationReason = info->noCreationReason.c_str(); + setSingletonClassInfo(pyObjType); + } if (info->foreignType) registerObject = reinterpret_cast<PyObject *>(info->foreignType); } - if (!qmlElementMacroHelper(registerObject, decoratorName, typeName, mode, noCreationReason)) + + const auto importDataO = getGlobalImportData(decoratorName); + if (!importDataO.has_value()) + return nullptr; + const auto importData = importDataO.value(); + + int result{}; + if (mode == RegisterMode::Singleton) { + auto singletonCreateMethodO = singletonCreateMethod(pyObjType); + if (!singletonCreateMethodO.has_value()) { + if (PyErr_Occurred() != nullptr) + return nullptr; + singletonCreateMethodO = SingletonQObjectFromTypeCreation(pyObj); + } + result = PySide::Qml::qmlRegisterSingletonTypeV2(registerObject, pyObj, importData, + singletonCreateMethodO.value()); + } else { + result = PySide::Qml::qmlRegisterType(registerObject, pyObj, importData); + } + if (result == -1) { + PyErr_Format(PyExc_TypeError, "%s: Failed to register type %s.", + decoratorName, pyObjType->tp_name); return nullptr; + } + return pyObj; } PyObject *qmlElementMacro(PyObject *pyObj) { - return qmlElementMacro(pyObj, "QmlElement"); + return qmlElementMacro(pyObj, "QmlElement", "auto"_ba); } -PyObject *qmlNamedElementMacro(PyObject *pyObj, const char *typeName) +PyObject *qmlNamedElementMacro(PyObject *pyObj, const QByteArray &typeName) { - return qmlElementMacro(pyObj, "QmlNamedElement", qstrdup(typeName)); + return qmlElementMacro(pyObj, "QmlNamedElement", typeName); } PyObject *qmlAnonymousMacro(PyObject *pyObj) { - return qmlElementMacroHelper(pyObj, "QmlAnonymous", nullptr, - RegisterMode::Anonymous); + return qmlElementMacro(pyObj, "QmlAnonymous", "anonymous"_ba); } PyObject *qmlSingletonMacro(PyObject *pyObj) |