diff options
Diffstat (limited to 'sources/pyside6/libpysideqml')
27 files changed, 2390 insertions, 0 deletions
diff --git a/sources/pyside6/libpysideqml/CMakeLists.txt b/sources/pyside6/libpysideqml/CMakeLists.txt new file mode 100644 index 000000000..1af8c02cf --- /dev/null +++ b/sources/pyside6/libpysideqml/CMakeLists.txt @@ -0,0 +1,115 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +set(libpysideqml_libraries Qt::Core Qt::CorePrivate Qt::Qml Qt::QmlPrivate) + +set(libpysideqml_HEADERS # installed below + pysideqmlattached.h + pysideqmlattached_p.h + pysideqmlextended_p.h + pysideqmlforeign_p.h + pysideqml.h + pysideqmllistproperty_p.h + pysideqmlmacros.h + pysideqmlmetacallerror_p.h + pysideqmlnamedelement_p.h + pysideqmlregistertype.h + pysideqmlregistertype_p.h + pysideqmltypeinfo_p.h + pysideqmluncreatable.h +) + +set(libpysideqml_SRC + pysideqml.cpp + pysideqmlattached.cpp + pysideqmlforeign.cpp + pysideqmlextended.cpp + pysideqmlregistertype.cpp + pysideqmlmetacallerror.cpp + pysideqmllistproperty.cpp + pysideqmlnamedelement.cpp + pysideqmluncreatable.cpp + pysideqmltypeinfo.cpp + ${libpysideqml_HEADERS} +) + +# Hack for // https://github.com/python/cpython/issues/86286 causes issues +set_source_files_properties( + pysideqmlmetacallerror.cpp PROPERTIES SKIP_UNITY_BUILD_INCLUSION ON +) + +add_library(pyside6qml SHARED ${libpysideqml_SRC} ${other_files}) +add_library(PySide6::pyside6qml ALIAS pyside6qml) + +target_include_directories(pyside6qml PUBLIC + $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}> + $<INSTALL_INTERFACE:include/PySide6Qml> +) + +target_compile_definitions(pyside6qml PRIVATE -DQT_LEAN_HEADERS=1 -DQT_NO_KEYWORDS=1) + +target_link_libraries(pyside6qml + PRIVATE PySide6::pyside6 Shiboken6::libshiboken ${libpysideqml_libraries}) + +set_target_properties(pyside6qml PROPERTIES + VERSION ${BINDING_API_VERSION} + SOVERSION "${PYSIDE_SO_VERSION}" + OUTPUT_NAME "pyside6qml${pyside6_SUFFIX}${SHIBOKEN_PYTHON_SHARED_LIBRARY_SUFFIX}" + DEFINE_SYMBOL BUILD_LIBPYSIDEQML) + +target_compile_definitions(pyside6qml PRIVATE -DQT_LEAN_HEADERS=1) + +set_property(TARGET pyside6qml PROPERTY CXX_STANDARD 17) + +if(PYSIDE_QT_CONF_PREFIX) + set_property(SOURCE pysideqml.cpp + APPEND + PROPERTY COMPILE_DEFINITIONS + PYSIDE_QT_CONF_PREFIX=${PYSIDE_QT_CONF_PREFIX}) +endif() + +# +# install stuff +# + +if (CMAKE_BUILD_TYPE STREQUAL "Debug") + set(LIBRARY_OUTPUT_SUFFIX ${CMAKE_DEBUG_POSTFIX}) +else() + set(LIBRARY_OUTPUT_SUFFIX ${CMAKE_RELEASE_POSTFIX}) +endif() + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D QT_NO_CAST_FROM_ASCII -D QT_NO_CAST_TO_ASCII") + +qfp_strip_library("pyside6qml") + +# Install-tree / relocatable package config file. +configure_package_config_file( + "${CMAKE_CURRENT_SOURCE_DIR}/PySide6QmlConfig-spec.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/install/PySide6QmlConfig${SHIBOKEN_PYTHON_CONFIG_SUFFIX}.cmake" + INSTALL_DESTINATION "${LIB_INSTALL_DIR}/cmake/PySide6Qml" +) + +configure_file("${CMAKE_CURRENT_SOURCE_DIR}/PySide6QmlConfig.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/PySide6QmlConfig.cmake" @ONLY) +configure_file("${CMAKE_CURRENT_SOURCE_DIR}/PySide6QmlConfigVersion.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/PySide6QmlConfigVersion.cmake" @ONLY) + +install(FILES ${libpysideqml_HEADERS} + DESTINATION include/${BINDING_NAME}${pyside6qml_SUFFIX}) + +install(TARGETS pyside6qml EXPORT PySide6QmlTargets + LIBRARY DESTINATION "${LIB_INSTALL_DIR}" + ARCHIVE DESTINATION "${LIB_INSTALL_DIR}" + RUNTIME DESTINATION bin) +install(EXPORT PySide6QmlTargets NAMESPACE PySide6Qml:: + DESTINATION "${LIB_INSTALL_DIR}/cmake/PySide6Qml") + +install(FILES "${CMAKE_CURRENT_BINARY_DIR}/PySide6QmlConfig.cmake" + DESTINATION "${LIB_INSTALL_DIR}/cmake/PySide6Qml") + +install(FILES + "${CMAKE_CURRENT_BINARY_DIR}/install/PySide6QmlConfig${SHIBOKEN_PYTHON_CONFIG_SUFFIX}.cmake" + DESTINATION "${LIB_INSTALL_DIR}/cmake/PySide6Qml") + +install(FILES "${CMAKE_CURRENT_BINARY_DIR}/PySide6QmlConfigVersion.cmake" + DESTINATION "${LIB_INSTALL_DIR}/cmake/PySide6Qml") diff --git a/sources/pyside6/libpysideqml/PySide6QmlConfig-spec.cmake.in b/sources/pyside6/libpysideqml/PySide6QmlConfig-spec.cmake.in new file mode 100644 index 000000000..36eb4123a --- /dev/null +++ b/sources/pyside6/libpysideqml/PySide6QmlConfig-spec.cmake.in @@ -0,0 +1,7 @@ +@PACKAGE_INIT@ + +# Import targets only when using an installed PySide6 config file (so not during a regular +# PySide6 build, or during a super project build). +if (NOT TARGET PySide6::pyside6qml) + include("${CMAKE_CURRENT_LIST_DIR}/PySide6QmlTargets.cmake") +endif() diff --git a/sources/pyside6/libpysideqml/PySide6QmlConfig.cmake.in b/sources/pyside6/libpysideqml/PySide6QmlConfig.cmake.in new file mode 100644 index 000000000..dab0a6b13 --- /dev/null +++ b/sources/pyside6/libpysideqml/PySide6QmlConfig.cmake.in @@ -0,0 +1,5 @@ +if (NOT PYTHON_CONFIG_SUFFIX) + message(STATUS "PySide6QmlConfig: Using default python: @SHIBOKEN_PYTHON_CONFIG_SUFFIX@") + SET(PYTHON_CONFIG_SUFFIX @SHIBOKEN_PYTHON_CONFIG_SUFFIX@) +endif() +include(${CMAKE_CURRENT_LIST_DIR}/PySide6QmlConfig${PYTHON_CONFIG_SUFFIX}.cmake) diff --git a/sources/pyside6/libpysideqml/PySide6QmlConfigVersion.cmake.in b/sources/pyside6/libpysideqml/PySide6QmlConfigVersion.cmake.in new file mode 100644 index 000000000..f5073ce08 --- /dev/null +++ b/sources/pyside6/libpysideqml/PySide6QmlConfigVersion.cmake.in @@ -0,0 +1,10 @@ +set(PACKAGE_VERSION @BINDING_API_VERSION@) + +if("${PACKAGE_VERSION}" VERSION_LESS "${PACKAGE_FIND_VERSION}" ) + set(PACKAGE_VERSION_COMPATIBLE FALSE) +else("${PACKAGE_VERSION}" VERSION_LESS "${PACKAGE_FIND_VERSION}" ) + set(PACKAGE_VERSION_COMPATIBLE TRUE) + if( "${PACKAGE_FIND_VERSION}" STREQUAL "${PACKAGE_VERSION}") + set(PACKAGE_VERSION_EXACT TRUE) + endif( "${PACKAGE_FIND_VERSION}" STREQUAL "${PACKAGE_VERSION}") +endif("${PACKAGE_VERSION}" VERSION_LESS "${PACKAGE_FIND_VERSION}" ) diff --git a/sources/pyside6/libpysideqml/pysideqml.cpp b/sources/pyside6/libpysideqml/pysideqml.cpp new file mode 100644 index 000000000..3fe673fdf --- /dev/null +++ b/sources/pyside6/libpysideqml/pysideqml.cpp @@ -0,0 +1,35 @@ +// Copyright (C) 2021 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 "pysideqml.h" +#include "pysideqmllistproperty_p.h" +#include "pysideqmlattached_p.h" +#include "pysideqmlextended_p.h" +#include "pysideqmlforeign_p.h" +#include "pysideqmlnamedelement_p.h" +#include "pysideqmluncreatable.h" +#include "pysideqmlmetacallerror_p.h" + +#include <QtQml/QQmlPropertyMap> +#include <QtQml/QQmlComponent> + +#include <signalmanager.h> + +namespace PySide::Qml +{ + +void init(PyObject *module) +{ + initQtQmlListProperty(module); + initQmlAttached(module); + initQmlForeign(module); + initQmlExtended(module); + initQmlNamedElement(module); + initQmlUncreatable(module); + PySide::SignalManager::setQmlMetaCallErrorHandler(PySide::Qml::qmlMetaCallErrorHandler); + + qRegisterMetaType<QQmlPropertyMap *>(); // PYSIDE-1845, QQmlPropertyMap * properties + qRegisterMetaType<QQmlComponent *>(); // PYSIDE-2415, QQmlComponent * properties +} + +} //namespace PySide::Qml diff --git a/sources/pyside6/libpysideqml/pysideqml.h b/sources/pyside6/libpysideqml/pysideqml.h new file mode 100644 index 000000000..d975bcf97 --- /dev/null +++ b/sources/pyside6/libpysideqml/pysideqml.h @@ -0,0 +1,18 @@ +// Copyright (C) 2021 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 + +#ifndef PYSIDEQML_H +#define PYSIDEQML_H + +#include "pysideqmlmacros.h" + +#include <sbkpython.h> + +namespace PySide::Qml +{ + +PYSIDEQML_API void init(PyObject *module); + +} //namespace PySide::Qml + +#endif // PYSIDEQML_H 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 diff --git a/sources/pyside6/libpysideqml/pysideqmlattached.h b/sources/pyside6/libpysideqml/pysideqmlattached.h new file mode 100644 index 000000000..96f788268 --- /dev/null +++ b/sources/pyside6/libpysideqml/pysideqmlattached.h @@ -0,0 +1,28 @@ +// 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 + +#ifndef PYSIDEQMLATTACHED_H +#define PYSIDEQMLATTACHED_H + +#include <sbkpython.h> + +#include "pysideqmlmacros.h" + +#include <QtCore/qtconfigmacros.h> + +QT_FORWARD_DECLARE_CLASS(QObject) + +namespace PySide::Qml +{ + +/// PySide implementation of qmlAttachedPropertiesObject<T> function. +/// \param typeObject attaching type +/// \param obj attachee +/// \param create Whether to create the Attachment object +/// \return Attachment object instance +PYSIDEQML_API QObject *qmlAttachedPropertiesObject(PyObject *typeObject, QObject *obj, + bool create = true); + +} // namespace PySide::Qml + +#endif // PYSIDEQMLATTACHED_H diff --git a/sources/pyside6/libpysideqml/pysideqmlattached_p.h b/sources/pyside6/libpysideqml/pysideqmlattached_p.h new file mode 100644 index 000000000..7c8a47fb8 --- /dev/null +++ b/sources/pyside6/libpysideqml/pysideqmlattached_p.h @@ -0,0 +1,21 @@ +// 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 + +#ifndef PYSIDEQMLATTACHED_P_H +#define PYSIDEQMLATTACHED_P_H + +#include <sbkpython.h> + +#include <memory> + +namespace PySide::Qml { +struct QmlExtensionInfo; +struct QmlTypeInfo; + +void initQmlAttached(PyObject *module); + +PySide::Qml::QmlExtensionInfo qmlAttachedInfo(PyTypeObject *t, + const std::shared_ptr<QmlTypeInfo> &info); +} // namespace PySide::Qml + +#endif // PYSIDEQMLATTACHED_P_H diff --git a/sources/pyside6/libpysideqml/pysideqmlextended.cpp b/sources/pyside6/libpysideqml/pysideqmlextended.cpp new file mode 100644 index 000000000..23543d589 --- /dev/null +++ b/sources/pyside6/libpysideqml/pysideqmlextended.cpp @@ -0,0 +1,145 @@ +// 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 "pysideqmlextended_p.h" +#include "pysideqmltypeinfo_p.h" +#include "pysideqmlregistertype_p.h" + +#include <pyside_p.h> +#include <pysideclassdecorator_p.h> + +#include <shiboken.h> +#include <signature.h> +#include <sbkstring.h> + +#include <QtQml/qqml.h> + +// The QmlExtended decorator modifies QmlElement to register an extension. +// Due to the (reverse) execution order of decorators, it needs to follow +// QmlElement. +class PySideQmlExtendedPrivate : 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 *PySideQmlExtendedPrivate::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<PySideQmlExtendedPrivate>(self); + PySide::Qml::ensureQmlTypeInfo(klass)->extensionType = data->type(); + + Py_INCREF(klass); + return klass; +} + +const char *PySideQmlExtendedPrivate::name() const +{ + return "QmlExtended"; +} + +extern "C" { + +static PyTypeObject *createPySideQmlExtendedType(void) +{ + auto typeSlots = + PySide::ClassDecorator::Methods<PySideQmlExtendedPrivate>::typeSlots(); + + PyType_Spec PySideQmlExtendedType_spec = { + "2:PySide6.QtCore.qmlExtended", + sizeof(PySideClassDecorator), + 0, + Py_TPFLAGS_DEFAULT, + typeSlots.data() + }; + return SbkType_FromSpec(&PySideQmlExtendedType_spec); +} + +PyTypeObject *PySideQmlExtended_TypeF(void) +{ + static auto *type = createPySideQmlExtendedType(); + return type; +} + +} // extern "C" + +static const char *qmlExtended_SignatureStrings[] = { + "PySide6.QtQml.QmlExtended(self,type:type)", + nullptr // Sentinel +}; + +namespace PySide::Qml { + +static QObject *extensionFactory(QObject *o) +{ + Shiboken::GilState gilState; + Shiboken::Conversions::SpecificConverter converter("QObject"); + Q_ASSERT(converter); + PyObject *pyObj = converter.toPython(&o); + Q_ASSERT(pyObj); + + // Search for the extension type and create an instance by invoking + // the call operator on type with the parent parameter. + // If there is an error and nullptr is returned, a crash occurs, + // so, errors should at least be printed. + + auto *pyObjType = Py_TYPE(pyObj); + const auto info = qmlTypeInfo(reinterpret_cast<PyObject *>(pyObjType)); + if (!info || info->extensionType == nullptr) { + qWarning("QmlExtended: Cannot find extension of %s.", pyObjType->tp_name); + return nullptr; + } + + Shiboken::AutoDecRef args(PyTuple_New(1)); + PyTuple_SET_ITEM(args.object(), 0, pyObj); + auto *extensionTypeObj = reinterpret_cast<PyObject *>(info->extensionType); + Shiboken::AutoDecRef pyResult(PyObject_Call(extensionTypeObj, args, nullptr)); + if (pyResult.isNull() || PyErr_Occurred()) { + PyErr_Print(); + return nullptr; + } + + if (PyType_IsSubtype(pyResult->ob_type, qObjectType()) == 0) { + qWarning("QmlExtended: Extension objects must inherit QObject, got %s.", + pyResult->ob_type->tp_name); + return nullptr; + } + + QObject *result = nullptr; + converter.toCpp(pyResult.object(), &result); + return result; +} + +void initQmlExtended(PyObject *module) +{ + if (InitSignatureStrings(PySideQmlExtended_TypeF(), qmlExtended_SignatureStrings) < 0) + return; + + Py_INCREF(PySideQmlExtended_TypeF()); + PyModule_AddObject(module, "QmlExtended", + reinterpret_cast<PyObject *>(PySideQmlExtended_TypeF())); +} + +PySide::Qml::QmlExtensionInfo qmlExtendedInfo(PyObject *t, + const std::shared_ptr<QmlTypeInfo> &info) +{ + PySide::Qml::QmlExtensionInfo result{nullptr, nullptr}; + if (info && info->extensionType) { + result.metaObject = PySide::retrieveMetaObject(info->extensionType); + if (result.metaObject) { + result.factory = extensionFactory; + } else { + qWarning("Unable to retrieve meta object for %s", + reinterpret_cast<PyTypeObject *>(t)->tp_name); + } + } + return result; +} + +} // namespace PySide::Qml diff --git a/sources/pyside6/libpysideqml/pysideqmlextended_p.h b/sources/pyside6/libpysideqml/pysideqmlextended_p.h new file mode 100644 index 000000000..17d6dae64 --- /dev/null +++ b/sources/pyside6/libpysideqml/pysideqmlextended_p.h @@ -0,0 +1,21 @@ +// 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 + +#ifndef PYSIDEQMLEXTENDED_P_H +#define PYSIDEQMLEXTENDED_P_H + +#include <sbkpython.h> + +#include <memory> + +namespace PySide::Qml { +struct QmlExtensionInfo; +struct QmlTypeInfo; + +void initQmlExtended(PyObject *module); + +PySide::Qml::QmlExtensionInfo qmlExtendedInfo(PyObject *t, + const std::shared_ptr<QmlTypeInfo> &info); +} // namespace PySide::Qml + +#endif // PYSIDEQMLEXTENDED_P_H diff --git a/sources/pyside6/libpysideqml/pysideqmlforeign.cpp b/sources/pyside6/libpysideqml/pysideqmlforeign.cpp new file mode 100644 index 000000000..18d39d121 --- /dev/null +++ b/sources/pyside6/libpysideqml/pysideqmlforeign.cpp @@ -0,0 +1,92 @@ +// 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 "pysideqmlforeign_p.h" +#include "pysideqmltypeinfo_p.h" + +#include <signalmanager.h> +#include <pysideclassdecorator_p.h> + +#include <shiboken.h> +#include <signature.h> +#include <sbkstring.h> + +#include <QtCore/QDebug> + +// The QmlForeign decorator modifies QmlElement to create a different type +// QmlElement. +class PySideQmlForeignPrivate : 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 *PySideQmlForeignPrivate::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<PySideQmlForeignPrivate>(self); + const auto info = PySide::Qml::ensureQmlTypeInfo(klass); + info->foreignType = data->type(); + // Insert an alias to be used by the factory functions of Decorators like + // @QmlExtended and @QmlAttached. + auto *foreignObj = reinterpret_cast<const PyObject *>(info->foreignType); + PySide::Qml::insertQmlTypeInfoAlias(foreignObj, info); + + Py_INCREF(klass); + return klass; +} + +const char *PySideQmlForeignPrivate::name() const +{ + return "QmlForeign"; +} + +extern "C" { + +static PyTypeObject *createPySideQmlForeignType(void) +{ + auto typeSlots = + PySide::ClassDecorator::Methods<PySideQmlForeignPrivate>::typeSlots(); + + PyType_Spec PySideQmlForeignType_spec = { + "2:PySide6.QtCore.qmlForeign", + sizeof(PySideClassDecorator), + 0, + Py_TPFLAGS_DEFAULT, + typeSlots.data() + }; + return SbkType_FromSpec(&PySideQmlForeignType_spec); +} + +PyTypeObject *PySideQmlForeign_TypeF(void) +{ + static auto *type = createPySideQmlForeignType(); + return type; +} + +} // extern "C" + +static const char *qmlForeign_SignatureStrings[] = { + "PySide6.QtQml.QmlForeign(self,type:type)", + nullptr // Sentinel +}; + +namespace PySide::Qml { + +void initQmlForeign(PyObject *module) +{ + if (InitSignatureStrings(PySideQmlForeign_TypeF(), qmlForeign_SignatureStrings) < 0) + return; + + Py_INCREF(PySideQmlForeign_TypeF()); + PyModule_AddObject(module, "QmlForeign", + reinterpret_cast<PyObject *>(PySideQmlForeign_TypeF())); +} + +} // namespace PySide::Qml diff --git a/sources/pyside6/libpysideqml/pysideqmlforeign_p.h b/sources/pyside6/libpysideqml/pysideqmlforeign_p.h new file mode 100644 index 000000000..85688aab0 --- /dev/null +++ b/sources/pyside6/libpysideqml/pysideqmlforeign_p.h @@ -0,0 +1,17 @@ +// 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 + +#ifndef PYSIDEQMLFOREIGN_P_H +#define PYSIDEQMLFOREIGN_P_H + +#include <sbkpython.h> + +namespace PySide::Qml { +struct QmlExtensionInfo; +struct QmlTypeInfo; + +void initQmlForeign(PyObject *module); + +} // namespace PySide::Qml + +#endif // PYSIDEQMLFOREIGN_P_H diff --git a/sources/pyside6/libpysideqml/pysideqmllistproperty.cpp b/sources/pyside6/libpysideqml/pysideqmllistproperty.cpp new file mode 100644 index 000000000..75bb5af96 --- /dev/null +++ b/sources/pyside6/libpysideqml/pysideqmllistproperty.cpp @@ -0,0 +1,307 @@ +// Copyright (C) 2021 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 "pysideqmllistproperty_p.h" +#include "pysideqmlregistertype_p.h" + +#include <shiboken.h> +#include <pep384ext.h> +#include <signature.h> + +#include <pysideproperty.h> +#include <pysideproperty_p.h> + +#include <QtCore/QObject> +#include <QtQml/QQmlListProperty> + +// This is the user data we store in the property. +class QmlListPropertyPrivate : public PySidePropertyPrivate +{ +public: + void metaCall(PyObject *source, QMetaObject::Call call, void **args) override; + + PyTypeObject *type = nullptr; + PyObject *append = nullptr; + PyObject *count = nullptr; + PyObject *at = nullptr; + PyObject *clear = nullptr; + PyObject *replace = nullptr; + PyObject *removeLast = nullptr; +}; + +extern "C" +{ + +static PyObject *propList_tp_new(PyTypeObject *subtype, PyObject * /* args */, PyObject * /* kwds */) +{ + auto *me = PepExt_TypeCallAlloc<PySideProperty>(subtype, 0); + me->d = new QmlListPropertyPrivate; + return reinterpret_cast<PyObject *>(me); +} + +static int propListTpInit(PyObject *self, PyObject *args, PyObject *kwds) +{ + static const char *kwlist[] = {"type", "append", "count", "at", "clear", + "replace", "removeLast", + "doc", "notify", // PySideProperty + "designable", "scriptable", "stored", + "user", "constant", "final", + nullptr}; + PySideProperty *pySelf = reinterpret_cast<PySideProperty *>(self); + + auto *data = static_cast<QmlListPropertyPrivate *>(pySelf->d); + + char *doc{}; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, + "O|OOOOOOsObbbbbb:QtQml.ListProperty", + const_cast<char **>(kwlist), + &data->type, + &data->append, + &data->count, + &data->at, + &data->clear, + &data->replace, + &data->removeLast, + /*s*/ &doc, + /*O*/ &(data->notify), // PySideProperty + /*bbb*/ &(data->designable), + &(data->scriptable), + &(data->stored), + /*bbb*/ &(data->user), + &(data->constant), + &(data->final))) { + return -1; + } + + if (doc) + data->doc = doc; + else + data->doc.clear(); + + PyTypeObject *qobjectType = qObjectType(); + + if (!PySequence_Contains(data->type->tp_mro, reinterpret_cast<PyObject *>(qobjectType))) { + PyErr_Format(PyExc_TypeError, "A type inherited from %s expected, got %s.", + qobjectType->tp_name, data->type->tp_name); + return -1; + } + + if ((data->append && data->append != Py_None && !PyCallable_Check(data->append)) || + (data->count && data->count != Py_None && !PyCallable_Check(data->count)) || + (data->at && data->at != Py_None && !PyCallable_Check(data->at)) || + (data->clear && data->clear != Py_None && !PyCallable_Check(data->clear)) || + (data->replace && data->replace != Py_None && !PyCallable_Check(data->replace)) || + (data->removeLast && data->removeLast != Py_None && !PyCallable_Check(data->removeLast))) { + PyErr_Format(PyExc_TypeError, "Non-callable parameter given"); + return -1; + } + + data->typeName = QByteArrayLiteral("QQmlListProperty<QObject>"); + + return 0; +} + +static PyTypeObject *createPropertyListType() +{ + PyType_Slot PropertyListType_slots[] = { + {Py_tp_new, reinterpret_cast<void *>(propList_tp_new)}, + {Py_tp_init, reinterpret_cast<void *>(propListTpInit)}, + {0, nullptr} + }; + + PyType_Spec PropertyListType_spec = { + "2:PySide6.QtQml.ListProperty", + sizeof(PySideProperty), + 0, + Py_TPFLAGS_DEFAULT, + PropertyListType_slots, + }; + + Shiboken::AutoDecRef bases(Py_BuildValue("(O)", PySideProperty_TypeF())); + return SbkType_FromSpecWithBases(&PropertyListType_spec, bases.object()); +} + +PyTypeObject *PropertyList_TypeF(void) +{ + // PYSIDE-2230: This was a wrong replacement by static AutoDecref. + // Never do that, deletes things way too late. + static PyTypeObject *type = createPropertyListType(); + return type; +} + +} // extern "C" + +// Implementation of QQmlListProperty<T>::AppendFunction callback +void propListAppender(QQmlListProperty<QObject> *propList, QObject *item) +{ + Shiboken::GilState state; + + Shiboken::AutoDecRef args(PyTuple_New(2)); + PyTypeObject *qobjectType = qObjectType(); + PyTuple_SET_ITEM(args, 0, + Shiboken::Conversions::pointerToPython(qobjectType, propList->object)); + PyTuple_SET_ITEM(args, 1, + Shiboken::Conversions::pointerToPython(qobjectType, item)); + + auto *data = reinterpret_cast<QmlListPropertyPrivate *>(propList->data); + Shiboken::AutoDecRef retVal(PyObject_CallObject(data->append, args)); + + if (PyErr_Occurred()) + PyErr_Print(); +} + +// Implementation of QQmlListProperty<T>::CountFunction callback +qsizetype propListCount(QQmlListProperty<QObject> *propList) +{ + Shiboken::GilState state; + + Shiboken::AutoDecRef args(PyTuple_New(1)); + PyTuple_SET_ITEM(args, 0, + Shiboken::Conversions::pointerToPython(qObjectType(), propList->object)); + + auto *data = reinterpret_cast<QmlListPropertyPrivate *>(propList->data); + Shiboken::AutoDecRef retVal(PyObject_CallObject(data->count, args)); + + // Check return type + if (PyErr_Occurred()) { + PyErr_Print(); + return 0; + } + + qsizetype cppResult = 0; + auto *converter = Shiboken::Conversions::PrimitiveTypeConverter<qsizetype>(); + if (auto *pythonToCpp = Shiboken::Conversions::isPythonToCppConvertible(converter, retVal)) + pythonToCpp(retVal, &cppResult); + return cppResult; +} + +// Implementation of QQmlListProperty<T>::AtFunction callback +QObject *propListAt(QQmlListProperty<QObject> *propList, qsizetype index) +{ + Shiboken::GilState state; + + Shiboken::AutoDecRef args(PyTuple_New(2)); + PyTypeObject *qobjectType = qObjectType(); + PyTuple_SET_ITEM(args, 0, + Shiboken::Conversions::pointerToPython(qobjectType, propList->object)); + auto *converter = Shiboken::Conversions::PrimitiveTypeConverter<qsizetype>(); + PyTuple_SET_ITEM(args, 1, + Shiboken::Conversions::copyToPython(converter, &index)); + + auto *data = reinterpret_cast<QmlListPropertyPrivate *>(propList->data); + Shiboken::AutoDecRef retVal(PyObject_CallObject(data->at, args)); + + QObject *result = 0; + if (PyErr_Occurred()) + PyErr_Print(); + else if (PyType_IsSubtype(Py_TYPE(retVal), data->type)) + Shiboken::Conversions::pythonToCppPointer(qobjectType, retVal, &result); + return result; +} + +// Implementation of QQmlListProperty<T>::ClearFunction callback +void propListClear(QQmlListProperty<QObject> * propList) +{ + Shiboken::GilState state; + + Shiboken::AutoDecRef args(PyTuple_New(1)); + PyTypeObject *qobjectType = qObjectType(); + PyTuple_SET_ITEM(args, 0, + Shiboken::Conversions::pointerToPython(qobjectType, propList->object)); + + auto *data = reinterpret_cast<QmlListPropertyPrivate *>(propList->data); + Shiboken::AutoDecRef retVal(PyObject_CallObject(data->clear, args)); + + if (PyErr_Occurred()) + PyErr_Print(); +} + +// Implementation of QQmlListProperty<T>::ReplaceFunction callback +void propListReplace(QQmlListProperty<QObject> *propList, qsizetype index, QObject *value) +{ + Shiboken::GilState state; + + Shiboken::AutoDecRef args(PyTuple_New(3)); + PyTypeObject *qobjectType = qObjectType(); + PyTuple_SET_ITEM(args, 0, + Shiboken::Conversions::pointerToPython(qobjectType, propList->object)); + auto *converter = Shiboken::Conversions::PrimitiveTypeConverter<qsizetype>(); + PyTuple_SET_ITEM(args, 1, + Shiboken::Conversions::copyToPython(converter, &index)); + PyTuple_SET_ITEM(args, 2, + Shiboken::Conversions::pointerToPython(qobjectType, value)); + + auto *data = reinterpret_cast<QmlListPropertyPrivate *>(propList->data); + Shiboken::AutoDecRef retVal(PyObject_CallObject(data->replace, args)); + + if (PyErr_Occurred()) + PyErr_Print(); +} + +// Implementation of QQmlListProperty<T>::RemoveLastFunction callback +void propListRemoveLast(QQmlListProperty<QObject> *propList) +{ + Shiboken::GilState state; + + Shiboken::AutoDecRef args(PyTuple_New(1)); + PyTypeObject *qobjectType = qObjectType(); + PyTuple_SET_ITEM(args, 0, + Shiboken::Conversions::pointerToPython(qobjectType, propList->object)); + + auto *data = reinterpret_cast<QmlListPropertyPrivate *>(propList->data); + Shiboken::AutoDecRef retVal(PyObject_CallObject(data->removeLast, args)); + + if (PyErr_Occurred()) + PyErr_Print(); +} + +// qt_metacall specialization for ListProperties +void QmlListPropertyPrivate::metaCall(PyObject *source, QMetaObject::Call call, void **args) +{ + if (call != QMetaObject::ReadProperty) + return; + + QObject *qobj; + PyTypeObject *qobjectType = qObjectType(); + Shiboken::Conversions::pythonToCppPointer(qobjectType, source, &qobj); + QQmlListProperty<QObject> declProp( + qobj, this, + append && append != Py_None ? &propListAppender : nullptr, + count && count != Py_None ? &propListCount : nullptr, + at && at != Py_None ? &propListAt : nullptr, + clear && clear != Py_None ? &propListClear : nullptr, + replace && replace != Py_None ? &propListReplace : nullptr, + removeLast && removeLast != Py_None ? &propListRemoveLast : nullptr); + + // Copy the data to the memory location requested by the meta call + void *v = args[0]; + *reinterpret_cast<QQmlListProperty<QObject> *>(v) = declProp; +} + +static const char *PropertyList_SignatureStrings[] = { + "PySide6.QtQml.ListProperty(self,type:type,append:typing.Callable," + "at:typing.Callable=None,clear:typing.Callable=None,count:typing.Callable=None)", + nullptr // Sentinel +}; + +namespace PySide::Qml { + +void initQtQmlListProperty(PyObject *module) +{ + // Export QmlListProperty type + if (InitSignatureStrings(PropertyList_TypeF(), PropertyList_SignatureStrings) < 0) { + PyErr_Print(); + qWarning() << "Error initializing PropertyList type."; + return; + } + + // Register QQmlListProperty metatype for use in QML + qRegisterMetaType<QQmlListProperty<QObject>>(); + + Py_INCREF(reinterpret_cast<PyObject *>(PropertyList_TypeF())); + PyModule_AddObject(module, PepType_GetNameStr(PropertyList_TypeF()), + reinterpret_cast<PyObject *>(PropertyList_TypeF())); +} + +} // namespace PySide::Qml diff --git a/sources/pyside6/libpysideqml/pysideqmllistproperty_p.h b/sources/pyside6/libpysideqml/pysideqmllistproperty_p.h new file mode 100644 index 000000000..c00ffbf5b --- /dev/null +++ b/sources/pyside6/libpysideqml/pysideqmllistproperty_p.h @@ -0,0 +1,13 @@ +// Copyright (C) 2021 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 + +#ifndef PYSIDEQMLLISTPROPERTY_H +#define PYSIDEQMLLISTPROPERTY_H + +#include <sbkpython.h> + +namespace PySide::Qml { +void initQtQmlListProperty(PyObject *module); +} + +#endif // PYSIDEQMLLISTPROPERTY_H diff --git a/sources/pyside6/libpysideqml/pysideqmlmacros.h b/sources/pyside6/libpysideqml/pysideqmlmacros.h new file mode 100644 index 000000000..e9f24d269 --- /dev/null +++ b/sources/pyside6/libpysideqml/pysideqmlmacros.h @@ -0,0 +1,18 @@ +// Copyright (C) 2021 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 + +#ifndef PYSIDEQMLMACROS_H +#define PYSIDEQMLMACROS_H + +#include <shibokenmacros.h> + +#define PYSIDEQML_EXPORT LIBSHIBOKEN_EXPORT +#define PYSIDEQML_IMPORT LIBSHIBOKEN_IMPORT + +#ifdef BUILD_LIBPYSIDEQML +# define PYSIDEQML_API PYSIDEQML_EXPORT +#else +# define PYSIDEQML_API PYSIDEQML_IMPORT +#endif + +#endif // PYSIDEQMLMACROS_H diff --git a/sources/pyside6/libpysideqml/pysideqmlmetacallerror.cpp b/sources/pyside6/libpysideqml/pysideqmlmetacallerror.cpp new file mode 100644 index 000000000..63cefedb5 --- /dev/null +++ b/sources/pyside6/libpysideqml/pysideqmlmetacallerror.cpp @@ -0,0 +1,67 @@ +// Copyright (C) 2021 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 "pysideqmlmetacallerror_p.h" + +#include <sbkpython.h> +#include <sbkstring.h> +#include <autodecref.h> + +// Remove deprecated MACRO of copysign for MSVC #86286 +// https://github.com/python/cpython/issues/86286 +#ifdef copysign +# undef copysign +#endif + +#include <QtCore/QObject> +#include <QtCore/QString> + +#include <QtQml/QQmlEngine> +#include <QtQml/QQmlListProperty> + +#if __has_include (<private/qv4engine_p.h>) +# define QML_PRIVATE_API_SUPPORT +# include <private/qv4engine_p.h> +# include <private/qv4context_p.h> +# include <private/qqmldata_p.h> +#endif + +namespace PySide::Qml { + +std::optional<int> qmlMetaCallErrorHandler(QObject *object) +{ +#ifdef QML_PRIVATE_API_SUPPORT + // This JS engine grabber based off of Qt 5.5's `qjsEngine` function + QQmlData *data = QQmlData::get(object, false); + if (!data || data->jsWrapper.isNullOrUndefined()) + return {}; + + QV4::ExecutionEngine *engine = data->jsWrapper.engine(); + if (engine->currentStackFrame == nullptr) + return {}; + + PyObject *errType, *errValue, *errTraceback; + PyErr_Fetch(&errType, &errValue, &errTraceback); + // PYSIDE-464: The error is only valid before PyErr_Restore, + // PYSIDE-464: therefore we take local copies. + Shiboken::AutoDecRef objStr(PyObject_Str(errValue)); + const QString errString = QString::fromUtf8(Shiboken::String::toCString(objStr)); + const bool isSyntaxError = errType == PyExc_SyntaxError; + const bool isTypeError = errType == PyExc_TypeError; + PyErr_Restore(errType, errValue, errTraceback); + + PyErr_Print(); // Note: PyErr_Print clears the error. + + if (isSyntaxError) + return engine->throwSyntaxError(errString); + if (isTypeError) + return engine->throwTypeError(errString); + return engine->throwError(errString); +#else + Q_UNUSED(object); + qWarning("libpyside6qml was built without QML private API support, error handling will not work."); + return {}; +#endif // QML_PRIVATE_API_SUPPORT +} + +} // namespace PySide::Qml diff --git a/sources/pyside6/libpysideqml/pysideqmlmetacallerror_p.h b/sources/pyside6/libpysideqml/pysideqmlmetacallerror_p.h new file mode 100644 index 000000000..fcbb6395d --- /dev/null +++ b/sources/pyside6/libpysideqml/pysideqmlmetacallerror_p.h @@ -0,0 +1,21 @@ +// Copyright (C) 2021 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 + +#ifndef PYSIDEQMLMETACALLERROR_P_H +#define PYSIDEQMLMETACALLERROR_P_H + +#include <optional> + +#include <QtCore/qtclasshelpermacros.h> + +QT_FORWARD_DECLARE_CLASS(QObject) + +namespace PySide::Qml { + +// Helper for SignalManager::qt_metacall(): +// Bubbles Python exceptions up to the Javascript engine, if called from one +std::optional<int> qmlMetaCallErrorHandler(QObject *object); + +} // namespace PySide::Qml + +#endif // PYSIDEQMLMETACALLERROR_P_H diff --git a/sources/pyside6/libpysideqml/pysideqmlnamedelement.cpp b/sources/pyside6/libpysideqml/pysideqmlnamedelement.cpp new file mode 100644 index 000000000..faf3e4116 --- /dev/null +++ b/sources/pyside6/libpysideqml/pysideqmlnamedelement.cpp @@ -0,0 +1,74 @@ +// 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 "pysideqmlnamedelement_p.h" +#include <pysideclassdecorator_p.h> +#include <pysideqmlregistertype_p.h> + +#include <shiboken.h> +#include <signature.h> + +class PySideQmlNamedElementPrivate : public PySide::ClassDecorator::StringDecoratorPrivate +{ +public: + PyObject *tp_call(PyObject *self, PyObject *args, PyObject * /* kw */) override; + const char *name() const override; +}; + +const char *PySideQmlNamedElementPrivate::name() const +{ + return "QmlNamedElement"; +} + +// The call operator is passed the class type and registers the type +PyObject *PySideQmlNamedElementPrivate::tp_call(PyObject *self, PyObject *args, PyObject *) +{ + PyObject *klass = tp_call_check(args, CheckMode::WrappedType); + if (klass == nullptr) + return nullptr; + + auto *data = DecoratorPrivate::get<PySideQmlNamedElementPrivate>(self); + auto *result = PySide::Qml::qmlNamedElementMacro(klass, data->string()); + Py_XINCREF(result); + return result; +} + +extern "C" { + +PyTypeObject *createPySideQmlNamedElementType(void) +{ + auto typeSlots = + PySide::ClassDecorator::Methods<PySideQmlNamedElementPrivate>::typeSlots(); + + PyType_Spec PySideQmlNamedElementType_spec = { + "2:PySide6.QtCore.qmlNamedElement", + sizeof(PySideClassDecorator), + 0, + Py_TPFLAGS_DEFAULT, + typeSlots.data() + }; + return SbkType_FromSpec(&PySideQmlNamedElementType_spec); +} + +PyTypeObject *PySideQmlNamedElement_TypeF(void) +{ + static auto *type = createPySideQmlNamedElementType(); + return type; +} + +} // extern "C" + +static const char *qmlNamedElement_SignatureStrings[] = { + "PySide6.QtQml.QmlNamedElement(self,reason:str)", + nullptr // Sentinel +}; + +void initQmlNamedElement(PyObject *module) +{ + if (InitSignatureStrings(PySideQmlNamedElement_TypeF(), qmlNamedElement_SignatureStrings) < 0) + return; + + Py_INCREF(PySideQmlNamedElement_TypeF()); + PyModule_AddObject(module, "QmlNamedElement", + reinterpret_cast<PyObject *>(PySideQmlNamedElement_TypeF())); +} diff --git a/sources/pyside6/libpysideqml/pysideqmlnamedelement_p.h b/sources/pyside6/libpysideqml/pysideqmlnamedelement_p.h new file mode 100644 index 000000000..4a4575de2 --- /dev/null +++ b/sources/pyside6/libpysideqml/pysideqmlnamedelement_p.h @@ -0,0 +1,11 @@ +// 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 + +#ifndef PYSIDEQMLNAMEDELEMENT_P_H +#define PYSIDEQMLNAMEDELEMENT_P_H + +#include <sbkpython.h> + +void initQmlNamedElement(PyObject *module); + +#endif // PYSIDEQMLNAMEDELEMENT_P_H diff --git a/sources/pyside6/libpysideqml/pysideqmlregistertype.cpp b/sources/pyside6/libpysideqml/pysideqmlregistertype.cpp new file mode 100644 index 000000000..4ccd459d5 --- /dev/null +++ b/sources/pyside6/libpysideqml/pysideqmlregistertype.cpp @@ -0,0 +1,757 @@ +// Copyright (C) 2016 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 "pysideqmlregistertype.h" +#include "pysideqmlregistertype_p.h" +#include "pysideqmltypeinfo_p.h" +#include "pysideqmlattached_p.h" +#include "pysideqmlextended_p.h" +#include "pysideqmluncreatable.h" + +#include <limits> +#include <optional> + +// shiboken +#include <shiboken.h> +#include <sbkstring.h> + +// pyside +#include <pyside.h> +#include <pysideqobject.h> +#include <pysideclassinfo.h> +#include <pyside_p.h> + +#include <QtCore/QMutex> +#include <QtCore/QTypeRevision> + +#include <QtQml/qqml.h> +#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()); + PySide::setNextQObjectMemoryAddr(memory); + Shiboken::GilState state; + PyObject *obj = PyObject_CallObject(reinterpret_cast<PyObject *>(type), 0); + if (!obj || PyErr_Occurred()) + PyErr_Print(); + PySide::setNextQObjectMemoryAddr(nullptr); +} + +PyTypeObject *qObjectType() +{ + static PyTypeObject *const result = + Shiboken::Conversions::getPythonTypeObject("QObject*"); + assert(result); + return result; +} + +static PyTypeObject *qQmlEngineType() +{ + static PyTypeObject *const result = + Shiboken::Conversions::getPythonTypeObject("QQmlEngine*"); + assert(result); + return result; +} + +static PyTypeObject *qQJSValueType() +{ + static PyTypeObject *const result = + Shiboken::Conversions::getPythonTypeObject("QJSValue*"); + assert(result); + return result; +} + +// Check if o inherits from baseClass +static bool inheritsFrom(const QMetaObject *o, const char *baseClass) +{ + for (auto *base = o->superClass(); base ; base = base->superClass()) { + if (qstrcmp(base->className(), baseClass) == 0) + return true; + } + return false; +} + +// Check if o inherits from QPyQmlPropertyValueSource. +static inline bool isQmlPropertyValueSource(const QMetaObject *o) +{ + return inheritsFrom(o, "QPyQmlPropertyValueSource"); +} + +// Check if o inherits from QQmlParserStatus. +static inline bool isQmlParserStatus(const QMetaObject *o) +{ + return inheritsFrom(o, "QPyQmlParserStatus"); +} + +static QByteArray getGlobalString(const char *name) +{ + PyObject *globalVar = PyDict_GetItemString(PyEval_GetGlobals(), name); + + 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) +{ + PyObject *globalVar = PyDict_GetItemString(PyEval_GetGlobals(), name); + + if (globalVar == nullptr || PyLong_Check(globalVar) == 0) + return -1; + + 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 {}; + } + + if (result.majorVersion == -1) { + PyErr_Format(PyExc_TypeError, "You need specify QML_IMPORT_MAJOR_VERSION in order to use %s.", + decoratorName); + return {}; + } + + // Specifying a minor version is optional + if (result.minorVersion == -1) + result.minorVersion = 0; + return result; +} + +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); + + 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))); + + const auto typeInfo = qmlTypeInfo(pyObj); + 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, + // it is found behind the QObject. + type.parserStatusCast = isQmlParserStatus(metaObject) + ? int(sizeof(QObject)) + : QQmlPrivate::StaticCastSelector<QObject, QQmlParserStatus>::cast(); + // Similar for QPyQmlPropertyValueSource + type.valueSourceCast = isQmlPropertyValueSource(metaObject) + ? int(sizeof(QObject)) + : QQmlPrivate::StaticCastSelector<QObject, QQmlPropertyValueSource>::cast(); + type.valueInterceptorCast = + QQmlPrivate::StaticCastSelector<QObject, QQmlPropertyValueInterceptor>::cast(); + } + + 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.", + typeName.constData()); + } + return qmlTypeId; +} + +static int qmlRegisterType(PyObject *pyObj, PyObject *pyClassInfoObj, + const ImportData &importData) +{ + 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); + return qmlRegisterType(pyObj, importData, metaObject, classInfoMetaObject); +} + +// 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; + + const QMetaObject *metaObject = PySide::retrieveMetaObject(type); + Q_ASSERT(metaObject); + + // 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); + + 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; + + if (isQObject) { + PyTypeObject *pyObjType = reinterpret_cast<PyTypeObject *>(pyObj); + + if (!isQObjectDerived(pyObjType, true)) + return -1; + + metaObject = PySide::retrieveMetaObject(pyObjType); + Q_ASSERT(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); + + 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)); + + PyTuple_SET_ITEM(args, 0, Conversions::pointerToPython( + qQmlEngineType(), engine)); + + AutoDecRef retVal(PyObject_CallObject(callback, args)); + + PyTypeObject *qjsvalueType = qQJSValueType(); + + // Make sure the callback returns something we can convert, else the entire application will crash. + if (retVal.isNull() || + Conversions::isPythonToCppPointerConvertible(qjsvalueType, retVal) == nullptr) { + PyErr_Format(PyExc_TypeError, "Callback returns invalid value."); + return QJSValue(QJSValue::UndefinedValue); + } + + QJSValue *val = nullptr; + Conversions::pythonToCppPointer(qjsvalueType, retVal, &val); + + Py_INCREF(retVal); + + return *val; + }; + } + + return QQmlPrivate::qmlregister(QQmlPrivate::SingletonRegistration, &type); +} + +// 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; + + // Check if the Python Type inherit from QObject + PyTypeObject *pyObjType = reinterpret_cast<PyTypeObject *>(pyObj); + + if (!isQObjectDerived(pyObjType, true)) + return -1; + + // Convert the instanceObject (PyObject) into a QObject + QObject *instanceQObject = PySide::convertToQObject(instanceObject, true); + if (instanceQObject == nullptr) + return -1; + + // Create Singleton Functor to pass the QObject to the Type registration step + // similarly to the case when we have a callback + QQmlPrivate::SingletonInstanceFunctor registrationFunctor; + registrationFunctor.m_object = instanceQObject; + + const QMetaObject *metaObject = PySide::retrieveMetaObject(pyObjType); + Q_ASSERT(metaObject); + + 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); +} + +// 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) +{ + auto *type = checkTypeObject(pyObj, "qmlRegisterSingletonInstance()"); + if (type == nullptr || !setClassInfo(type, qmlElementKey, qmlName) + || !setSingletonClassInfo(type)) { + return -1; + } + return qmlRegisterSingletonInstance(pyObj, {uri, versionMajor, versionMinor}, + instanceObject); +} + +} // namespace PySide::Qml + +enum class RegisterMode { + Normal, + Singleton +}; + +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) +{ + 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)); +} + +PyObject *qmlElementMacro(PyObject *pyObj, const char *decoratorName, + const QByteArray &typeName) +{ + auto *pyObjType = checkTypeObject(pyObj, decoratorName); + if (pyObjType == nullptr) + return nullptr; + + 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 (!setClassInfo(pyObjType, qmlElementKey, typeName)) + return nullptr; + + RegisterMode mode = RegisterMode::Normal; + const auto info = PySide::Qml::qmlTypeInfo(pyObj); + auto *registerObject = pyObj; + if (info) { + if (info->flags.testFlag(PySide::Qml::QmlTypeFlag::Singleton)) { + mode = RegisterMode::Singleton; + setSingletonClassInfo(pyObjType); + } + if (info->foreignType) + registerObject = reinterpret_cast<PyObject *>(info->foreignType); + } + + 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", "auto"_ba); +} + +PyObject *qmlNamedElementMacro(PyObject *pyObj, const QByteArray &typeName) +{ + return qmlElementMacro(pyObj, "QmlNamedElement", typeName); +} + +PyObject *qmlAnonymousMacro(PyObject *pyObj) +{ + return qmlElementMacro(pyObj, "QmlAnonymous", "anonymous"_ba); +} + +PyObject *qmlSingletonMacro(PyObject *pyObj) +{ + PySide::Qml::ensureQmlTypeInfo(pyObj)->flags.setFlag(PySide::Qml::QmlTypeFlag::Singleton); + Py_INCREF(pyObj); + return pyObj; +} + +QuickRegisterItemFunction getQuickRegisterItemFunction() +{ + return quickRegisterItemFunction; +} + +void setQuickRegisterItemFunction(QuickRegisterItemFunction function) +{ + quickRegisterItemFunction = function; +} + +} // namespace PySide::Qml diff --git a/sources/pyside6/libpysideqml/pysideqmlregistertype.h b/sources/pyside6/libpysideqml/pysideqmlregistertype.h new file mode 100644 index 000000000..859172322 --- /dev/null +++ b/sources/pyside6/libpysideqml/pysideqmlregistertype.h @@ -0,0 +1,99 @@ +// Copyright (C) 2016 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 + +#ifndef PYSIDEQMLREGISTERTYPE_H +#define PYSIDEQMLREGISTERTYPE_H + +#include "pysideqmlmacros.h" + +#include <sbkpython.h> +#include <QtCore/qtconfigmacros.h> + +QT_BEGIN_NAMESPACE +namespace QQmlPrivate +{ +struct RegisterTypeAndRevisions; +} +QT_END_NAMESPACE + +namespace PySide::Qml +{ + +/** + * PySide implementation of qmlRegisterType<T> function. + * + * This is a helper for the legacy free qmlRegisterType*() type functions. + * Decorators should be used instead. + * + * \param pyObj Python type to be registered. + * \param uri QML element uri. + * \param versionMajor QML component major version. + * \param versionMinor QML component minor version. + * \param qmlName QML element name + * \return the metatype id of the registered type. + */ +PYSIDEQML_API int qmlRegisterType(PyObject *pyObj, const char *uri, + int versionMajor, int versionMinor, + const char *qmlName, const char *noCreationReason = nullptr, + bool creatable = true); + +/** + * PySide implementation of qmlRegisterSingletonType<T> function. + * + * This is a helper for the legacy free qmlRegisterSingletonType<T> type function. + * Decorators should be used instead. + * + * \param pyObj Python type to be registered. + * \param uri QML element uri. + * \param versionMajor QML component major version. + * \param versionMinor QML component minor version. + * \param qmlName QML element name + * \param callback Registration callback + * \return the metatype id of the registered type. + */ +PYSIDEQML_API int qmlRegisterSingletonType(PyObject *pyObj,const char *uri, + int versionMajor, int versionMinor, const char *qmlName, + PyObject *callback, bool isQObject, bool hasCallback); + +/** + * PySide implementation of qmlRegisterSingletonInstance<T> function. + * + * \param pyObj Python type to be registered. + * \param uri QML element uri. + * \param versionMajor QML component major version. + * \param versionMinor QML component minor version. + * \param qmlName QML element name + * \param instanceObject singleton object to be registered. + * \return the metatype id of the registered type. + */ +PYSIDEQML_API int qmlRegisterSingletonInstance(PyObject *pyObj, const char *uri, + int versionMajor, int versionMinor, + const char *qmlName, PyObject *instanceObject); + +/** + * PySide implementation of the QML_ELEMENT macro + * + * \param pyObj Python type to be registered + */ +PYSIDEQML_API PyObject *qmlElementMacro(PyObject *pyObj); + +/// PySide implementation of the QML_ANONYMOUS macro +/// \param pyObj Python type to be registered +PYSIDEQML_API PyObject *qmlAnonymousMacro(PyObject *pyObj); + +/// PySide implementation of the QML_SINGLETON macro +/// \param pyObj Python type to be registered +PYSIDEQML_API PyObject *qmlSingletonMacro(PyObject *pyObj); + + +// Used by QtQuick module to fill the QQmlPrivate::RegisterType::parserStatusCast, +// valueSourceCast and valueInterceptorCast fields with the correct values. +using QuickRegisterItemFunction = + bool (*)(PyObject *pyObj, QT_PREPEND_NAMESPACE(QQmlPrivate::RegisterTypeAndRevisions) *); + +PYSIDEQML_API QuickRegisterItemFunction getQuickRegisterItemFunction(); +PYSIDEQML_API void setQuickRegisterItemFunction(QuickRegisterItemFunction function); + +} // namespace PySide::Qml + +#endif // PYSIDEQMLREGISTERTYPE_H diff --git a/sources/pyside6/libpysideqml/pysideqmlregistertype_p.h b/sources/pyside6/libpysideqml/pysideqmlregistertype_p.h new file mode 100644 index 000000000..f11f92241 --- /dev/null +++ b/sources/pyside6/libpysideqml/pysideqmlregistertype_p.h @@ -0,0 +1,20 @@ +// Copyright (C) 2021 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 + +#ifndef PYSIDEQMLREGISTERTYPE_P_H +#define PYSIDEQMLREGISTERTYPE_P_H + +#include <sbkpython.h> + +#include <QtCore/QByteArray> + +PyTypeObject *qObjectType(); + + +namespace PySide::Qml { + +PyObject *qmlNamedElementMacro(PyObject *pyObj, const QByteArray &typeName); + +} + +#endif // PYSIDEQMLREGISTERTYPE_P_H diff --git a/sources/pyside6/libpysideqml/pysideqmltypeinfo.cpp b/sources/pyside6/libpysideqml/pysideqmltypeinfo.cpp new file mode 100644 index 000000000..f369f7400 --- /dev/null +++ b/sources/pyside6/libpysideqml/pysideqmltypeinfo.cpp @@ -0,0 +1,70 @@ +// 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 "pysideqmltypeinfo_p.h" + +#include <QtCore/QDebug> +#include <QtCore/QHash> + +#include <algorithm> + +namespace PySide::Qml { + +using QmlTypeInfoHash = QHash<const PyObject *, QmlTypeInfoPtr>; + +Q_GLOBAL_STATIC(QmlTypeInfoHash, qmlTypeInfoHashStatic); + +QmlTypeInfoPtr ensureQmlTypeInfo(const PyObject *o) +{ + auto *hash = qmlTypeInfoHashStatic(); + auto it = hash->find(o); + if (it == hash->end()) + it = hash->insert(o, std::make_shared<QmlTypeInfo>()); + return it.value(); +} + +void insertQmlTypeInfoAlias(const PyObject *o, const QmlTypeInfoPtr &value) +{ + qmlTypeInfoHashStatic()->insert(o, value); +} + +QmlTypeInfoPtr qmlTypeInfo(const PyObject *o) +{ + auto *hash = qmlTypeInfoHashStatic(); + auto it = hash->constFind(o); + return it != hash->cend() ? it.value() : QmlTypeInfoPtr{}; +} + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug d, const QmlTypeInfo &i) +{ + QDebugStateSaver saver(d); + d.noquote(); + d.nospace(); + d << "QmlTypeInfo(" << i.flags; + if (i.foreignType) + d << ", foreignType=" << i.foreignType->tp_name; + if (i.attachedType) + d << ", attachedType=" << i.attachedType->tp_name; + if (i.extensionType) + d << ", extensionType=" << i.extensionType->tp_name; + d << ')'; + return d; +} + +QDebug operator<<(QDebug d, const QmlExtensionInfo &e) +{ + QDebugStateSaver saver(d); + d.noquote(); + d.nospace(); + d << "QmlExtensionInfo("; + if (e.factory != nullptr && e.metaObject != nullptr) + d << '"' << e.metaObject->className() << "\", factory=" + << reinterpret_cast<const void *>(e.factory); + d << ')'; + return d; +} + +#endif // QT_NO_DEBUG_STREAM + +} // namespace PySide::Qml diff --git a/sources/pyside6/libpysideqml/pysideqmltypeinfo_p.h b/sources/pyside6/libpysideqml/pysideqmltypeinfo_p.h new file mode 100644 index 000000000..112e127a7 --- /dev/null +++ b/sources/pyside6/libpysideqml/pysideqmltypeinfo_p.h @@ -0,0 +1,59 @@ +// 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 + +#ifndef PYSIDEQMLTYPEINFO_P_H +#define PYSIDEQMLTYPEINFO_P_H + +#include <sbkpython.h> + +#include <QtCore/QByteArray> +#include <QtCore/QFlags> + +#include <memory> + +QT_FORWARD_DECLARE_CLASS(QDebug) +QT_FORWARD_DECLARE_CLASS(QObject) +QT_FORWARD_DECLARE_STRUCT(QMetaObject) + +namespace PySide::Qml { + +enum class QmlTypeFlag +{ + Singleton = 0x1 +}; + +Q_DECLARE_FLAGS(QmlTypeFlags, QmlTypeFlag) +Q_DECLARE_OPERATORS_FOR_FLAGS(QmlTypeFlags) + +// Type information associated with QML type objects +struct QmlTypeInfo +{ + QmlTypeFlags flags; + PyTypeObject *foreignType = nullptr; + PyTypeObject *attachedType = nullptr; + PyTypeObject *extensionType = nullptr; +}; + +using QmlTypeInfoPtr = std::shared_ptr<QmlTypeInfo>; + +QmlTypeInfoPtr ensureQmlTypeInfo(const PyObject *o); +void insertQmlTypeInfoAlias(const PyObject *o, const QmlTypeInfoPtr &value); +QmlTypeInfoPtr qmlTypeInfo(const PyObject *o); + +// Meta Object and factory function for QmlExtended/QmlAttached +struct QmlExtensionInfo +{ + using Factory = QObject *(*)(QObject *); + + Factory factory; + const QMetaObject *metaObject; +}; + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug d, const QmlTypeInfo &); +QDebug operator<<(QDebug d, const QmlExtensionInfo &); +#endif + +} // namespace PySide::Qml + +#endif // PYSIDEQMLTYPEINFO_P_H diff --git a/sources/pyside6/libpysideqml/pysideqmluncreatable.cpp b/sources/pyside6/libpysideqml/pysideqmluncreatable.cpp new file mode 100644 index 000000000..7c0f6b8ff --- /dev/null +++ b/sources/pyside6/libpysideqml/pysideqmluncreatable.cpp @@ -0,0 +1,118 @@ +// Copyright (C) 2021 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 "pysideqmluncreatable.h" +#include <pysideclassdecorator_p.h> +#include <pysideclassinfo.h> + +#include <shiboken.h> +#include <signature.h> +#include <sbkcppstring.h> + +#include <QtCore/qbytearray.h> +#include <private/qmetaobjectbuilder_p.h> + +using namespace Qt::StringLiterals; + +class PySideQmlUncreatablePrivate : public PySide::ClassDecorator::StringDecoratorPrivate +{ +public: + PyObject *tp_call(PyObject *self, PyObject *args, PyObject * /* kw */) override; + int tp_init(PyObject *self, PyObject *args, PyObject *kwds) override; + const char *name() const override; +}; + +const char *PySideQmlUncreatablePrivate::name() const +{ + return "QmlUncreatable"; +} + +// The call operator is passed the class type and registers the reason +// in the uncreatableReasonMap() +PyObject *PySideQmlUncreatablePrivate::tp_call(PyObject *self, PyObject *args, PyObject * /* kw */) +{ + PyObject *klass = tp_call_check(args, CheckMode::WrappedType); + if (klass== nullptr) + return nullptr; + + auto *type = reinterpret_cast<PyTypeObject *>(klass); + auto *data = DecoratorPrivate::get<PySideQmlUncreatablePrivate>(self); + setUncreatableClassInfo(type, data->string()); + + Py_INCREF(klass); + return klass; +} + +int PySideQmlUncreatablePrivate::tp_init(PyObject *self, PyObject *args, PyObject * /* kwds */) +{ + int result = -1; + const auto argsCount = PyTuple_Size(args); + if (argsCount == 0) { + result = 0; // QML-generated reason + } else if (argsCount == 1) { + PyObject *arg = PyTuple_GET_ITEM(args, 0); + result = arg == Py_None + ? 0 // QML-generated reason + : convertToString(self, args); + } + + if (result != 0) { + PyErr_Format(PyExc_TypeError, + "QmlUncreatable() takes a single string argument or no argument"); + } + + return result; +} + +extern "C" { + +PyTypeObject *createPySideQmlUncreatableType(void) +{ + auto typeSlots = + PySide::ClassDecorator::Methods<PySideQmlUncreatablePrivate>::typeSlots(); + + PyType_Spec PySideQmlUncreatableType_spec = { + "2:PySide6.QtCore.qmlUncreatable", + sizeof(PySideClassDecorator), + 0, + Py_TPFLAGS_DEFAULT, + typeSlots.data() + }; + return SbkType_FromSpec(&PySideQmlUncreatableType_spec); +} + +PyTypeObject *PySideQmlUncreatable_TypeF(void) +{ + static auto *type = createPySideQmlUncreatableType(); + return type; +} + +} // extern "C" + +static const char *qmlUncreatable_SignatureStrings[] = { + "PySide6.QtQml.QmlUncreatable(self,reason:str)", + nullptr // Sentinel +}; + +void initQmlUncreatable(PyObject *module) +{ + if (InitSignatureStrings(PySideQmlUncreatable_TypeF(), qmlUncreatable_SignatureStrings) < 0) + return; + + Py_INCREF(PySideQmlUncreatable_TypeF()); + PyModule_AddObject(module, "QmlUncreatable", + reinterpret_cast<PyObject *>(PySideQmlUncreatable_TypeF())); +} + +void setUncreatableClassInfo(PyTypeObject *type, const QByteArray &reason) +{ + PySide::ClassInfo::setClassInfo(type, { + {"QML.Creatable"_ba, "false"_ba}, + {"QML.UncreatableReason"_ba, reason} }); +} + +void setUncreatableClassInfo(QMetaObjectBuilder *builder, const QByteArray &reason) +{ + builder->addClassInfo("QML.Creatable", "false"); + builder->addClassInfo("QML.UncreatableReason", reason); +} diff --git a/sources/pyside6/libpysideqml/pysideqmluncreatable.h b/sources/pyside6/libpysideqml/pysideqmluncreatable.h new file mode 100644 index 000000000..8a8adb3c8 --- /dev/null +++ b/sources/pyside6/libpysideqml/pysideqmluncreatable.h @@ -0,0 +1,26 @@ +// Copyright (C) 2021 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 + +#ifndef PYSIDEQMLUNCREATABLE_H +#define PYSIDEQMLUNCREATABLE_H + +#include <sbkpython.h> + +#include <QtCore/QByteArray> + +QT_FORWARD_DECLARE_CLASS(QMetaObjectBuilder) + +// The QmlUncreatable decorator modifies QmlElement to register an uncreatable +// type. Due to the (reverse) execution order of decorators, it needs to follow +// QmlElement. +extern "C" +{ + extern PyTypeObject *PySideQmlUncreatable_TypeF(void); +} + +void initQmlUncreatable(PyObject *module); + +void setUncreatableClassInfo(PyTypeObject *type, const QByteArray &reason); +void setUncreatableClassInfo(QMetaObjectBuilder *builder, const QByteArray &reason); + +#endif // PYSIDEQMLUNCREATABLE_H |