diff options
Diffstat (limited to 'sources/pyside6/libpysideqml/pysideqmlattached.cpp')
-rw-r--r-- | sources/pyside6/libpysideqml/pysideqmlattached.cpp | 216 |
1 files changed, 216 insertions, 0 deletions
diff --git a/sources/pyside6/libpysideqml/pysideqmlattached.cpp b/sources/pyside6/libpysideqml/pysideqmlattached.cpp new file mode 100644 index 000000000..d484257e2 --- /dev/null +++ b/sources/pyside6/libpysideqml/pysideqmlattached.cpp @@ -0,0 +1,216 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "pysideqmlattached.h" +#include "pysideqmlattached_p.h" +#include "pysideqmltypeinfo_p.h" +#include "pysideqmlregistertype_p.h" + +#include <signalmanager.h> +#include <pyside_p.h> +#include <pysideclassdecorator_p.h> + +#include <shiboken.h> +#include <signature.h> +#include <sbkstring.h> + +#include <QtQml/qqml.h> + +#include <algorithm> + +// The QmlAttached decorator modifies QmlElement to register an attached property +// type. Due to the (reverse) execution order of decorators, it needs to follow +// QmlElement. +class PySideQmlAttachedPrivate : public PySide::ClassDecorator::TypeDecoratorPrivate +{ +public: + PyObject *tp_call(PyObject *self, PyObject *args, PyObject * /* kw */) override; + const char *name() const override; +}; + +// The call operator is passed the class type and registers the type +// in QmlTypeInfo. +PyObject *PySideQmlAttachedPrivate::tp_call(PyObject *self, PyObject *args, PyObject * /* kw */) +{ + PyObject *klass = tp_call_check(args, CheckMode::WrappedType); + if (klass == nullptr) + return nullptr; + + auto *data = DecoratorPrivate::get<PySideQmlAttachedPrivate>(self); + PySide::Qml::ensureQmlTypeInfo(klass)->attachedType = data->type(); + + Py_INCREF(klass); + return klass; +} + +const char *PySideQmlAttachedPrivate::name() const +{ + return "QmlAttached"; +} + +extern "C" { + +static PyTypeObject *createPySideQmlAttachedType(void) +{ + auto typeSlots = + PySide::ClassDecorator::Methods<PySideQmlAttachedPrivate>::typeSlots(); + + PyType_Spec PySideQmlAttachedType_spec = { + "2:PySide6.QtCore.qmlAttached", + sizeof(PySideClassDecorator), + 0, + Py_TPFLAGS_DEFAULT, + typeSlots.data() + }; + return SbkType_FromSpec(&PySideQmlAttachedType_spec); +} + +PyTypeObject *PySideQmlAttached_TypeF(void) +{ + static auto *type = createPySideQmlAttachedType(); + return type; +} + +} // extern "C" + +static const char *qmlAttached_SignatureStrings[] = { + "PySide6.QtQml.QmlAttached(self,type:type)", + nullptr // Sentinel +}; + +namespace PySide::Qml { + +static QObject *attachedFactoryHelper(PyTypeObject *attachingType, QObject *o) +{ + // Call static qmlAttachedProperties() on type. If there is an error + // and nullptr is returned, a crash occurs. So, errors should at least be + // printed. + + Shiboken::GilState gilState; + Shiboken::Conversions::SpecificConverter converter("QObject"); + Q_ASSERT(converter); + + static const char methodName[] = "qmlAttachedProperties"; + static PyObject *const pyMethodName = Shiboken::String::createStaticString(methodName); + PyObject *attachingTypeObj = reinterpret_cast<PyObject *>(attachingType); + Shiboken::AutoDecRef pyResult(PyObject_CallMethodObjArgs(attachingTypeObj, pyMethodName, + attachingTypeObj /* self */, + converter.toPython(&o), + nullptr)); + if (pyResult.isNull() || PyErr_Occurred()) { + PyErr_Print(); + return nullptr; + } + + if (PyType_IsSubtype(pyResult->ob_type, qObjectType()) == 0) { + qWarning("QmlAttached: Attached objects must inherit QObject, got %s.", + pyResult->ob_type->tp_name); + return nullptr; + } + + QObject *result = nullptr; + converter.toCpp(pyResult.object(), &result); + return result; +} + +// Since the required attached factory signature does not have a void *user +// parameter to store the attaching type, we employ a template trick, storing +// the attaching types in an array and create non-type-template (int) functions +// taking the array index as template parameter. +// We initialize the attachedFactories array with factory functions +// accessing the attachingTypes[N] using template metaprogramming. + +enum { MAX_ATTACHING_TYPES = 50}; + +using AttachedFactory = QObject *(*)(QObject *); + +static int nextAttachingType = 0; +static PyTypeObject *attachingTypes[MAX_ATTACHING_TYPES]; +static AttachedFactory attachedFactories[MAX_ATTACHING_TYPES]; + +template <int N> +static QObject *attachedFactory(QObject *o) +{ + return attachedFactoryHelper(attachingTypes[N], o); +} + +template<int N> +struct AttachedFactoryInitializerBase +{ +}; + +template<int N> +struct AttachedFactoryInitializer : AttachedFactoryInitializerBase<N> +{ + static void init() + { + attachedFactories[N] = attachedFactory<N>; + AttachedFactoryInitializer<N-1>::init(); + } +}; + +template<> +struct AttachedFactoryInitializer<0> : AttachedFactoryInitializerBase<0> +{ + static void init() + { + attachedFactories[0] = attachedFactory<0>; + } +}; + +void initQmlAttached(PyObject *module) +{ + std::fill(attachingTypes, attachingTypes + MAX_ATTACHING_TYPES, nullptr); + AttachedFactoryInitializer<MAX_ATTACHING_TYPES - 1>::init(); + + if (InitSignatureStrings(PySideQmlAttached_TypeF(), qmlAttached_SignatureStrings) < 0) + return; + + Py_INCREF(PySideQmlAttached_TypeF()); + PyModule_AddObject(module, "QmlAttached", + reinterpret_cast<PyObject *>(PySideQmlAttached_TypeF())); +} + +PySide::Qml::QmlExtensionInfo qmlAttachedInfo(PyTypeObject *t, + const std::shared_ptr<QmlTypeInfo> &info) +{ + PySide::Qml::QmlExtensionInfo result{nullptr, nullptr}; + if (!info || info->attachedType == nullptr) + return result; + + auto *name = reinterpret_cast<PyTypeObject *>(t)->tp_name; + if (nextAttachingType >= MAX_ATTACHING_TYPES) { + qWarning("Unable to initialize attached type \"%s\": " + "The limit %d of attached types has been reached.", + name, MAX_ATTACHING_TYPES); + return result; + } + + result.metaObject = PySide::retrieveMetaObject(info->attachedType); + if (result.metaObject == nullptr) { + qWarning("Unable to retrieve meta object for %s", name); + return result; + } + + attachingTypes[nextAttachingType] = t; + result.factory = attachedFactories[nextAttachingType]; + ++nextAttachingType; + + return result; +} + +QObject *qmlAttachedPropertiesObject(PyObject *typeObject, QObject *obj, bool create) +{ + auto *type = reinterpret_cast<PyTypeObject *>(typeObject); + auto *end = attachingTypes + nextAttachingType; + auto *typePtr = std::find(attachingTypes, end, type); + if (typePtr == end) { + qWarning("%s: Attaching type \"%s\" not found.", __FUNCTION__, type->tp_name); + return nullptr; + } + + auto func = attachedFactories[std::uintptr_t(typePtr - attachingTypes)]; + return ::qmlAttachedPropertiesObject(obj, func, create); +} + +} // namespace PySide::Qml |