diff options
Diffstat (limited to 'sources/pyside6/libpyside')
56 files changed, 4570 insertions, 3674 deletions
diff --git a/sources/pyside6/libpyside/CMakeLists.txt b/sources/pyside6/libpyside/CMakeLists.txt index 50cc3108f..ebfe123dd 100644 --- a/sources/pyside6/libpyside/CMakeLists.txt +++ b/sources/pyside6/libpyside/CMakeLists.txt @@ -1,42 +1,47 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + project(libpyside) -if(${Qt${QT_MAJOR_VERSION}Qml_FOUND}) - if(NOT "${Qt${QT_MAJOR_VERSION}Qml_PRIVATE_INCLUDE_DIRS}" MATCHES "/QtQml/") - string(REPLACE "/QtCore" "/QtQml" replaceme "${Qt${QT_MAJOR_VERSION}Core_PRIVATE_INCLUDE_DIRS}") - list(APPEND Qt${QT_MAJOR_VERSION}Qml_PRIVATE_INCLUDE_DIRS ${replaceme}) - list(REMOVE_DUPLICATES Qt${QT_MAJOR_VERSION}Qml_PRIVATE_INCLUDE_DIRS) - endif() -endif() +set(libpyside_libraries Qt::Core Qt::CorePrivate) -if(${Qt${QT_MAJOR_VERSION}Quick_FOUND}) - if(NOT "${Qt${QT_MAJOR_VERSION}Quick_PRIVATE_INCLUDE_DIRS}" MATCHES "/QtQuick/") - string(REPLACE "/QtCore" "/QtQuick" replaceme "${Qt${QT_MAJOR_VERSION}Core_PRIVATE_INCLUDE_DIRS}") - list(APPEND Qt${QT_MAJOR_VERSION}Quick_PRIVATE_INCLUDE_DIRS ${Qt${QT_MAJOR_VERSION}Qml_PRIVATE_INCLUDE_DIRS}) - list(APPEND Qt${QT_MAJOR_VERSION}Quick_PRIVATE_INCLUDE_DIRS ${replaceme}) - list(REMOVE_DUPLICATES Qt${QT_MAJOR_VERSION}Quick_PRIVATE_INCLUDE_DIRS) - endif() -endif() +set(CMAKE_AUTOMOC ON) -set(QML_PRIVATE_API_SUPPORT 0) -if(Qt${QT_MAJOR_VERSION}Qml_FOUND) - # Used for registering custom QQuickItem classes defined in Python code. - set(QML_SUPPORT 1) - set(QML_INCLUDES ${Qt${QT_MAJOR_VERSION}Qml_INCLUDE_DIRS}) - set(QML_LIBRARIES ${Qt${QT_MAJOR_VERSION}Qml_LIBRARIES}) - - if(Qt${QT_MAJOR_VERSION}Qml_PRIVATE_INCLUDE_DIRS) - # Used for transforming QML exceptions into Python exceptions. - set(QML_PRIVATE_API_SUPPORT 1) - set(QML_INCLUDES ${QML_INCLUDES} ${Qt${QT_MAJOR_VERSION}Qml_PRIVATE_INCLUDE_DIRS}) - else() - message(WARNING "QML private API include files could not be found, support for catching QML exceptions inside Python code will not work.") - endif() -else() - set(QML_SUPPORT 0) - set(QML_PRIVATE_API_SUPPORT 0) - set(QML_INCLUDES "") - set(QML_LIBRARIES "") -endif() +set(libpyside_HEADERS # installed below + class_property.h + dynamicqmetaobject.h + feature_select.h + globalreceiverv2.h + pysideclassdecorator_p.h + pysideclassinfo.h + pysideclassinfo_p.h + pysidecleanup.h + pyside.h + pysideinit.h + pysidelogging_p.h + pysidemacros.h + pysidemetafunction.h + pysidemetafunction_p.h + pysidemetatype.h + pyside_numpy.h + pyside_p.h + pysideproperty.h + pysideproperty_p.h + pysideqapp.h + pysideqenum.h + pysideqhash.h + pysideqmetatype.h + pysideqobject.h + pysideqslotobject_p.h + pysidesignal.h + pysidesignal_p.h + pysideslot_p.h + pysidestaticstrings.h + pysideutils.h + pysideweakref.h + qobjectconnect.h + signalmanager.h +) set(libpyside_SRC class_property.cpp @@ -44,16 +49,20 @@ set(libpyside_SRC feature_select.cpp signalmanager.cpp globalreceiverv2.cpp + pysideclassdecorator.cpp pysideclassinfo.cpp pysideqenum.cpp + pysideqslotobject_p.cpp pysidemetafunction.cpp pysidesignal.cpp pysideslot.cpp pysideproperty.cpp - pysideqflags.cpp pysideweakref.cpp pyside.cpp + pyside_numpy.cpp pysidestaticstrings.cpp + qobjectconnect.cpp + ${libpyside_HEADERS} ) qt6_add_resources(libpyside_SRC libpyside.qrc) @@ -83,21 +92,18 @@ add_other_files(${other_files}) add_library(pyside6 SHARED ${libpyside_SRC} ${other_files}) add_library(PySide6::pyside6 ALIAS pyside6) -target_include_directories(pyside6 PRIVATE - ${QML_INCLUDES} - ${Qt${QT_MAJOR_VERSION}Core_PRIVATE_INCLUDE_DIRS} - ${Qt${QT_MAJOR_VERSION}Core_INCLUDE_DIRS} -) +#does nothing if QFP_NO_OVERRIDE_OPTIMIZATION_FLAGS (no-size-optimization) flag is not set +append_size_optimization_flags(pyside6) target_include_directories(pyside6 PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}> $<INSTALL_INTERFACE:include/PySide6> ) +target_compile_definitions(pyside6 PRIVATE -DQT_LEAN_HEADERS=1 -DQT_NO_KEYWORDS=1) + target_link_libraries(pyside6 - PRIVATE Shiboken6::libshiboken - PRIVATE ${QML_LIBRARIES} - PRIVATE ${Qt${QT_MAJOR_VERSION}Core_LIBRARIES}) + PRIVATE Shiboken6::libshiboken ${libpyside_libraries}) set_target_properties(pyside6 PROPERTIES VERSION ${BINDING_API_VERSION} @@ -105,16 +111,7 @@ set_target_properties(pyside6 PROPERTIES OUTPUT_NAME "pyside6${pyside6_SUFFIX}${SHIBOKEN_PYTHON_SHARED_LIBRARY_SUFFIX}" DEFINE_SYMBOL BUILD_LIBPYSIDE) -if(${QT_MAJOR_VERSION} GREATER_EQUAL 6) - set_property(TARGET pyside6 PROPERTY CXX_STANDARD 17) -else() - set_property(TARGET pyside6 PROPERTY CXX_STANDARD 11) -endif() - -if(QML_SUPPORT) - target_compile_definitions(pyside6 PUBLIC PYSIDE_QML_SUPPORT=1) -endif() -target_compile_definitions(pyside6 PRIVATE PYSIDE_QML_PRIVATE_API_SUPPORT=${QML_PRIVATE_API_SUPPORT}) +set_property(TARGET pyside6 PROPERTY CXX_STANDARD 17) if(PYSIDE_QT_CONF_PREFIX) set_property(SOURCE pyside.cpp @@ -127,23 +124,6 @@ endif() # install stuff # -set(libpyside_HEADERS - class_property.h - dynamicqmetaobject.h - feature_select.h - pysideclassinfo.h - pysideqenum.h - pysidemacros.h - signalmanager.h - pyside.h - pysidestaticstrings.h - pysidemetafunction.h - pysidesignal.h - pysideproperty.h - pysideqflags.h - pysideweakref.h -) - if (CMAKE_BUILD_TYPE STREQUAL "Debug") set(LIBRARY_OUTPUT_SUFFIX ${CMAKE_DEBUG_POSTFIX}) else() @@ -152,6 +132,8 @@ endif() set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D QT_NO_CAST_FROM_ASCII -D QT_NO_CAST_TO_ASCII") +qfp_strip_library("pyside6") + # create pkg-config file configure_file("${CMAKE_CURRENT_SOURCE_DIR}/pyside6.pc.in" "${CMAKE_CURRENT_BINARY_DIR}/pyside6${pyside6_SUFFIX}.pc" @ONLY) @@ -180,7 +162,7 @@ set(PYSIDE_GLUE "${CMAKE_INSTALL_PREFIX}/share/PySide6${pyside6_SUFFIX}/glue") configure_package_config_file( "${CMAKE_CURRENT_SOURCE_DIR}/PySide6Config-spec.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/install/PySide6Config${SHIBOKEN_PYTHON_CONFIG_SUFFIX}.cmake" - INSTALL_DESTINATION "${LIB_INSTALL_DIR}/cmake/PySide6-${BINDING_API_VERSION}" + INSTALL_DESTINATION "${LIB_INSTALL_DIR}/cmake/PySide6" PATH_VARS PYSIDE_PYTHONPATH PYSIDE_TYPESYSTEMS PYSIDE_GLUE ) @@ -197,16 +179,16 @@ install(TARGETS pyside6 EXPORT PySide6Targets ARCHIVE DESTINATION "${LIB_INSTALL_DIR}" RUNTIME DESTINATION bin) install(EXPORT PySide6Targets NAMESPACE PySide6:: - DESTINATION "${LIB_INSTALL_DIR}/cmake/PySide6-${BINDING_API_VERSION}") + DESTINATION "${LIB_INSTALL_DIR}/cmake/PySide6") install(FILES "${CMAKE_CURRENT_BINARY_DIR}/pyside6${pyside6_SUFFIX}.pc" DESTINATION "${LIB_INSTALL_DIR}/pkgconfig") install(FILES "${CMAKE_CURRENT_BINARY_DIR}/PySide6Config.cmake" - DESTINATION "${LIB_INSTALL_DIR}/cmake/PySide6-${BINDING_API_VERSION}") + DESTINATION "${LIB_INSTALL_DIR}/cmake/PySide6") install(FILES "${CMAKE_CURRENT_BINARY_DIR}/install/PySide6Config${SHIBOKEN_PYTHON_CONFIG_SUFFIX}.cmake" - DESTINATION "${LIB_INSTALL_DIR}/cmake/PySide6-${BINDING_API_VERSION}") + DESTINATION "${LIB_INSTALL_DIR}/cmake/PySide6") install(FILES "${CMAKE_CURRENT_BINARY_DIR}/PySide6ConfigVersion.cmake" - DESTINATION "${LIB_INSTALL_DIR}/cmake/PySide6-${BINDING_API_VERSION}") + DESTINATION "${LIB_INSTALL_DIR}/cmake/PySide6") diff --git a/sources/pyside6/libpyside/class_property.cpp b/sources/pyside6/libpyside/class_property.cpp index 35375d0f3..2bed97ef5 100644 --- a/sources/pyside6/libpyside/class_property.cpp +++ b/sources/pyside6/libpyside/class_property.cpp @@ -1,47 +1,11 @@ -/**************************************************************************** -** -** Copyright (C) 2020 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of Qt for Python. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#include "pyside.h" +// Copyright (C) 2020 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 "class_property.h" #include "pysidestaticstrings.h" #include "feature_select.h" -#include "class_property.h" +#include <pep384ext.h> #include <shiboken.h> #include <sbkstaticstrings.h> @@ -58,56 +22,97 @@ extern "C" { */ // `class_property.__get__()`: Always pass the class instead of the instance. -static PyObject *PyClassProperty_get(PyObject *self, PyObject * /*ob*/, PyObject *cls) +static PyObject *PyClassProperty_descr_get(PyObject *self, PyObject * /*ob*/, PyObject *cls) { - return PyProperty_Type.tp_descr_get(self, cls, cls); + return PepExt_Type_GetDescrGetSlot(&PyProperty_Type)(self, cls, cls); } // `class_property.__set__()`: Just like the above `__get__()`. -static int PyClassProperty_set(PyObject *self, PyObject *obj, PyObject *value) +static int PyClassProperty_descr_set(PyObject *self, PyObject *obj, PyObject *value) { PyObject *cls = PyType_Check(obj) ? obj : reinterpret_cast<PyObject *>(Py_TYPE(obj)); - return PyProperty_Type.tp_descr_set(self, cls, value); + return PepExt_Type_GetDescrSetSlot(&PyProperty_Type)(self, cls, value); +} + +// PYSIDE-2230: Why is this metaclass necessary? +// +// The problem is that the property object already exists as a Python +// object. We derive a subclass for class properties, without +// repeating everything but just by adding something to support +// the class-ness. +// +// But this Python property has as metaclass `type` which is incompatible +// now with SbkObjectType, which generates physically larger types that +// are incompatible with properties by using PEP 697. +// Adding a compatible metaclass that is unrelated to `SbkObjectType` +// is the correct solution. Re-using `SbkObjectType` was actually an abuse, +// since Python properties are in no way PySide objects. + +static PyTypeObject *createClassPropertyTypeType() +{ + PyType_Slot PyClassPropertyType_Type_slots[] = { + {Py_tp_base, static_cast<void *>(&PyType_Type)}, + {Py_tp_alloc, reinterpret_cast<void *>(PyType_GenericAlloc)}, + {Py_tp_free, reinterpret_cast<void *>(PyObject_GC_Del)}, + {0, nullptr} + }; + + PyType_Spec PyClassPropertyType_Type_spec = { + "1:Shiboken.ClassPropertyType", + 0, + 0, // sizeof(PyMemberDef), not for PyPy without a __len__ defined + Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE|Py_TPFLAGS_TYPE_SUBCLASS, + PyClassPropertyType_Type_slots, + }; + + return SbkType_FromSpec(&PyClassPropertyType_Type_spec); +} + +PyTypeObject *PyClassPropertyType_TypeF() +{ + static auto *type = createClassPropertyTypeType(); + return type; } // The property `__doc__` default does not work for class properties // because PyProperty_Type.tp_init thinks this is a subclass which needs PyObject_SetAttr. // We call `__init__` while pretending to be a PyProperty_Type instance. -static int PyClassProperty_init(PyObject *self, PyObject *args, PyObject *kwargs) +static int PyClassProperty_tp_init(PyObject *self, PyObject *args, PyObject *kwargs) { auto hold = Py_TYPE(self); - Py_TYPE(self) = &PyProperty_Type; - auto ret = PyProperty_Type.tp_init(self, args, kwargs); - Py_TYPE(self) = hold; + self->ob_type = &PyProperty_Type; + auto ret = PepExt_Type_GetInitSlot(&PyProperty_Type)(self, args, kwargs); + self->ob_type = hold; return ret; } -static PyType_Slot PyClassProperty_slots[] = { - {Py_tp_getset, nullptr}, // will be set below - {Py_tp_base, reinterpret_cast<void *>(&PyProperty_Type)}, - {Py_tp_descr_get, reinterpret_cast<void *>(PyClassProperty_get)}, - {Py_tp_descr_set, reinterpret_cast<void *>(PyClassProperty_set)}, - {Py_tp_init, reinterpret_cast<void *>(PyClassProperty_init)}, - {0, 0} -}; - -static PyType_Spec PyClassProperty_spec = { - "PySide6.PyClassProperty", - sizeof(propertyobject), - 0, - Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, - PyClassProperty_slots, -}; - -PyTypeObject *PyClassPropertyTypeF() +static PyTypeObject *createPyClassPropertyType() +{ + PyType_Slot PyClassProperty_slots[] = { + {Py_tp_getset, reinterpret_cast<void *>(PyProperty_Type.tp_getset)}, // will be set below + {Py_tp_base, reinterpret_cast<void *>(&PyProperty_Type)}, + {Py_tp_descr_get, reinterpret_cast<void *>(PyClassProperty_descr_get)}, + {Py_tp_descr_set, reinterpret_cast<void *>(PyClassProperty_descr_set)}, + {Py_tp_init, reinterpret_cast<void *>(PyClassProperty_tp_init)}, + {0, nullptr} + }; + + PyType_Spec PyClassProperty_spec = { + "2:PySide6.QtCore.PyClassProperty", + sizeof(propertyobject), + 0, + Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, + PyClassProperty_slots, + }; + + if (_PepRuntimeVersion() >= 0x030A00) + PyClassProperty_spec.basicsize = sizeof(propertyobject310); + return SbkType_FromSpecWithMeta(&PyClassProperty_spec, PyClassPropertyType_TypeF()); +} + +PyTypeObject *PyClassProperty_TypeF() { - static PyTypeObject *type = nullptr; - if (type == nullptr) { - // Provide the same `tp_getset`, which is not inherited. - PyClassProperty_slots[0].pfunc = PyProperty_Type.tp_getset; - type = reinterpret_cast<PyTypeObject *>( - PyType_FromSpec(&PyClassProperty_spec)); - } + static auto *type = createPyClassPropertyType(); return type; } @@ -129,16 +134,14 @@ static int SbkObjectType_meta_setattro(PyObject *obj, PyObject *name, PyObject * // 1. `Type.class_prop = value` --> descr_set: `Type.class_prop.__set__(value)` // 2. `Type.class_prop = other_class_prop` --> setattro: replace existing `class_prop` // 3. `Type.regular_attribute = value` --> setattro: regular attribute assignment - const auto class_prop = reinterpret_cast<PyObject *>(PyClassPropertyTypeF()); + const auto class_prop = reinterpret_cast<PyObject *>(PyClassProperty_TypeF()); const auto call_descr_set = descr && PyObject_IsInstance(descr, class_prop) && !PyObject_IsInstance(value, class_prop); if (call_descr_set) { // Call `class_property.__set__()` instead of replacing the `class_property`. - return Py_TYPE(descr)->tp_descr_set(descr, obj, value); - } else { - // Replace existing attribute. - return PyType_Type.tp_setattro(obj, name, value); - } + return PepExt_Type_GetDescrSetSlot(Py_TYPE(descr))(descr, obj, value); + } // Replace existing attribute. + return PepExt_Type_GetSetAttroSlot(&PyType_Type)(obj, name, value); } } // extern "C" @@ -146,14 +149,30 @@ static int SbkObjectType_meta_setattro(PyObject *obj, PyObject *name, PyObject * /* * These functions are added to the SbkObjectType_TypeF() dynamically. */ -namespace PySide { namespace ClassProperty { - -void init() +namespace PySide::ClassProperty { + +static const char *PyClassProperty_SignatureStrings[] = { + "PySide6.QtCore.PyClassProperty(cls," + "fget:typing.Optional[typing.Callable[[typing.Any],typing.Any]]=None," + "fset:typing.Optional[typing.Callable[[typing.Any,typing.Any],None]]=None," + "fdel:typing.Optional[typing.Callable[[typing.Any],None]]=None," + "doc:typing.Optional[str]=None)", + "PySide6.QtCore.PyClassProperty.getter(cls,fget:typing.Callable[[typing.Any],typing.Any])->PySide6.QtCore.PyClassProperty", + "PySide6.QtCore.PyClassProperty.setter(cls,fset:typing.Callable[[typing.Any,typing.Any],None])->PySide6.QtCore.PyClassProperty", + "PySide6.QtCore.PyClassProperty.deleter(cls,fdel:typing.Callable[[typing.Any],None])->PySide6.QtCore.PyClassProperty", + nullptr}; // Sentinel + +void init(PyObject *module) { PyTypeObject *type = SbkObjectType_TypeF(); type->tp_setattro = SbkObjectType_meta_setattro; - Py_TYPE(PyClassPropertyTypeF()) = type; + + if (InitSignatureStrings(PyClassProperty_TypeF(), PyClassProperty_SignatureStrings) < 0) + return; + + Py_INCREF(PyClassProperty_TypeF()); + auto classproptype = reinterpret_cast<PyObject *>(PyClassProperty_TypeF()); + PyModule_AddObject(module, "PyClassProperty", classproptype); } -} // namespace ClassProperty -} // namespace PySide +} // namespace PySide::ClassProperty diff --git a/sources/pyside6/libpyside/class_property.h b/sources/pyside6/libpyside/class_property.h index f94fdde31..f2ed29f1f 100644 --- a/sources/pyside6/libpyside/class_property.h +++ b/sources/pyside6/libpyside/class_property.h @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2020 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of Qt for Python. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2020 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 CLASS_PROPERTY_H #define CLASS_PROPERTY_H @@ -45,25 +9,35 @@ extern "C" { -typedef struct { +struct propertyobject { PyObject_HEAD PyObject *prop_get; PyObject *prop_set; PyObject *prop_del; PyObject *prop_doc; int getter_doc; -} propertyobject; +}; -PYSIDE_API PyTypeObject *PyClassPropertyTypeF(); +struct propertyobject310 { + PyObject_HEAD + PyObject *prop_get; + PyObject *prop_set; + PyObject *prop_del; + PyObject *prop_doc; + // Note: This is a problem with Limited API: We have no direct access. + // You need to pick it from runtime info. + PyObject *prop_name; + int getter_doc; +}; + +PYSIDE_API PyTypeObject *PyClassProperty_TypeF(); } // extern "C" -namespace PySide { -namespace ClassProperty { +namespace PySide::ClassProperty { -PYSIDE_API void init(); +PYSIDE_API void init(PyObject *module); -} // namespace ClassProperty -} // namespace PySide +} // namespace PySide::ClassProperty #endif // CLASS_PROPERTY_H diff --git a/sources/pyside6/libpyside/dynamicqmetaobject.cpp b/sources/pyside6/libpyside/dynamicqmetaobject.cpp index 39be38b29..048001f81 100644 --- a/sources/pyside6/libpyside/dynamicqmetaobject.cpp +++ b/sources/pyside6/libpyside/dynamicqmetaobject.cpp @@ -1,50 +1,16 @@ -/**************************************************************************** -** -** Copyright (C) 2017 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of Qt for Python. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2017 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 "dynamicqmetaobject.h" -#include "dynamicqmetaobject_p.h" +#include "pysideqobject.h" #include "pysidesignal.h" #include "pysidesignal_p.h" #include "pysideproperty.h" #include "pysideproperty_p.h" #include "pysideslot_p.h" #include "pysideqenum.h" +#include "pyside_p.h" +#include "pysidestaticstrings.h" #include <shiboken.h> @@ -58,6 +24,8 @@ #include <cstring> #include <vector> +using namespace Qt::StringLiterals; + using namespace PySide; // MetaObjectBuilder: Provides the QMetaObject's returned by @@ -85,7 +53,8 @@ public: const QByteArray &signature) const; int indexOfProperty(const QByteArray &name) const; int addSlot(const QByteArray &signature); - int addSlot(const QByteArray &signature, const QByteArray &type); + int addSlot(const QByteArray &signature, const QByteArray &type, + const QByteArray &tag = {}); int addSignal(const QByteArray &signature); void removeMethod(QMetaMethod::MethodType mtype, int index); int getPropertyNotifyId(PySideProperty *property) const; @@ -102,6 +71,10 @@ public: const QMetaObject *m_baseObject = nullptr; MetaObjects m_cachedMetaObjects; bool m_dirty = true; + +private: + QMetaPropertyBuilder + createProperty(PySideProperty *property, const QByteArray &propertyName); }; QMetaObjectBuilder *MetaObjectBuilderPrivate::ensureBuilder() @@ -212,8 +185,8 @@ int MetaObjectBuilder::indexOfProperty(const QByteArray &name) const static bool checkMethodSignature(const QByteArray &signature) { // Common mistake not to add parentheses to the signature. - const int openParen = signature.indexOf('('); - const int closingParen = signature.lastIndexOf(')'); + const auto openParen = signature.indexOf('('); + const auto closingParen = signature.lastIndexOf(')'); const bool ok = openParen != -1 && closingParen != -1 && openParen < closingParen; if (!ok) { const QByteArray message = @@ -239,13 +212,17 @@ int MetaObjectBuilder::addSlot(const char *signature) } int MetaObjectBuilderPrivate::addSlot(const QByteArray &signature, - const QByteArray &type) + const QByteArray &type, + const QByteArray &tag) { if (!checkMethodSignature(signature)) return -1; m_dirty = true; QMetaMethodBuilder methodBuilder = ensureBuilder()->addSlot(signature); - methodBuilder.setReturnType(type); + if (!type.isEmpty() && type != "void"_ba) + methodBuilder.setReturnType(type); + if (!tag.isEmpty()) + methodBuilder.setTag(tag); return m_baseObject->methodCount() + methodBuilder.index(); } @@ -300,6 +277,35 @@ int MetaObjectBuilderPrivate::getPropertyNotifyId(PySideProperty *property) cons return notifyId; } +QMetaPropertyBuilder + MetaObjectBuilderPrivate::createProperty(PySideProperty *property, + const QByteArray &propertyName) +{ + int propertyNotifyId = getPropertyNotifyId(property); + if (propertyNotifyId >= 0) + propertyNotifyId -= m_baseObject->methodCount(); + + // For QObject-derived Python types, retrieve the meta type registered + // by name from the qmlRegisterType, if there is one. This is required for + // grouped QML properties to work. + auto *builder = ensureBuilder(); + auto *typeObject = Property::getTypeObject(property); + if (typeObject != nullptr && PyType_Check(typeObject)) { + auto *pyTypeObject = reinterpret_cast<PyTypeObject *>(typeObject); + if (qstrncmp(pyTypeObject->tp_name, "PySide", 6) != 0 + && PySide::isQObjectDerived(pyTypeObject, false)) { + const QByteArray pyType(pyTypeObject->tp_name); + const auto metaType = QMetaType::fromName(pyType + '*'); + if (metaType.isValid()) { + return builder->addProperty(propertyName, pyType, + metaType, propertyNotifyId); + } + } + } + return builder->addProperty(propertyName, property->d->typeName, + propertyNotifyId); +} + int MetaObjectBuilderPrivate::addProperty(const QByteArray &propertyName, PyObject *data) { @@ -307,13 +313,9 @@ int MetaObjectBuilderPrivate::addProperty(const QByteArray &propertyName, if (index != -1) return index; - PySideProperty *property = reinterpret_cast<PySideProperty *>(data); - int propertyNotifyId = getPropertyNotifyId(property); - if (propertyNotifyId >= 0) - propertyNotifyId -= m_baseObject->methodCount(); - auto newProperty = - ensureBuilder()->addProperty(propertyName, property->d->typeName, - propertyNotifyId); + auto *property = reinterpret_cast<PySideProperty *>(data); + auto newProperty = createProperty(property, propertyName); + // Adding property attributes newProperty.setReadable(PySide::Property::isReadable(property)); newProperty.setWritable(PySide::Property::isWritable(property)); @@ -455,6 +457,107 @@ const QMetaObject *MetaObjectBuilder::update() return m_d->update(); } +static void formatEnum(QTextStream &str, const QMetaEnum &e) +{ + str << '"' << e.name() << "\" {"; + for (int k = 0, cnt = e.keyCount(); k < cnt; ++k) { + if (k) + str << ", "; + str << e.key(k); + } + str << "}"; +} + +static void formatProperty(QTextStream &str, const QMetaProperty &p) +{ + str << '"' << p.name() << "\", " << p.typeName(); + if (p.isWritable()) + str << " [writeable]"; + if (p.isResettable()) + str << " [resettable]"; + if (p.isConstant()) + str << " [constant]"; + if (p.isFinal()) + str << " [final]"; + if (p.isDesignable()) + str << " [designable]"; + auto sig = p.notifySignal(); + if (sig.isValid()) + str << ", notify=" << sig.name(); +} + +static void formatMethod(QTextStream &str, const QMetaMethod &m) +{ + str << "type="; + switch (m.methodType()) { + case QMetaMethod::Method: + str << "Method"; + break; + case QMetaMethod::Signal: + str << "Signal"; + break; + case QMetaMethod::Slot: + str << "Slot"; + break; + case QMetaMethod::Constructor: + str << "Constructor"; + break; + } + + str << ", signature=" + << m.methodSignature(); + const QByteArrayList parms = m.parameterTypes(); + if (!parms.isEmpty()) + str << ", parameters=" << parms.join(", "); +} + +QString MetaObjectBuilder::formatMetaObject(const QMetaObject *metaObject) +{ + QString result; + QTextStream str(&result); + str << "PySide" << QT_VERSION_MAJOR << ".QtCore.QMetaObject(\"" + << metaObject->className() << '"'; + if (auto *s = metaObject->superClass()) + str << " inherits \"" << s->className() << '"'; + str << ":\n"; + + int offset = metaObject->enumeratorOffset(); + int count = metaObject->enumeratorCount(); + if (offset < count) { + str << "Enumerators:\n"; + for (int e = offset; e < count; ++e) { + str << " #" << e << ' '; + formatEnum(str, metaObject->enumerator(e)); + str << '\n'; + } + } + + offset = metaObject->propertyOffset(); + count = metaObject->propertyCount(); + if (offset < count) { + str << "Properties:\n"; + for (int p = offset; p < count; ++p) { + str << " #" << p << ' '; + formatProperty(str, metaObject->property(p)); + str << '\n'; + } + } + + offset = metaObject->methodOffset(); + count = metaObject->methodCount(); + if (offset < count) { + str << "Methods:\n"; + for (int m = offset; m < count; ++m) { + str << " #" << m << ' '; + formatMethod(str, metaObject->method(m)); + str << '\n'; + } + } + + str << ')'; + return result; +} + using namespace Shiboken; void MetaObjectBuilderPrivate::parsePythonType(PyTypeObject *type) @@ -466,19 +569,18 @@ void MetaObjectBuilderPrivate::parsePythonType(PyTypeObject *type) // existing connections. const PyObject *mro = type->tp_mro; const Py_ssize_t basesCount = PyTuple_GET_SIZE(mro); - PyTypeObject *qObjectType = Conversions::getPythonTypeObject("QObject*"); std::vector<PyTypeObject *> basesToCheck; // Prepend the actual type that we are parsing. basesToCheck.reserve(1u + basesCount); basesToCheck.push_back(type); - auto sbkObjTypeF = reinterpret_cast<PyTypeObject *>(SbkObject_TypeF()); + auto sbkObjTypeF = SbkObject_TypeF(); auto baseObjType = reinterpret_cast<PyTypeObject *>(&PyBaseObject_Type); for (Py_ssize_t i = 0; i < basesCount; ++i) { auto baseType = reinterpret_cast<PyTypeObject *>(PyTuple_GET_ITEM(mro, i)); if (baseType != sbkObjTypeF && baseType != baseObjType - && PyType_IsSubtype(baseType, qObjectType) == 0) { + && !PySide::isQObjectDerived(baseType, false)) { basesToCheck.push_back(baseType); } } @@ -487,7 +589,8 @@ void MetaObjectBuilderPrivate::parsePythonType(PyTypeObject *type) // Leave the properties to be registered after signals because they may depend on // notify signals. for (PyTypeObject *baseType : basesToCheck) { - PyObject *attrs = baseType->tp_dict; + AutoDecRef tpDict(PepType_GetDict(baseType)); + PyObject *attrs = tpDict.object(); PyObject *key = nullptr; PyObject *value = nullptr; Py_ssize_t pos = 0; @@ -495,64 +598,51 @@ void MetaObjectBuilderPrivate::parsePythonType(PyTypeObject *type) while (PyDict_Next(attrs, &pos, &key, &value)) { if (Signal::checkType(value)) { // Register signals. - auto data = reinterpret_cast<PySideSignal *>(value); - if (data->data->signalName.isEmpty()) - data->data->signalName = String::toCString(key); - for (const auto &s : data->data->signatures) { - const auto sig = data->data->signalName + '(' + s.signature + ')'; + auto *data = reinterpret_cast<PySideSignal *>(value)->data; + if (data->signalName.isEmpty()) + data->signalName = String::toCString(key); + for (const auto &s : data->signatures) { + const auto sig = data->signalName + '(' + s.signature + ')'; if (m_baseObject->indexOfSignal(sig) == -1) { // Registering the parameterNames to the QMetaObject (PYSIDE-634) // from: // Signal(..., arguments=['...', ...] // the arguments are now on data-data->signalArguments - if (!data->data->signalArguments->isEmpty()) { - m_builder->addSignal(sig).setParameterNames(*data->data->signalArguments); - } else { - m_builder->addSignal(sig); - } + auto builder = m_builder->addSignal(sig); + if (!data->signalArguments.isEmpty()) + builder.setParameterNames(data->signalArguments); } } } } } - AutoDecRef slotAttrName(String::fromCString(PYSIDE_SLOT_LIST_ATTR)); + PyObject *slotAttrName = PySide::PySideMagicName::slot_list_attr(); // PYSIDE-315: Now take care of the rest. // Signals and slots should be separated, unless the types are modified, later. // We check for this using "is_sorted()". Sorting no longer happens at all. for (PyTypeObject *baseType : basesToCheck) { - PyObject *attrs = baseType->tp_dict; + AutoDecRef tpDict(PepType_GetDict(baseType)); + PyObject *attrs = tpDict.object(); PyObject *key = nullptr; PyObject *value = nullptr; Py_ssize_t pos = 0; while (PyDict_Next(attrs, &pos, &key, &value)) { if (Property::checkType(value)) { - const int index = m_baseObject->indexOfProperty(String::toCString(key)); + const QByteArray name = String::toCString(key); + const int index = m_baseObject->indexOfProperty(name); if (index == -1) - addProperty(String::toCString(key), value); - } else if (Py_TYPE(value)->tp_call != nullptr) { + addProperty(name, value); + } else if (PepType_GetSlot(Py_TYPE(value), Py_tp_call) != nullptr) { // PYSIDE-198: PyFunction_Check does not work with Nuitka. // Register slots. if (PyObject_HasAttr(value, slotAttrName)) { - PyObject *signatureList = PyObject_GetAttr(value, slotAttrName); - for (Py_ssize_t i = 0, i_max = PyList_Size(signatureList); i < i_max; ++i) { - PyObject *pySignature = PyList_GET_ITEM(signatureList, i); - QByteArray signature(String::toCString(pySignature)); - // Split the slot type and its signature. - QByteArray type; - const int spacePos = signature.indexOf(' '); - if (spacePos != -1) { - type = signature.left(spacePos); - signature.remove(0, spacePos + 1); - } - const int index = m_baseObject->indexOfSlot(signature); - if (index == -1) { - if (type.isEmpty() || type == "void") - addSlot(signature); - else - addSlot(signature, type); - } + auto *capsule = PyObject_GetAttr(value, slotAttrName); + const auto *entryList = PySide::Slot::dataListFromCapsule(capsule); + for (const auto &e : *entryList) { + if (m_baseObject->indexOfSlot(e.signature) == -1) + addSlot(e.signature, e.resultType, e.tag); } } } @@ -570,16 +660,15 @@ void MetaObjectBuilderPrivate::parsePythonType(PyTypeObject *type) AutoDecRef items(PyMapping_Items(members)); Py_ssize_t nr_items = PySequence_Length(items); - QList<QPair<QByteArray, int> > entries; + QList<std::pair<QByteArray, int> > entries; for (Py_ssize_t idx = 0; idx < nr_items; ++idx) { AutoDecRef item(PySequence_GetItem(items, idx)); AutoDecRef key(PySequence_GetItem(item, 0)); AutoDecRef member(PySequence_GetItem(item, 1)); AutoDecRef value(PyObject_GetAttr(member, Shiboken::PyName::value())); auto ckey = String::toCString(key); - auto ivalue = PyInt_AsSsize_t(value); // int/long cheating - auto thing = QPair<QByteArray, int>(ckey, int(ivalue)); - entries.push_back(thing); + auto ivalue = PyLong_AsSsize_t(value); + entries.push_back(std::make_pair(ckey, int(ivalue))); } addEnumerator(name, isFlag, true, entries); } diff --git a/sources/pyside6/libpyside/dynamicqmetaobject.h b/sources/pyside6/libpyside/dynamicqmetaobject.h index 6e7a079a3..dd33f65f7 100644 --- a/sources/pyside6/libpyside/dynamicqmetaobject.h +++ b/sources/pyside6/libpyside/dynamicqmetaobject.h @@ -1,50 +1,17 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of Qt for Python. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// 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 DYNAMICQMETAOBJECT_H #define DYNAMICQMETAOBJECT_H #include <sbkpython.h> +#include <pysidemacros.h> #include <QtCore/QMetaObject> #include <QtCore/QMetaMethod> +#include <utility> + class MetaObjectBuilderPrivate; namespace PySide @@ -52,9 +19,9 @@ namespace PySide class MetaObjectBuilder { - Q_DISABLE_COPY(MetaObjectBuilder) + Q_DISABLE_COPY_MOVE(MetaObjectBuilder) public: - using EnumValue = QPair<QByteArray, int>; + using EnumValue = std::pair<QByteArray, int>; using EnumValues = QList<EnumValue>; MetaObjectBuilder(const char *className, const QMetaObject *metaObject); @@ -77,6 +44,8 @@ public: const QMetaObject *update(); + PYSIDE_API static QString formatMetaObject(const QMetaObject *metaObject); + private: MetaObjectBuilderPrivate *m_d; }; diff --git a/sources/pyside6/libpyside/dynamicqmetaobject_p.h b/sources/pyside6/libpyside/dynamicqmetaobject_p.h deleted file mode 100644 index 11211ca11..000000000 --- a/sources/pyside6/libpyside/dynamicqmetaobject_p.h +++ /dev/null @@ -1,108 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of Qt for Python. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#ifndef DYNAMICMETAPROPERTY_P_H -#define DYNAMICMETAPROPERTY_P_H - -#include <sbkpython.h> - -#include <QtCore/QByteArray> -#include <QtCore/QMetaMethod> - -struct PySideProperty; -namespace PySide -{ - class MethodData - { - public: - MethodData(); - /** - * \param signature method signature - * \param type method return type - */ - MethodData(QMetaMethod::MethodType mtype, - const QByteArray &signature, - const QByteArray &rtype = QByteArray("void")); - void clear(); - bool isValid() const; - const QByteArray &signature() const { return m_signature; } - const QByteArray &returnType() const { return m_rtype; } - QMetaMethod::MethodType methodType() const { return m_mtype; } - //Qt5 moc: now we have to store method parameter names, count, type - QList<QByteArray> parameterTypes() const; - int parameterCount() const; - QByteArray name() const; - bool operator==(const MethodData &other) const; - - private: - QByteArray m_signature; - QByteArray m_rtype; - QMetaMethod::MethodType m_mtype; - static const QByteArray m_emptySig; - }; - - class PropertyData - { - public: - PropertyData(); - PropertyData(const char *name, int cachedNotifyId = 0, - PySideProperty *data = nullptr); - const QByteArray &name() const { return m_name; } - PySideProperty *data() const { return m_data; } - QByteArray type() const; - uint flags() const; - bool isValid() const; - int cachedNotifyId() const; - bool operator==(const PropertyData &other) const; - bool operator==(const char *name) const; - - private: - QByteArray m_name; - int m_cachedNotifyId; - PySideProperty *m_data; - }; - -inline bool MethodData::operator==(const MethodData &other) const -{ - return m_mtype == other.methodType() && m_signature == other.signature(); -} - -} - -#endif diff --git a/sources/pyside6/libpyside/feature_select.cpp b/sources/pyside6/libpyside/feature_select.cpp index 443296f0e..cfd465267 100644 --- a/sources/pyside6/libpyside/feature_select.cpp +++ b/sources/pyside6/libpyside/feature_select.cpp @@ -1,48 +1,17 @@ -/**************************************************************************** -** -** Copyright (C) 2020 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of Qt for Python. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2020 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 "feature_select.h" -#include "pyside.h" +#include "pysidecleanup.h" +#include "pysideqobject.h" #include "pysidestaticstrings.h" #include "class_property.h" #include <shiboken.h> +#include <sbkfeature_base.h> +#include <signature_p.h> + +#include <QtCore/QStringList> ////////////////////////////////////////////////////////////////////////////// // @@ -120,47 +89,14 @@ This is everything that the following code does. *****************************************************************************/ -namespace PySide { namespace Feature { +namespace PySide::Feature { using namespace Shiboken; -typedef bool(*FeatureProc)(PyTypeObject *type, PyObject *prev_dict, int id); +using FeatureProc = bool(*)(PyTypeObject *type, PyObject *prev_dict, int id); static FeatureProc *featurePointer = nullptr; -static PyObject *cached_globals = nullptr; -static PyObject *last_select_id = nullptr; - -static PyObject *_fast_id_array[1 + 256] = {}; -// this will point to element 1 to allow indexing from -1 -static PyObject **fast_id_array; - -static inline PyObject *getFeatureSelectId() -{ - static PyObject *undef = fast_id_array[-1]; - static PyObject *feature_dict = GetFeatureDict(); - // these things are all borrowed - PyObject *globals = PyEval_GetGlobals(); - if (globals == nullptr - || globals == cached_globals) - return last_select_id; - - PyObject *modname = PyDict_GetItem(globals, PyMagicName::name()); - if (modname == nullptr) - return last_select_id; - - PyObject *select_id = PyDict_GetItem(feature_dict, modname); - if (select_id == nullptr - || !PyInt_Check(select_id) // int/long cheating - || select_id == undef) - return last_select_id; - - cached_globals = globals; - last_select_id = select_id; - assert(PyInt_AsSsize_t(select_id) >= 0); - return select_id; -} - // Create a derived dict class static PyTypeObject * createDerivedDictType() @@ -171,7 +107,7 @@ createDerivedDictType() PyObject *ChameleonDict = PepRun_GetResult(R"CPP(if True: class ChameleonDict(dict): - __slots__ = ("dict_ring", "select_id") + __slots__ = ("dict_ring", "select_id", "orig_dict") result = ChameleonDict @@ -193,43 +129,30 @@ static void ensureNewDictType() static inline PyObject *nextInCircle(PyObject *dict) { // returns a borrowed ref - AutoDecRef next_dict(PyObject_GetAttr(dict, PyName::dict_ring())); + AutoDecRef next_dict(PyObject_GetAttr(dict, PySideName::dict_ring())); return next_dict; } static inline void setNextDict(PyObject *dict, PyObject *next_dict) { - PyObject_SetAttr(dict, PyName::dict_ring(), next_dict); -} - -static inline void setSelectId(PyObject *dict, PyObject *select_id) -{ - PyObject_SetAttr(dict, PyName::select_id(), select_id); -} - -static inline PyObject *getSelectId(PyObject *dict) -{ - auto select_id = PyObject_GetAttr(dict, PyName::select_id()); - return select_id; + PyObject_SetAttr(dict, PySideName::dict_ring(), next_dict); } -static inline void setCurrentSelectId(PyTypeObject *type, PyObject *select_id) +static inline void setSelectId(PyObject *dict, int select_id) { - SbkObjectType_SetReserved(type, PyInt_AsSsize_t(select_id)); // int/long cheating + PyObject_SetAttr(dict, PySideName::select_id(), PyLong_FromLong(select_id)); } -static inline void setCurrentSelectId(PyTypeObject *type, int id) +static inline int getSelectId(PyObject *dict) { - SbkObjectType_SetReserved(type, id); -} - -static inline PyObject *getCurrentSelectId(PyTypeObject *type) -{ - int id = SbkObjectType_GetReserved(type); - // This can be too early. - if (id < 0) - id = 0; - return fast_id_array[id]; + auto *py_select_id = PyObject_GetAttr(dict, PyName::select_id()); + if (py_select_id == nullptr) { + PyErr_Clear(); + return 0; + } + int ret = PyLong_AsLong(py_select_id); + Py_DECREF(py_select_id); + return ret; } static bool replaceClassDict(PyTypeObject *type) @@ -239,32 +162,32 @@ static bool replaceClassDict(PyTypeObject *type) * This is mandatory for all type dicts when they are touched. */ ensureNewDictType(); - PyObject *dict = type->tp_dict; - auto ob_ndt = reinterpret_cast<PyObject *>(new_dict_type); - PyObject *new_dict = PyObject_CallObject(ob_ndt, nullptr); + AutoDecRef dict(PepType_GetDict(type)); + auto *ob_ndt = reinterpret_cast<PyObject *>(new_dict_type); + auto *new_dict = PyObject_CallObject(ob_ndt, nullptr); if (new_dict == nullptr || PyDict_Update(new_dict, dict) < 0) return false; // Insert the default id. Cannot fail for small numbers. - AutoDecRef select_id(PyInt_FromLong(0)); - setSelectId(new_dict, select_id); + setSelectId(new_dict, 0); // insert the dict into itself as ring setNextDict(new_dict, new_dict); // We have now an exact copy of the dict with a new type. - // Replace `__dict__` which usually has refcount 1 (but see cyclic_test.py) - Py_DECREF(type->tp_dict); - type->tp_dict = new_dict; + PepType_SetDict(type, new_dict); + // PYSIDE-2404: Retain the original dict for easy late init. + PyObject_SetAttr(new_dict, PySideName::orig_dict(), dict); return true; } -static bool addNewDict(PyTypeObject *type, PyObject *select_id) +static bool addNewDict(PyTypeObject *type, int select_id) { /* * Add a new dict to the ring and set it as `type->tp_dict`. * A 'false' return is fatal. */ - auto dict = type->tp_dict; - auto ob_ndt = reinterpret_cast<PyObject *>(new_dict_type); - auto new_dict = PyObject_CallObject(ob_ndt, nullptr); + AutoDecRef dict(PepType_GetDict(type)); + AutoDecRef orig_dict(PyObject_GetAttr(dict, PySideName::orig_dict())); + auto *ob_ndt = reinterpret_cast<PyObject *>(new_dict_type); + auto *new_dict = PyObject_CallObject(ob_ndt, nullptr); if (new_dict == nullptr) return false; setSelectId(new_dict, select_id); @@ -272,33 +195,35 @@ static bool addNewDict(PyTypeObject *type, PyObject *select_id) auto next_dict = nextInCircle(dict); setNextDict(dict, new_dict); setNextDict(new_dict, next_dict); - type->tp_dict = new_dict; + PepType_SetDict(type, new_dict); + // PYSIDE-2404: Retain the original dict for easy late init. + PyObject_SetAttr(new_dict, PySideName::orig_dict(), orig_dict); return true; } -static bool moveToFeatureSet(PyTypeObject *type, PyObject *select_id) +static inline bool moveToFeatureSet(PyTypeObject *type, int select_id) { /* * Rotate the ring to the given `select_id` and return `true`. * If not found, stay at the current position and return `false`. */ - auto initial_dict = type->tp_dict; - auto dict = initial_dict; + AutoDecRef tpDict(PepType_GetDict(type)); + auto *initial_dict = tpDict.object(); + auto *dict = initial_dict; do { - dict = nextInCircle(dict); - AutoDecRef current_id(getSelectId(dict)); + int current_id = getSelectId(dict); // This works because small numbers are singleton objects. if (current_id == select_id) { - type->tp_dict = dict; - setCurrentSelectId(type, select_id); + PepType_SetDict(type, dict); return true; } + dict = nextInCircle(dict); } while (dict != initial_dict); - type->tp_dict = initial_dict; + PepType_SetDict(type, initial_dict); return false; } -static bool createNewFeatureSet(PyTypeObject *type, PyObject *select_id) +static bool createNewFeatureSet(PyTypeObject *type, int select_id) { /* * Create a new feature set. @@ -308,37 +233,29 @@ static bool createNewFeatureSet(PyTypeObject *type, PyObject *select_id) * content in `prev_dict`. It is responsible of filling `type->tp_dict` * with modified content. */ - static auto small_1 = PyInt_FromLong(255); - Q_UNUSED(small_1); - static auto small_2 = PyInt_FromLong(255); - Q_UNUSED(small_2); - // make sure that small integers are cached - assert(small_1 != nullptr && small_1 == small_2); - - static auto zero = fast_id_array[0]; - bool ok = moveToFeatureSet(type, zero); + + bool ok = moveToFeatureSet(type, 0); Q_UNUSED(ok); assert(ok); - AutoDecRef prev_dict(type->tp_dict); - Py_INCREF(prev_dict); // keep the first ref unchanged + AutoDecRef prev_dict(PepType_GetDict(type)); if (!addNewDict(type, select_id)) return false; - auto id = PyInt_AsSsize_t(select_id); // int/long cheating + int id = select_id; if (id == -1) return false; - setCurrentSelectId(type, id); FeatureProc *proc = featurePointer; for (int idx = id; *proc != nullptr; ++proc, idx >>= 1) { if (idx & 1) { // clear the tp_dict that will get new content - PyDict_Clear(type->tp_dict); + AutoDecRef tpDict(PepType_GetDict(type)); + PyDict_Clear(tpDict); // let the proc re-fill the tp_dict if (!(*proc)(type, prev_dict, id)) return false; // if there is still a step, prepare `prev_dict` if (idx >> 1) { - prev_dict.reset(PyDict_Copy(type->tp_dict)); + prev_dict.reset(PyDict_Copy(tpDict.object())); if (prev_dict.isNull()) return false; } @@ -347,30 +264,59 @@ static bool createNewFeatureSet(PyTypeObject *type, PyObject *select_id) return true; } -static bool SelectFeatureSetSubtype(PyTypeObject *type, PyObject *select_id) +static inline void SelectFeatureSetSubtype(PyTypeObject *type, int select_id) { /* * This is the selector for one sublass. We need to call this for * every subclass until no more subclasses or reaching the wanted id. */ - if (Py_TYPE(type->tp_dict) == Py_TYPE(PyType_Type.tp_dict)) { + static const auto *pyTypeType_tp_dict = PepType_GetDict(&PyType_Type); + AutoDecRef tpDict(PepType_GetDict(type)); + if (Py_TYPE(tpDict.object()) == Py_TYPE(pyTypeType_tp_dict)) { // On first touch, we initialize the dynamic naming. // The dict type will be replaced after the first call. if (!replaceClassDict(type)) { Py_FatalError("failed to replace class dict!"); - return false; + return; } } if (!moveToFeatureSet(type, select_id)) { if (!createNewFeatureSet(type, select_id)) { Py_FatalError("failed to create a new feature set!"); - return false; + return; } } - return true; + } + +static PyObject *cached_globals{}; +static int last_select_id{}; + +static inline int getFeatureSelectId() +{ + static auto *undef = PyLong_FromLong(-1); + static auto *feature_dict = GetFeatureDict(); + // these things are all borrowed + auto *globals = PyEval_GetGlobals(); + if (globals == nullptr + || globals == cached_globals) + return last_select_id; + + auto *modname = PyDict_GetItem(globals, PyMagicName::name()); + if (modname == nullptr) + return last_select_id; + + auto *py_select_id = PyDict_GetItem(feature_dict, modname); + if (py_select_id == nullptr + || !PyLong_Check(py_select_id) + || py_select_id == undef) + return last_select_id; + + cached_globals = globals; + last_select_id = PyLong_AsLong(py_select_id) & 0xff; + return last_select_id; } -static inline PyObject *SelectFeatureSet(PyTypeObject *type) +static inline void SelectFeatureSet(PyTypeObject *type) { /* * This is the main function of the module. @@ -380,31 +326,35 @@ static inline PyObject *SelectFeatureSet(PyTypeObject *type) * Generated functions call this directly. * Shiboken will assign it via a public hook of `basewrapper.cpp`. */ - if (Py_TYPE(type->tp_dict) == Py_TYPE(PyType_Type.tp_dict)) { + static const auto *pyTypeType_tp_dict = PepType_GetDict(&PyType_Type); + AutoDecRef tpDict(PepType_GetDict(type)); + if (Py_TYPE(tpDict.object()) == Py_TYPE(pyTypeType_tp_dict)) { // We initialize the dynamic features by using our own dict type. - if (!replaceClassDict(type)) - return nullptr; - } - PyObject *select_id = getFeatureSelectId(); // borrowed - PyObject *current_id = getCurrentSelectId(type); // borrowed - static PyObject *undef = fast_id_array[-1]; - - // PYSIDE-1019: During import PepType_SOTP is still zero. - if (current_id == undef) - current_id = select_id = fast_id_array[0]; - - if (select_id != current_id) { - PyObject *mro = type->tp_mro; - Py_ssize_t idx, n = PyTuple_GET_SIZE(mro); - // We leave 'Shiboken.Object' and 'object' alone, therefore "n - 2". - for (idx = 0; idx < n - 2; idx++) { - auto *sub_type = reinterpret_cast<PyTypeObject *>(PyTuple_GET_ITEM(mro, idx)); - // When any subtype is already resolved (false), we can stop. - if (!SelectFeatureSetSubtype(sub_type, select_id)) - break; + if (!replaceClassDict(type)) { + Py_FatalError("failed to replace class dict!"); + return; } } - return type->tp_dict; + + int select_id = getFeatureSelectId(); + static int last_select_id{}; + static PyTypeObject *last_type{}; + + // PYSIDE-2029: Implement a very simple but effective cache that cannot fail. + if (type == last_type && select_id == last_select_id) + return; + last_type = type; + last_select_id = select_id; + + auto *mro = type->tp_mro; + Py_ssize_t idx, n = PyTuple_GET_SIZE(mro); + // We leave 'Shiboken.Object' and 'object' alone, therefore "n - 2". + for (idx = 0; idx < n - 2; idx++) { + auto *sub_type = reinterpret_cast<PyTypeObject *>(PyTuple_GET_ITEM(mro, idx)); + SelectFeatureSetSubtype(sub_type, select_id); + } + // PYSIDE-1436: Clear all caches for the type and subtypes. + PyType_Modified(type); } // For cppgenerator: @@ -412,15 +362,14 @@ void Select(PyObject *obj) { if (featurePointer == nullptr) return; - auto type = Py_TYPE(obj); - type->tp_dict = SelectFeatureSet(type); + auto *type = Py_TYPE(obj); + SelectFeatureSet(type); } -PyObject *Select(PyTypeObject *type) +void Select(PyTypeObject *type) { if (featurePointer != nullptr) - type->tp_dict = SelectFeatureSet(type); - return type->tp_dict; + SelectFeatureSet(type); } static bool feature_01_addLowerNames(PyTypeObject *type, PyObject *prev_dict, int id); @@ -444,30 +393,25 @@ static FeatureProc featureProcArray[] = { nullptr }; -void finalize() -{ - for (int idx = -1; idx < 256; ++idx) - Py_DECREF(fast_id_array[idx]); -} - static bool patch_property_impl(); static bool is_initialized = false; +static void featureEnableCallback(bool enable) +{ + featurePointer = enable ? featureProcArray : nullptr; +} + void init() { // This function can be called multiple times. if (!is_initialized) { - fast_id_array = &_fast_id_array[1]; - for (int idx = -1; idx < 256; ++idx) - fast_id_array[idx] = PyInt_FromLong(idx); - last_select_id = fast_id_array[0]; featurePointer = featureProcArray; initSelectableFeature(SelectFeatureSet); - registerCleanupFunction(finalize); + setSelectableFeatureCallback(featureEnableCallback); patch_property_impl(); - PySide::ClassProperty::init(); is_initialized = true; } + last_select_id = 0; // Reset the cache. This is called at any "from __feature__ import". cached_globals = nullptr; } @@ -499,7 +443,7 @@ static PyObject *methodWithNewName(PyTypeObject *type, /* * Create a method with a lower case name. */ - auto obtype = reinterpret_cast<PyObject *>(type); + auto *obtype = reinterpret_cast<PyObject *>(type); int len = strlen(new_name); auto name = new char[len + 1]; strcpy(name, new_name); @@ -521,12 +465,20 @@ static PyObject *methodWithNewName(PyTypeObject *type, return descr; } -static bool feature_01_addLowerNames(PyTypeObject *type, PyObject *prev_dict, int id) +static bool feature_01_addLowerNames(PyTypeObject *type, PyObject *prev_dict, int /* id */) { + PyMethodDef *meth = type->tp_methods; + AutoDecRef tpDict(PepType_GetDict(type)); + PyObject *lower_dict = tpDict.object(); + + // PYSIDE-1702: A user-defined class in Python has no internal method list. + // We are not going to change anything. + if (!meth) + return PyDict_Update(lower_dict, prev_dict) >= 0; + /* * Add objects with lower names to `type->tp_dict` from 'prev_dict`. */ - PyObject *lower_dict = type->tp_dict; PyObject *key, *value; Py_ssize_t pos = 0; @@ -539,11 +491,9 @@ static bool feature_01_addLowerNames(PyTypeObject *type, PyObject *prev_dict, in continue; } } + // Then we walk over the tp_methods to get all methods and insert // them with changed names. - PyMethodDef *meth = type->tp_methods; - if (!meth) - return true; for (; meth != nullptr && meth->ml_name != nullptr; ++meth) { const char *name = String::toCString(String::getSnakeCaseName(meth->ml_name, true)); @@ -566,20 +516,20 @@ static bool feature_01_addLowerNames(PyTypeObject *type, PyObject *prev_dict, in // This is the Python 2 version for inspection of m_ml, only. // The actual Python 3 version is larget. -typedef struct { +struct PyCFunctionObject { PyObject_HEAD PyMethodDef *m_ml; /* Description of the C function to call */ PyObject *m_self; /* Passed as 'self' arg to the C func, can be NULL */ PyObject *m_module; /* The __module__ attribute, can be anything */ -} PyCFunctionObject; +}; static PyObject *modifyStaticToClassMethod(PyTypeObject *type, PyObject *sm) { AutoDecRef func_ob(PyObject_GetAttr(sm, PyMagicName::func())); if (func_ob.isNull()) return nullptr; - auto func = reinterpret_cast<PyCFunctionObject *>(func_ob.object()); - auto new_func = new PyMethodDef; + auto *func = reinterpret_cast<PyCFunctionObject *>(func_ob.object()); + auto *new_func = new PyMethodDef; new_func->ml_name = func->m_ml->ml_name; new_func->ml_meth = func->m_ml->ml_meth; new_func->ml_flags = (func->m_ml->ml_flags & ~METH_STATIC) | METH_CLASS; @@ -593,25 +543,27 @@ static PyObject *createProperty(PyTypeObject *type, PyObject *getter, PyObject * assert(getter != nullptr); if (setter == nullptr) setter = Py_None; - auto ptype = &PyProperty_Type; + auto *ptype = &PyProperty_Type; if (Py_TYPE(getter) == PepStaticMethod_TypePtr) { - ptype = PyClassPropertyTypeF(); + ptype = PyClassProperty_TypeF(); getter = modifyStaticToClassMethod(type, getter); if (setter != Py_None) setter = modifyStaticToClassMethod(type, setter); } - auto obtype = reinterpret_cast<PyObject *>(ptype); + auto *obtype = reinterpret_cast<PyObject *>(ptype); PyObject *prop = PyObject_CallFunctionObjArgs(obtype, getter, setter, nullptr); return prop; } -static QStringList parseFields(const char *propstr, bool *stdwrite) +static const QByteArrayList parseFields(const char *propStr, bool *stdWrite) { /* * Break the string into subfields at ':' and add defaults. */ - QString s = QString(QLatin1String(propstr)); - auto list = s.split(QLatin1Char(':')); + if (stdWrite) + *stdWrite = true; + QByteArray s = QByteArray(propStr); + auto list = s.split(u':'); assert(list.size() == 2 || list.size() == 3); auto name = list[0]; auto read = list[1]; @@ -620,20 +572,70 @@ static QStringList parseFields(const char *propstr, bool *stdwrite) if (list.size() == 2) return list; auto write = list[2]; - if (stdwrite) - *stdwrite = write.isEmpty(); + if (stdWrite) + *stdWrite = write.isEmpty(); if (write.isEmpty()) { - list[2] = QLatin1String("set") + name; - list[2][3] = list[2][3].toUpper(); + list[2] = "set" + name; + list[2][3] = std::toupper(list[2][3]); } return list; } -static PyObject *make_snake_case(QString s, bool lower) +static PyObject *make_snake_case(const QByteArray &s, bool lower) { if (s.isNull()) return nullptr; - return String::getSnakeCaseName(s.toLatin1().data(), lower); + return String::getSnakeCaseName(s.constData(), lower); +} + +PyObject *adjustPropertyName(PyObject *dict, PyObject *name) +{ + // PYSIDE-1670: If this is a function with multiple arity or with + // parameters, we use a mangled name for the property. + PyObject *existing = PyDict_GetItem(dict, name); // borrowed + if (existing) { + Shiboken::AutoDecRef sig(get_signature_intern(existing, nullptr)); + if (sig.object()) { + bool name_clash = false; + if (PyList_CheckExact(sig)) { + name_clash = true; + } else { + Shiboken::AutoDecRef params(PyObject_GetAttr(sig, PySideName::parameters())); + // Are there parameters except self or cls? + if (PyObject_Size(params.object()) > 1) + name_clash = true; + } + if (name_clash) { + // PyPy has no PyUnicode_AppendAndDel function, yet + Shiboken::AutoDecRef hold(name); + Shiboken::AutoDecRef under(Py_BuildValue("s", "_")); + name = PyUnicode_Concat(hold, under); + } + } + } + return name; +} + +static QByteArrayList GetPropertyStringsMro(PyTypeObject *type) +{ + /* + * PYSIDE-2042: There are possibly more methods which should become properties, + * because the wrapping process does not obey inheritance. + * Therefore, we need to walk the mro to find property strings. + */ + auto res = QByteArrayList(); + + PyObject *mro = type->tp_mro; + const Py_ssize_t n = PyTuple_GET_SIZE(mro); + // We leave 'Shiboken.Object' and 'object' alone, therefore "n - 2". + for (Py_ssize_t idx = 0; idx < n - 2; idx++) { + auto *subType = reinterpret_cast<PyTypeObject *>(PyTuple_GET_ITEM(mro, idx)); + auto props = SbkObjectType_GetPropertyStrings(subType); + if (props != nullptr) + for (; *props != nullptr; ++props) + res << QByteArray(*props); + } + return res; } static bool feature_02_true_property(PyTypeObject *type, PyObject *prev_dict, int id) @@ -642,11 +644,19 @@ static bool feature_02_true_property(PyTypeObject *type, PyObject *prev_dict, in * Use the property info to create true Python property objects. */ + PyMethodDef *meth = type->tp_methods; + AutoDecRef tpDict(PepType_GetDict(type)); + PyObject *prop_dict = tpDict.object(); + // The empty `tp_dict` gets populated by the previous dict. - PyObject *prop_dict = type->tp_dict; if (PyDict_Update(prop_dict, prev_dict) < 0) return false; + // PYSIDE-1702: A user-defined class in Python has no internal method list. + // We are not going to change anything. + if (!meth) + return true; + // For speed, we establish a helper dict that maps the removed property // method names to property name. PyObject *prop_methods = PyDict_GetItem(prop_dict, PyMagicName::property_methods()); @@ -658,13 +668,13 @@ static bool feature_02_true_property(PyTypeObject *type, PyObject *prev_dict, in } // We then replace methods by properties. bool lower = (id & 0x01) != 0; - auto props = SbkObjectType_GetPropertyStrings(type); - if (props == nullptr || *props == nullptr) + auto props = GetPropertyStringsMro(type); + if (props.isEmpty()) return true; - for (; *props != nullptr; ++props) { + + for (const auto &propStr : std::as_const(props)) { bool isStdWrite; - auto propstr = *props; - auto fields = parseFields(propstr, &isStdWrite); + auto fields = parseFields(propStr, &isStdWrite); bool haveWrite = fields.size() == 3; PyObject *name = make_snake_case(fields[0], lower); PyObject *read = make_snake_case(fields[1], lower); @@ -675,6 +685,9 @@ static bool feature_02_true_property(PyTypeObject *type, PyObject *prev_dict, in continue; PyObject *setter = haveWrite ? PyDict_GetItem(prev_dict, write) : nullptr; + // PYSIDE-1670: If multiple arities exist as a property name, rename it. + name = adjustPropertyName(prop_dict, name); + AutoDecRef PyProperty(createProperty(type, getter, setter)); if (PyProperty.isNull()) return false; @@ -707,7 +720,7 @@ static bool feature_02_true_property(PyTypeObject *type, PyObject *prev_dict, in static PyObject *property_doc_get(PyObject *self, void *) { - auto po = reinterpret_cast<propertyobject *>(self); + auto *po = reinterpret_cast<propertyobject *>(self); if (po->prop_doc != nullptr && po->prop_doc != Py_None) { Py_INCREF(po->prop_doc); @@ -715,7 +728,7 @@ static PyObject *property_doc_get(PyObject *self, void *) } if (po->prop_get) { // PYSIDE-1019: Fetch the default `__doc__` from fget. We do it late. - auto txt = PyObject_GetAttr(po->prop_get, PyMagicName::doc()); + auto *txt = PyObject_GetAttr(po->prop_get, PyMagicName::doc()); if (txt != nullptr) { Py_INCREF(txt); po->prop_doc = txt; @@ -729,7 +742,7 @@ static PyObject *property_doc_get(PyObject *self, void *) static int property_doc_set(PyObject *self, PyObject *value, void *) { - auto po = reinterpret_cast<propertyobject *>(self); + auto *po = reinterpret_cast<propertyobject *>(self); Py_INCREF(value); po->prop_doc = value; @@ -746,12 +759,12 @@ static bool patch_property_impl() { // Turn `__doc__` into a computed attribute without changing writability. auto gsp = property_getset; - auto type = &PyProperty_Type; - auto dict = type->tp_dict; + auto *type = &PyProperty_Type; + AutoDecRef dict(PepType_GetDict(type)); AutoDecRef descr(PyDescr_NewGetSet(type, gsp)); if (descr.isNull()) return false; - if (PyDict_SetItemString(dict, gsp->name, descr) < 0) + if (PyDict_SetItemString(dict.object(), gsp->name, descr) < 0) return false; return true; } @@ -760,13 +773,14 @@ static bool patch_property_impl() // // PYSIDE-1019: Support switchable extensions // -// Feature 0x04..0x40: A fake switchable option for testing +// Feature 0x04..0x80: A fake switchable option for testing // #define SIMILAR_FEATURE(xx) \ -static bool feature_##xx##_addDummyNames(PyTypeObject *type, PyObject *prev_dict, int id) \ +static bool feature_##xx##_addDummyNames(PyTypeObject *type, PyObject *prev_dict, int /* id */) \ { \ - PyObject *dict = type->tp_dict; \ + AutoDecRef tpDict(PepType_GetDict(type)); \ + PyObject *dict = tpDict.object(); \ if (PyDict_Update(dict, prev_dict) < 0) \ return false; \ if (PyDict_SetItemString(dict, "fake_feature_" #xx, Py_None) < 0) \ @@ -781,5 +795,4 @@ SIMILAR_FEATURE(20) SIMILAR_FEATURE(40) SIMILAR_FEATURE(80) -} // namespace PySide -} // namespace Feature +} // namespace PySide::Feature diff --git a/sources/pyside6/libpyside/feature_select.h b/sources/pyside6/libpyside/feature_select.h index c7c14df3c..bf5a1b56b 100644 --- a/sources/pyside6/libpyside/feature_select.h +++ b/sources/pyside6/libpyside/feature_select.h @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2020 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of Qt for Python. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2020 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 FEATURE_SELECT_H #define FEATURE_SELECT_H @@ -43,15 +7,13 @@ #include "pysidemacros.h" #include <sbkpython.h> -namespace PySide { -namespace Feature { +namespace PySide::Feature { PYSIDE_API void init(); PYSIDE_API void Select(PyObject *obj); -PYSIDE_API PyObject *Select(PyTypeObject *type); +PYSIDE_API void Select(PyTypeObject *type); PYSIDE_API void Enable(bool); -} // namespace Feature -} // namespace PySide +} // namespace PySide::Feature #endif // FEATURE_SELECT_H diff --git a/sources/pyside6/libpyside/globalreceiverv2.cpp b/sources/pyside6/libpyside/globalreceiverv2.cpp index 8ff5d896f..2c75e39e1 100644 --- a/sources/pyside6/libpyside/globalreceiverv2.cpp +++ b/sources/pyside6/libpyside/globalreceiverv2.cpp @@ -1,76 +1,31 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of Qt for Python. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// 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 "globalreceiverv2.h" -#include "dynamicqmetaobject_p.h" #include "pysideweakref.h" +#include "pysidestaticstrings.h" +#include "pysideutils.h" #include "signalmanager.h" #include <autodecref.h> #include <gilstate.h> +#include <pep384ext.h> -#include <QtCore/qhashfunctions.h> #include <QtCore/QMetaMethod> #include <QtCore/QSet> +#include <QtCore/QDebug> + +#include <cstring> #define RECEIVER_DESTROYED_SLOT_NAME "__receiverDestroyed__(QObject*)" -namespace -{ - static int DESTROY_SIGNAL_ID = 0; - static int DESTROY_SLOT_ID = 0; -} namespace PySide { -size_t qHash(const GlobalReceiverKey &k, size_t seed) -{ - QtPrivate::QHashCombine hash; - seed = hash(seed, k.object); - seed = hash(seed, k.method); - return seed; -} - class DynamicSlotDataV2 { - Q_DISABLE_COPY(DynamicSlotDataV2) + Q_DISABLE_COPY_MOVE(DynamicSlotDataV2) public: DynamicSlotDataV2(PyObject *callback, GlobalReceiverV2 *parent); ~DynamicSlotDataV2(); @@ -84,6 +39,8 @@ class DynamicSlotDataV2 static void onCallbackDestroyed(void *data); static GlobalReceiverKey key(PyObject *callback); + void formatDebug(QDebug &debug) const; + private: bool m_isMethod; PyObject *m_callback; @@ -94,26 +51,65 @@ class DynamicSlotDataV2 GlobalReceiverV2 *m_parent; }; +void DynamicSlotDataV2::formatDebug(QDebug &debug) const +{ + debug << "method=" << m_isMethod << ", m_callback=" << m_callback; + if (m_callback != nullptr) + debug << '/' << Py_TYPE(m_callback)->tp_name; + debug << ", self=" << m_pythonSelf; + if (m_pythonSelf != nullptr) + debug << '/' << Py_TYPE(m_pythonSelf)->tp_name; + debug << ", m_pyClass=" << m_pyClass; + if (m_pyClass != nullptr) + debug << '/' << Py_TYPE(m_pyClass)->tp_name; + debug << ", signatures=" << m_signatures.keys(); +} + +QDebug operator<<(QDebug debug, const DynamicSlotDataV2 *d) +{ + QDebugStateSaver saver(debug); + debug.noquote(); + debug.nospace(); + debug << "DynamicSlotDataV2("; + if (d) + d->formatDebug(debug); + else + debug << '0'; + debug << ')'; + return debug; } using namespace PySide; DynamicSlotDataV2::DynamicSlotDataV2(PyObject *callback, GlobalReceiverV2 *parent) : - m_isMethod(PyMethod_Check(callback)), m_parent(parent) { Shiboken::GilState gil; - if (m_isMethod) { - //Can not store calback pointe because this will be destroyed at the end of the scope - //To avoid increment intance reference keep the callback information + if (PyMethod_Check(callback)) { + m_isMethod = true; + // To avoid increment instance reference keep the callback information m_callback = PyMethod_GET_FUNCTION(callback); + Py_INCREF(m_callback); m_pythonSelf = PyMethod_GET_SELF(callback); //monitor class from method lifetime m_weakRef = WeakRef::create(m_pythonSelf, DynamicSlotDataV2::onCallbackDestroyed, this); + } else if (PySide::isCompiledMethod(callback)) { + // PYSIDE-1523: PyMethod_Check is not accepting compiled form, we just go by attributes. + m_isMethod = true; + m_callback = PyObject_GetAttr(callback, PySide::PySideName::im_func()); + Py_DECREF(m_callback); + + m_pythonSelf = PyObject_GetAttr(callback, PySide::PySideName::im_self()); + Py_DECREF(m_pythonSelf); + + //monitor class from method lifetime + m_weakRef = WeakRef::create(m_pythonSelf, DynamicSlotDataV2::onCallbackDestroyed, this); } else { + m_isMethod = false; + m_callback = callback; Py_INCREF(m_callback); } @@ -125,6 +121,11 @@ GlobalReceiverKey DynamicSlotDataV2::key(PyObject *callback) if (PyMethod_Check(callback)) { // PYSIDE-1422: Avoid hash on self which might be unhashable. return {PyMethod_GET_SELF(callback), PyMethod_GET_FUNCTION(callback)}; + } else if (PySide::isCompiledMethod(callback)) { + // PYSIDE-1589: Fix for slots in compiled functions + Shiboken::AutoDecRef self(PyObject_GetAttr(callback, PySide::PySideName::im_self())); + Shiboken::AutoDecRef func(PyObject_GetAttr(callback, PySide::PySideName::im_func())); + return {self, func}; } return {nullptr, callback}; } @@ -135,7 +136,7 @@ PyObject *DynamicSlotDataV2::callback() //create a callback based on method data if (m_isMethod) - callback = PyMethod_New(m_callback, m_pythonSelf); + callback = PepExt_Type_CallDescrGet(m_callback, m_pythonSelf, nullptr); else Py_INCREF(callback); @@ -159,9 +160,9 @@ int DynamicSlotDataV2::addSlot(const char *signature) void DynamicSlotDataV2::onCallbackDestroyed(void *data) { auto self = reinterpret_cast<DynamicSlotDataV2 *>(data); - self->m_weakRef = 0; + self->m_weakRef = nullptr; Py_BEGIN_ALLOW_THREADS - delete self->m_parent; + SignalManager::instance().deleteGlobalReceiver(self->m_parent); Py_END_ALLOW_THREADS } @@ -170,37 +171,25 @@ DynamicSlotDataV2::~DynamicSlotDataV2() Shiboken::GilState gil; Py_XDECREF(m_weakRef); - m_weakRef = 0; + m_weakRef = nullptr; - if (!m_isMethod) - Py_DECREF(m_callback); + Py_DECREF(m_callback); } -GlobalReceiverV2::GlobalReceiverV2(PyObject *callback, GlobalReceiverV2MapPtr map) : +const char *GlobalReceiverV2::senderDynamicProperty = "_q_pyside_sender"; + +GlobalReceiverV2::GlobalReceiverV2(PyObject *callback, QObject *receiver) : QObject(nullptr), m_metaObject("__GlobalReceiver__", &QObject::staticMetaObject), - m_sharedMap(std::move(map)) + m_receiver(receiver) { m_data = new DynamicSlotDataV2(callback, this); - m_metaObject.addSlot(RECEIVER_DESTROYED_SLOT_NAME); - m_metaObject.update(); - m_refs.append(NULL); - - - if (DESTROY_SIGNAL_ID == 0) - DESTROY_SIGNAL_ID = QObject::staticMetaObject.indexOfSignal("destroyed(QObject*)"); - - if (DESTROY_SLOT_ID == 0) - DESTROY_SLOT_ID = m_metaObject.indexOfMethod(QMetaMethod::Slot, RECEIVER_DESTROYED_SLOT_NAME); - - } GlobalReceiverV2::~GlobalReceiverV2() { m_refs.clear(); // Remove itself from map. - m_sharedMap->remove(m_data->key()); // Suppress handling of destroyed() for objects whose last reference is contained inside // the callback object that will now be deleted. The reference could be a default argument, // a callback local variable, etc. @@ -209,7 +198,7 @@ GlobalReceiverV2::~GlobalReceiverV2() // leading to the object being deleted, which emits destroyed(), which would try to invoke // the already deleted callback, and also try to delete the object again. DynamicSlotDataV2 *data = m_data; - m_data = Q_NULLPTR; + m_data = nullptr; delete data; } @@ -220,67 +209,34 @@ int GlobalReceiverV2::addSlot(const char *signature) void GlobalReceiverV2::incRef(const QObject *link) { - if (link) { - if (!m_refs.contains(link)) { - bool connected; - Py_BEGIN_ALLOW_THREADS - connected = QMetaObject::connect(link, DESTROY_SIGNAL_ID, this, DESTROY_SLOT_ID); - Py_END_ALLOW_THREADS - if (connected) - m_refs.append(link); - else - Q_ASSERT(false); - } else { - m_refs.append(link); - } - } else { - m_refs.append(NULL); - } + Q_ASSERT(link); + m_refs.append(link); } void GlobalReceiverV2::decRef(const QObject *link) { - if (m_refs.empty()) - return; - - + Q_ASSERT(link); m_refs.removeOne(link); - if (link) { - if (!m_refs.contains(link)) { - bool result; - Py_BEGIN_ALLOW_THREADS - result = QMetaObject::disconnect(link, DESTROY_SIGNAL_ID, this, DESTROY_SLOT_ID); - Py_END_ALLOW_THREADS - Q_ASSERT(result); - if (!result) - return; - } - } - - if (m_refs.empty()) - Py_BEGIN_ALLOW_THREADS - delete this; - Py_END_ALLOW_THREADS +} +void GlobalReceiverV2::notify() +{ + purgeDeletedSenders(); } -int GlobalReceiverV2::refCount(const QObject *link) const +static bool isNull(const QPointer<const QObject> &p) { - if (link) - return m_refs.count(link); + return p.isNull(); +} - return m_refs.size(); +void GlobalReceiverV2::purgeDeletedSenders() +{ + m_refs.erase(std::remove_if(m_refs.begin(), m_refs.end(), isNull), m_refs.end()); } -void GlobalReceiverV2::notify() +bool GlobalReceiverV2::isEmpty() const { - const QSet<const QObject *> objSet(m_refs.cbegin(), m_refs.cend()); - Py_BEGIN_ALLOW_THREADS - for (const QObject *o : objSet) { - QMetaObject::disconnect(o, DESTROY_SIGNAL_ID, this, DESTROY_SLOT_ID); - QMetaObject::connect(o, DESTROY_SIGNAL_ID, this, DESTROY_SLOT_ID); - } - Py_END_ALLOW_THREADS + return std::all_of(m_refs.cbegin(), m_refs.cend(), isNull); } GlobalReceiverKey GlobalReceiverV2::key() const @@ -308,26 +264,21 @@ int GlobalReceiverV2::qt_metacall(QMetaObject::Call call, int id, void **args) Q_ASSERT(slot.methodType() == QMetaMethod::Slot); if (!m_data) { - if (id != DESTROY_SLOT_ID) { - const QByteArray message = "PySide6 Warning: Skipping callback call " - + slot.methodSignature() + " because the callback object is being destructed."; - PyErr_WarnEx(PyExc_RuntimeWarning, message.constData(), 0); - } + const QByteArray message = "PySide6 Warning: Skipping callback call " + + slot.methodSignature() + " because the callback object is being destructed."; + PyErr_WarnEx(PyExc_RuntimeWarning, message.constData(), 0); return -1; } - if (id == DESTROY_SLOT_ID) { - if (m_refs.empty()) - return -1; - auto obj = *reinterpret_cast<QObject **>(args[1]); - incRef(); //keep the object live (safe ref) - m_refs.removeAll(obj); // remove all refs to this object - decRef(); //remove the safe ref - } else { - bool isShortCuit = (strstr(slot.methodSignature(), "(") == 0); - Shiboken::AutoDecRef callback(m_data->callback()); - SignalManager::callPythonMetaMethod(slot, args, callback, isShortCuit); - } + const bool setSenderDynamicProperty = !m_receiver.isNull(); + if (setSenderDynamicProperty) + m_receiver->setProperty(senderDynamicProperty, QVariant::fromValue(sender())); + + Shiboken::AutoDecRef callback(m_data->callback()); + SignalManager::callPythonMetaMethod(slot, args, callback); + + if (setSenderDynamicProperty) + m_receiver->setProperty(senderDynamicProperty, QVariant{}); // SignalManager::callPythonMetaMethod might have failed, in that case we have to print the // error so it considered "handled". @@ -344,3 +295,28 @@ int GlobalReceiverV2::qt_metacall(QMetaObject::Call call, int id, void **args) return -1; } + +void GlobalReceiverV2::formatDebug(QDebug &debug) const +{ + debug << "receiver=" << m_receiver << ", slot=" << m_data; + if (isEmpty()) + debug << ", empty"; + else + debug << ", refs=" << m_refs; +}; + +QDebug operator<<(QDebug debug, const GlobalReceiverV2 *g) +{ + QDebugStateSaver saver(debug); + debug.noquote(); + debug.nospace(); + debug << "GlobalReceiverV2("; + if (g) + g->formatDebug(debug); + else + debug << '0'; + debug << ')'; + return debug; +} + +} // namespace PySide diff --git a/sources/pyside6/libpyside/globalreceiverv2.h b/sources/pyside6/libpyside/globalreceiverv2.h index da01e6d33..0e3bc562a 100644 --- a/sources/pyside6/libpyside/globalreceiverv2.h +++ b/sources/pyside6/libpyside/globalreceiverv2.h @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of Qt for Python. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// 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 GLOBALRECEIVER_V2_H #define GLOBALRECEIVER_V2_H @@ -44,10 +8,16 @@ #include "dynamicqmetaobject.h" +#include <QtCore/QtCompare> #include <QtCore/QByteArray> +#include <QtCore/QHashFunctions> #include <QtCore/QObject> +#include <QtCore/QPointer> #include <QtCore/QMap> -#include <QtCore/QSharedPointer> + +#include <memory> + +QT_FORWARD_DECLARE_CLASS(QDebug); namespace PySide { @@ -59,110 +29,87 @@ struct GlobalReceiverKey { const PyObject *object; const PyObject *method; -}; - -inline bool operator==(const GlobalReceiverKey &k1, const GlobalReceiverKey &k2) -{ - return k1.object == k2.object && k1.method == k2.method; -} - -inline bool operator!=(const GlobalReceiverKey &k1, const GlobalReceiverKey &k2) -{ - return k1.object != k2.object || k1.method != k2.method; -} - -size_t qHash(const GlobalReceiverKey &k, size_t seed = 0); - -using GlobalReceiverV2Map = QHash<GlobalReceiverKey, GlobalReceiverV2 *>; -using GlobalReceiverV2MapPtr = QSharedPointer<GlobalReceiverV2Map>; -/** - * A class used to make the link between the C++ Signal/Slot and Python callback - * This class is used internally by SignalManager - **/ + friend constexpr size_t qHash(GlobalReceiverKey k, size_t seed = 0) noexcept + { + return qHashMulti(seed, k.object, k.method); + } + friend constexpr bool comparesEqual(const GlobalReceiverKey &lhs, + const GlobalReceiverKey &rhs) noexcept + { + return lhs.object == rhs.object && lhs.method == rhs.method; + } + Q_DECLARE_EQUALITY_COMPARABLE_LITERAL_TYPE(GlobalReceiverKey) +}; +/// A class used to link C++ Signals to non C++ slots (Python callbacks) by +/// providing fake slots for QObject::connect(). +/// It keeps a Python callback and the list of QObject senders. It is stored +/// in SignalManager by a hash of the Python callback. class GlobalReceiverV2 : public QObject { public: - /** - * Create a GlobalReceiver object that will call 'callback' argumentent - * - * @param callback A Python callable object (can be a method or not) - * @param ma A SharedPointer used on Signal manager that contains all instaces of GlobalReceiver - **/ - GlobalReceiverV2(PyObject *callback, GlobalReceiverV2MapPtr map); - - /** - * Destructor - **/ + Q_DISABLE_COPY_MOVE(GlobalReceiverV2) + + /// Create a GlobalReceiver object that will call 'callback' + /// @param callback A Python callable object (can be a method or not) + explicit GlobalReceiverV2(PyObject *callback, QObject *receiver = nullptr); + ~GlobalReceiverV2() override; - /** - * Reimplemented function from QObject - **/ + /// Reimplemented function from QObject int qt_metacall(QMetaObject::Call call, int id, void **args) override; const QMetaObject *metaObject() const override; - /** - * Add a extra slot to this object - * - * @param signature The signature of the slot to be added - * @return The index of this slot on metaobject - **/ + /// Add a extra slot to this object + /// @param signature The signature of the slot to be added + /// @return The index of this slot on metaobject int addSlot(const char *signature); - /** - * Notify to GlobalReceiver about when a new connection was made - **/ + /// Notify to GlobalReceiver about when a new connection was made void notify(); - /** - * Used to increment the reference of the GlobalReceiver object - * - * @param link This is a optional paramenter used to link the ref to some QObject life - **/ - void incRef(const QObject *link = nullptr); - - /** - * Used to decrement the reference of the GlobalReceiver object - * - * @param link This is a optional paramenter used to dismiss the link ref to some QObject - **/ - void decRef(const QObject *link = nullptr); - - /* - * Return the count of refs which the GlobalReceiver has - * - * @param link If any QObject was passed, the function return the number of references relative to this 'link' object - * @return The number of references - **/ - int refCount(const QObject *link) const; - - /** - * Use to retrieve the unique hash of this GlobalReceiver object - * - * @return a string with a unique id based on GlobalReceiver contents - **/ + /// Used to increment the reference of the GlobalReceiver object + /// @param link This is a parameter used to link the ref to + /// some QObject life. + void incRef(const QObject *link); + + /// Used to decrement the reference of the GlobalReceiver object. + /// @param link This is a parameter used to dismiss the link + /// ref to some QObject. + void decRef(const QObject *link); + + /// Returns whether any senders are registered. + bool isEmpty() const; + + /// Use to retrieve the unique hash of this GlobalReceiver object + /// @return hash key GlobalReceiverKey key() const; - /** - * Use to retrieve the unique hash of the PyObject based on GlobalReceiver rules - * - * @param callback The Python callable object used to calculate the id - * @return a string with a unique id based on GlobalReceiver contents - **/ + /// Use to retrieve the unique hash of the PyObject based on GlobalReceiver rules + /// @param callback The Python callable object used to calculate the id + /// @return hash key static GlobalReceiverKey key(PyObject *callback); const MetaObjectBuilder &metaObjectBuilder() const { return m_metaObject; } MetaObjectBuilder &metaObjectBuilder() { return m_metaObject; } + static const char *senderDynamicProperty; + + void formatDebug(QDebug &debug) const; + private: + void purgeDeletedSenders(); + MetaObjectBuilder m_metaObject; DynamicSlotDataV2 *m_data; - QList<const QObject *> m_refs; - GlobalReceiverV2MapPtr m_sharedMap; + using QObjectPointer = QPointer<const QObject>; + QList<QObjectPointer> m_refs; + QPointer<QObject> m_receiver; }; +QDebug operator<<(QDebug debug, const GlobalReceiverV2 *g); + } #endif diff --git a/sources/pyside6/libpyside/libpyside.qrc b/sources/pyside6/libpyside/libpyside.qrc index 927c676a7..1e652eb4c 100644 --- a/sources/pyside6/libpyside/libpyside.qrc +++ b/sources/pyside6/libpyside/libpyside.qrc @@ -1,5 +1,5 @@ <RCC> <qresource prefix="/qt-project.org/logos"> - <file alias="pysidelogo.png">../doc/_themes/pysidedocs/static/pysidelogo.png</file> + <file alias="pysidelogo.png">../doc/_static/qtforpython.png</file> </qresource> </RCC> diff --git a/sources/pyside6/libpyside/pyside.cpp b/sources/pyside6/libpyside/pyside.cpp index 22230703e..9e12e3cd7 100644 --- a/sources/pyside6/libpyside/pyside.cpp +++ b/sources/pyside6/libpyside/pyside.cpp @@ -1,47 +1,18 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of Qt for Python. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// 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 "pyside.h" +#include "pysideinit.h" +#include "pysidecleanup.h" +#include "pysidemetatype.h" +#include "pysideqapp.h" +#include "pysideqobject.h" +#include "pysideutils.h" #include "pyside_p.h" #include "signalmanager.h" #include "pysideclassinfo_p.h" #include "pysideproperty_p.h" +#include "class_property.h" #include "pysideproperty.h" #include "pysidesignal.h" #include "pysidesignal_p.h" @@ -51,6 +22,7 @@ #include "pysidemetafunction.h" #include "dynamicqmetaobject.h" #include "feature_select.h" +#include "pysidelogging_p.h" #include <autodecref.h> #include <basewrapper.h> @@ -59,19 +31,29 @@ #include <sbkconverter.h> #include <sbkstring.h> #include <sbkstaticstrings.h> +#include <sbkfeature_base.h> +#include <sbkmodule.h> #include <QtCore/QByteArray> #include <QtCore/QCoreApplication> +#include <QtCore/QDebug> #include <QtCore/QDir> #include <QtCore/QFileInfo> -#include <QtCore/QSharedPointer> +#include <QtCore/QMetaMethod> +#include <QtCore/QMutex> #include <QtCore/QStack> +#include <QtCore/QThread> +#include <QtCore/private/qobject_p.h> #include <algorithm> #include <cstring> #include <cctype> +#include <memory> +#include <optional> #include <typeinfo> +using namespace Qt::StringLiterals; + static QStack<PySide::CleanupFunction> cleanupFunctionList; static void *qobjectNextAddr; @@ -80,72 +62,315 @@ extern bool qRegisterResourceData(int, const unsigned char *, const unsigned cha const unsigned char *); QT_END_NAMESPACE +Q_LOGGING_CATEGORY(lcPySide, "qt.pyside.libpyside", QtCriticalMsg) + +static QObjectData *qt_object_private(const QObject *o) +{ + class FriendlyQObject : public QObject { + public: + using QObject::d_ptr; + }; + return static_cast<const FriendlyQObject *>(o)->d_ptr.data(); +} + +static bool hasDynamicMetaObject(const QObject *o) +{ + return qt_object_private(o)->metaObject != nullptr; +} + namespace PySide { void init(PyObject *module) { - qobjectNextAddr = 0; + qobjectNextAddr = nullptr; ClassInfo::init(module); Signal::init(module); Slot::init(module); Property::init(module); + ClassProperty::init(module); MetaFunction::init(module); // Init signal manager, so it will register some meta types used by QVariant. SignalManager::instance(); initQApp(); } +static const QByteArray _sigWithMangledName(const QByteArray &signature, bool mangle) +{ + if (!mangle) + return signature; + auto bracePos = signature.indexOf('('); + auto limit = bracePos >= 0 ? bracePos : signature.size(); + if (limit < 3) + return signature; + QByteArray result; + result.reserve(signature.size() + 4); + for (auto i = 0; i < limit; ++i) { + const char c = signature.at(i); + if (std::isupper(c)) { + if (i > 0) { + if (std::isupper(signature.at(i - 1))) + return signature; // Give up at consecutive upper chars + result.append('_'); + } + result.append(std::tolower(c)); + } else { + result.append(c); + } + } + // Copy the rest after the opening brace (if any) + result.append(signature.mid(limit)); + return result; +} + +static const QByteArray _sigWithOrigName(const QByteArray &signature, bool mangle) +{ + if (!mangle) + return signature; + auto bracePos = signature.indexOf('('); + auto limit = bracePos >= 0 ? bracePos : signature.size(); + QByteArray result; + result.reserve(signature.size()); + for (auto i = 0; i < limit; ++i) { + const char c = signature.at(i); + if (std::isupper(c)) { + if (i > 0) { + if (std::isupper(signature.at(i - 1))) + return signature; // Give up at consecutive upper chars + return QByteArray{}; // Error, this was not converted! + } + } + if (std::islower(c) && i > 0 && signature.at(i - 1) == '_') { + result.chop(1); + result.append(std::toupper(c)); + } else { + result.append(c); + } + } + // Copy the rest after the opening brace (if any) + result.append(signature.mid(limit)); + return result; +} + +/***************************************************************************** + * + * How do we find a property? + * -------------------------- + * + * There are methods which are truly parts of properties, and there are + * other property-like methods which are not. True properties can be + * found by inspecting `SbkObjectType_GetPropertyStrings(type)`. + * + * Pseudo-properties have only a getter and a setter, and we must assume that + * the name of the getter is the property name, and the name of the setter + * is the uppercase of the getter with "set" prepended. + * + * We first walk the mro and search the property name and get the setter + * name. If that doesn't work, we use the heuristics for the setter. + * We then do the final mro lookup. + * + * Note that the true property lists have the original names, while the + * dict entries in the mro are already mangled. + */ + +static const QByteArrayList parseFields(const char *propStr, int flags, bool *stdWrite) +{ + /* + * Break the string into subfields at ':' and add defaults. + */ + if (stdWrite) + *stdWrite = true; + QByteArray s = QByteArray(propStr); + auto list = s.split(':'); + assert(list.size() == 2 || list.size() == 3); + auto name = list[0]; + auto read = list[1]; + if (read.isEmpty()) + list[1] = name; + if (list.size() == 2) + return list; + auto write = list[2]; + if (stdWrite) + *stdWrite = write.isEmpty(); + if (write.isEmpty()) { + auto snake_flag = flags & 0x01; + if (snake_flag) { + list[2] = ("set_") + name; + } else { + list[2] = QByteArray("set") + name; + list[2][3] = std::toupper(list[2][3]); + } + } + return list; +} + +static QByteArrayList _SbkType_LookupProperty(PyTypeObject *type, + const QByteArray &name, int flags) +{ + /* + * Looks up a property and returns all fields. + */ + int snake_flag = flags & 0x01; + QByteArray origName(_sigWithOrigName(name, snake_flag)); + if (origName.isEmpty()) + return QByteArrayList{}; + PyObject *mro = type->tp_mro; + auto n = PyTuple_GET_SIZE(mro); + auto len = std::strlen(origName); + for (Py_ssize_t idx = 0; idx < n; idx++) { + PyTypeObject *base = reinterpret_cast<PyTypeObject *>(PyTuple_GET_ITEM(mro, idx)); + if (!SbkObjectType_Check(base)) + continue; + auto props = SbkObjectType_GetPropertyStrings(base); + if (props == nullptr || *props == nullptr) + continue; + for (; *props != nullptr; ++props) { + QByteArray propStr(*props); + if (std::strncmp(propStr, origName, len) == 0) { + if (propStr[len] != ':') + continue; + // We found the property. Return the parsed fields. + propStr = _sigWithMangledName(propStr, snake_flag); + return parseFields(propStr, flags, nullptr); + } + } + } + return QByteArrayList{}; +} + +static QByteArrayList _SbkType_FakeProperty(const QByteArray &name, int flags) +{ + /* + * Handle a pseudo.property and return all fields. + */ + int snake_flag = flags & 0x01; + QByteArray propStr(name); + propStr += "::"; + propStr = _sigWithMangledName(propStr, snake_flag); + return parseFields(propStr, snake_flag, nullptr); +} + static bool _setProperty(PyObject *qObj, PyObject *name, PyObject *value, bool *accept) { + using Shiboken::AutoDecRef; + QByteArray propName(Shiboken::String::toCString(name)); - propName[0] = std::toupper(propName[0]); - propName.prepend("set"); + auto type = Py_TYPE(qObj); + int flags = currentSelectId(type); + int prop_flag = flags & 0x02; + auto found = false; + QByteArray getterName{}, setterName{}; + + auto fields = _SbkType_LookupProperty(type, propName, flags); + if (!fields.isEmpty()) { + found = true; + bool haveWrite = fields.size() == 3; + if (!haveWrite) + return false; + } else { + fields = _SbkType_FakeProperty(propName, flags); + } + + propName = fields[0]; + getterName = fields[1]; + setterName = fields[2]; + + // PYSIDE-1702: We do not use getattr, since that could trigger an action + // if we have a true property. Better to look inside the mro. + // That should return a descriptor or a property. + PyObject *look{}; - Shiboken::AutoDecRef propSetter(PyObject_GetAttrString(qObj, propName.constData())); - if (!propSetter.isNull()) { + if (found && prop_flag) { + // We have a property, and true_property is active. + // There must be a property object and we use it's fset. + AutoDecRef pyPropName(Shiboken::String::fromCString(propName.constData())); + look = _PepType_Lookup(Py_TYPE(qObj), pyPropName); + } else { + // We have a pseudo property or true_property is off, looking for a setter. + AutoDecRef pySetterName(Shiboken::String::fromCString(setterName.constData())); + look = _PepType_Lookup(Py_TYPE(qObj), pySetterName); + } + + if (look) { + AutoDecRef propSetter{}; + static PyObject *magicGet = Shiboken::PyMagicName::get(); + if (found && prop_flag) { + // the indirection of the setter descriptor in a true property + AutoDecRef descr(PyObject_GetAttr(look, PySideName::fset())); + propSetter.reset(PyObject_CallMethodObjArgs(descr, magicGet, qObj, nullptr)); + } else { + // look is already the descriptor + propSetter.reset(PyObject_CallMethodObjArgs(look, magicGet, qObj, nullptr)); + } *accept = true; - Shiboken::AutoDecRef args(PyTuple_Pack(1, value)); - Shiboken::AutoDecRef retval(PyObject_CallObject(propSetter, args)); + AutoDecRef args(PyTuple_Pack(1, value)); + AutoDecRef retval(PyObject_CallObject(propSetter, args)); if (retval.isNull()) return false; } else { PyErr_Clear(); - Shiboken::AutoDecRef attr(PyObject_GenericGetAttr(qObj, name)); + AutoDecRef attr(PyObject_GenericGetAttr(qObj, name)); if (PySide::Property::checkType(attr)) { *accept = true; - if (PySide::Property::setValue(reinterpret_cast<PySideProperty *>(attr.object()), qObj, value) < 0) + if (PySide::Property::setValue(reinterpret_cast<PySideProperty *>( + attr.object()), qObj, value) < 0) return false; } } return true; } -bool fillQtProperties(PyObject *qObj, const QMetaObject *metaObj, PyObject *kwds) +// PYSIDE-2329: Search a signal by name (Note: QMetaObject::indexOfSignal() +// searches by signature). +static std::optional<QMetaMethod> findSignal(const QMetaObject *mo, + const QByteArray &name) +{ + const auto count = mo->methodCount(); + for (int i = mo->methodOffset(); i < count; ++i) { + const auto method = mo->method(i); + if (method.methodType() == QMetaMethod::Signal && method.name() == name) + return method; + } + auto *base = mo->superClass(); + return base != nullptr ? findSignal(base, name) : std::nullopt; +} + +bool fillQtProperties(PyObject *qObj, const QMetaObject *metaObj, + PyObject *kwds, bool allowErrors) { PyObject *key, *value; Py_ssize_t pos = 0; + int flags = currentSelectId(Py_TYPE(qObj)); + int snake_flag = flags & 0x01; while (PyDict_Next(kwds, &pos, &key, &value)) { - QByteArray propName(Shiboken::String::toCString(key)); + const QByteArray propName = Shiboken::String::toCString(key); + QByteArray unmangledName = _sigWithOrigName(propName, snake_flag); bool accept = false; - if (metaObj->indexOfProperty(propName) != -1) { - if (!_setProperty(qObj, key, value, &accept)) - return false; - } else { - propName.append("()"); - if (metaObj->indexOfSignal(propName) != -1) { - accept = true; - propName.prepend('2'); - if (!PySide::Signal::connect(qObj, propName, value)) + // PYSIDE-1705: Make sure that un-mangled names are not recognized in snake_case mode. + if (!unmangledName.isEmpty()) { + if (metaObj->indexOfProperty(unmangledName) != -1) { + if (!_setProperty(qObj, key, value, &accept)) + return false; + } else { + const auto methodO = findSignal(metaObj, propName); + if (methodO.has_value()) { + const auto signature = "2"_ba + methodO->methodSignature(); + accept = true; + if (!PySide::Signal::connect(qObj, signature, value)) + return false; + } + } + if (!accept) { + // PYSIDE-1019: Allow any existing attribute in the constructor. + if (!_setProperty(qObj, key, value, &accept)) return false; } } - if (!accept) { - // PYSIDE-1019: Allow any existing attribute in the constructor. - if (!_setProperty(qObj, key, value, &accept)) - return false; + if (allowErrors) { + PyErr_Clear(); + continue; } if (!accept) { PyErr_Format(PyExc_AttributeError, "'%s' is not a Qt property or a signal", @@ -213,12 +438,12 @@ void destroyQCoreApplication() Py_DECREF(MakeQAppWrapper(nullptr)); } -std::size_t getSizeOfQObject(SbkObjectType *type) +std::size_t getSizeOfQObject(PyTypeObject *type) { return retrieveTypeUserData(type)->cppObjSize; } -void initDynamicMetaObject(SbkObjectType *type, const QMetaObject *base, std::size_t cppObjSize) +void initDynamicMetaObject(PyTypeObject *type, const QMetaObject *base, std::size_t cppObjSize) { //create DynamicMetaObject based on python type auto userData = new TypeUserData(reinterpret_cast<PyTypeObject *>(type), base, cppObjSize); @@ -232,17 +457,14 @@ void initDynamicMetaObject(SbkObjectType *type, const QMetaObject *base, std::si return; Shiboken::AutoDecRef pyMetaObject(Shiboken::Conversions::pointerToPython(converter, metaObjectPtr)); PyObject_SetAttr(reinterpret_cast<PyObject *>(type), - PySide::PyName::qtStaticMetaObject(), pyMetaObject); -} - -TypeUserData *retrieveTypeUserData(SbkObjectType *sbkTypeObj) -{ - return reinterpret_cast<TypeUserData *>(Shiboken::ObjectType::getTypeUserData(sbkTypeObj)); + Shiboken::PyName::qtStaticMetaObject(), pyMetaObject); } TypeUserData *retrieveTypeUserData(PyTypeObject *pyTypeObj) { - return retrieveTypeUserData(reinterpret_cast<SbkObjectType *>(pyTypeObj)); + if (!SbkObjectType_Check(pyTypeObj)) + return nullptr; + return reinterpret_cast<TypeUserData *>(Shiboken::ObjectType::getTypeUserData(pyTypeObj)); } TypeUserData *retrieveTypeUserData(PyObject *pyObj) @@ -265,10 +487,9 @@ const QMetaObject *retrieveMetaObject(PyObject *pyObj) return retrieveMetaObject(pyTypeObj); } -void initQObjectSubType(SbkObjectType *type, PyObject *args, PyObject * /* kwds */) +void initQObjectSubType(PyTypeObject *type, PyObject *args, PyObject * /* kwds */) { PyTypeObject *qObjType = Shiboken::Conversions::getPythonTypeObject("QObject*"); - QByteArray className(Shiboken::String::toCString(PyTuple_GET_ITEM(args, 0))); PyObject *bases = PyTuple_GET_ITEM(args, 1); int numBases = PyTuple_GET_SIZE(bases); @@ -283,7 +504,9 @@ void initQObjectSubType(SbkObjectType *type, PyObject *args, PyObject * /* kwds } } if (!userData) { - qWarning("Sub class of QObject not inheriting QObject!? Crash will happen when using %s.", className.constData()); + const char *className = Shiboken::String::toCString(PyTuple_GET_ITEM(args, 0)); + qWarning("Sub class of QObject not inheriting QObject!? Crash will happen when using %s.", + className); return; } // PYSIDE-1463: Don't change feature selection durin subtype initialization. @@ -313,8 +536,11 @@ void initQApp() setDestroyQApplication(destroyQCoreApplication); } -PyObject *getMetaDataFromQObject(QObject *cppSelf, PyObject *self, PyObject *name) +PyObject *getHiddenDataFromQObject(QObject *cppSelf, PyObject *self, PyObject *name) { + using Shiboken::AutoDecRef; + + // PYSIDE-68-bis: This getattr finds signals early by `signalDescrGet`. PyObject *attr = PyObject_GenericGetAttr(self, name); if (!Shiboken::Object::isValid(reinterpret_cast<SbkObject *>(self), false)) return attr; @@ -323,31 +549,67 @@ PyObject *getMetaDataFromQObject(QObject *cppSelf, PyObject *self, PyObject *nam PyObject *value = Property::getValue(reinterpret_cast<PySideProperty *>(attr), self); Py_DECREF(attr); if (!value) - return 0; + return nullptr; attr = value; } - //mutate native signals to signal instance type - if (attr && PyObject_TypeCheck(attr, PySideSignalTypeF())) { - PyObject *signal = reinterpret_cast<PyObject *>(Signal::initialize(reinterpret_cast<PySideSignal *>(attr), name, self)); - PyObject_SetAttr(self, name, reinterpret_cast<PyObject *>(signal)); - return signal; - } - - //search on metaobject (avoid internal attributes started with '__') + // Search on metaobject (avoid internal attributes started with '__') if (!attr) { + PyObject *type, *value, *traceback; + PyErr_Fetch(&type, &value, &traceback); // This was omitted for a loong time. + + int flags = currentSelectId(Py_TYPE(self)); + int snake_flag = flags & 0x01; + int propFlag = flags & 0x02; + + if (propFlag) { + // PYSIDE-1889: If we have actually a Python property, return f(get|set|del). + // Do not store this attribute in the instance dict, because this + // would create confusion with overload. + // Note: before implementing this property handling, the meta function code + // below created meta functions which was quite wrong. + auto *subdict = _PepType_Lookup(Py_TYPE(self), PySideMagicName::property_methods()); + PyObject *propName = PyDict_GetItem(subdict, name); + if (propName) { + // We really have a property name and need to fetch the fget or fset function. + static PyObject *const _fget = Shiboken::String::createStaticString("fget"); + static PyObject *const _fset = Shiboken::String::createStaticString("fset"); + static PyObject *const _fdel = Shiboken::String::createStaticString("fdel"); + static PyObject *const arr[3] = {_fget, _fset, _fdel}; + auto prop = _PepType_Lookup(Py_TYPE(self), propName); + for (int idx = 0; idx < 3; ++idx) { + auto *trial = arr[idx]; + auto *res = PyObject_GetAttr(prop, trial); + if (res) { + AutoDecRef elemName(PyObject_GetAttr(res, PySideMagicName::name())); + // Note: This comparison works because of interned strings. + if (elemName == name) + return res; + Py_DECREF(res); + } + PyErr_Clear(); + } + } + } + const char *cname = Shiboken::String::toCString(name); uint cnameLen = qstrlen(cname); - if (std::strncmp("__", cname, 2)) { + if (std::strncmp("__", cname, 2) != 0) { const QMetaObject *metaObject = cppSelf->metaObject(); - //signal QList<QMetaMethod> signalList; - for(int i=0, i_max = metaObject->methodCount(); i < i_max; i++) { + // Caution: This inserts a meta function or a signal into the instance dict. + for (int i=0, imax = metaObject->methodCount(); i < imax; i++) { QMetaMethod method = metaObject->method(i); - const QByteArray methSig_ = method.methodSignature(); + // PYSIDE-1753: Snake case names must be renamed here too, or they will be + // found unexpectedly when forgetting to rename them. + auto origSignature = method.methodSignature(); + // Currently, we rename only methods but no signals. This might change. + bool use_lower = snake_flag and method.methodType() != QMetaMethod::Signal; + const QByteArray methSig_ = _sigWithMangledName(origSignature, use_lower); const char *methSig = methSig_.constData(); - bool methMacth = !std::strncmp(cname, methSig, cnameLen) && methSig[cnameLen] == '('; - if (methMacth) { + bool methMatch = std::strncmp(cname, methSig, cnameLen) == 0 + && methSig[cnameLen] == '('; + if (methMatch) { if (method.methodType() == QMetaMethod::Signal) { signalList.append(method); } else { @@ -360,12 +622,14 @@ PyObject *getMetaDataFromQObject(QObject *cppSelf, PyObject *self, PyObject *nam } } } - if (!signalList.empty()) { - PyObject *pySignal = reinterpret_cast<PyObject *>(Signal::newObjectFromMethod(self, signalList)); + if (!signalList.isEmpty()) { + PyObject *pySignal = reinterpret_cast<PyObject *>( + Signal::newObjectFromMethod(self, signalList)); PyObject_SetAttr(self, name, pySignal); return pySignal; } } + PyErr_Restore(type, value, traceback); } return attr; } @@ -376,12 +640,18 @@ bool inherits(PyTypeObject *objType, const char *class_name) return true; PyTypeObject *base = objType->tp_base; - if (base == 0) + if (base == nullptr) return false; return inherits(base, class_name); } +QMutex &nextQObjectMemoryAddrMutex() +{ + static QMutex mutex; + return mutex; +} + void *nextQObjectMemoryAddr() { return qobjectNextAddr; @@ -394,34 +664,67 @@ void setNextQObjectMemoryAddr(void *addr) } // namespace PySide -// A QSharedPointer is used with a deletion function to invalidate a pointer +// A std::shared_ptr is used with a deletion function to invalidate a pointer // when the property value is cleared. This should be a QSharedPointer with // a void *pointer, but that isn't allowed -typedef char any_t; -Q_DECLARE_METATYPE(QSharedPointer<any_t>); +using any_t = char; +Q_DECLARE_METATYPE(std::shared_ptr<any_t>); + namespace PySide { static void invalidatePtr(any_t *object) { + // PYSIDE-2254: Guard against QObjects outliving Python, for example the + // adopted main thread as returned by QObjects::thread(). + if (Py_IsInitialized() == 0) + return; + Shiboken::GilState state; SbkObject *wrapper = Shiboken::BindingManager::instance().retrieveWrapper(object); - if (wrapper != NULL) + if (wrapper != nullptr) Shiboken::BindingManager::instance().releaseWrapper(wrapper); } static const char invalidatePropertyName[] = "_PySideInvalidatePtr"; +// PYSIDE-2749: Skip over internal QML classes and classes +// with dynamic meta objects when looking for the best matching +// type to avoid unnessarily triggering the lazy load mechanism +// for classes that do not have a binding from things like eventFilter(). +static inline bool isInternalObject(const char *name) +{ + return std::strstr(name, "QMLTYPE") != nullptr || std::strstr(name, "QQmlPrivate") != nullptr; +} + +static const QMetaObject *metaObjectCandidate(const QObject *o) +{ + auto *metaObject = o->metaObject(); + // Skip QML helper types and Python objects + if (hasDynamicMetaObject(o)) { + if (auto *super = metaObject->superClass()) + metaObject = super; + } + for (auto *candidate = metaObject; candidate != nullptr; candidate = candidate->superClass()) { + if (!isInternalObject(candidate->className())) { + metaObject = candidate; + break; + } + } + return metaObject; +} + // PYSIDE-1214, when creating new wrappers for classes inheriting QObject but // not exposed to Python, try to find the best-matching (most-derived) Qt // class by walking up the meta objects. -static const char *typeName(QObject *cppSelf) +static const char *typeName(const QObject *cppSelf) { const char *typeName = typeid(*cppSelf).name(); if (!Shiboken::Conversions::getConverter(typeName)) { - for (auto metaObject = cppSelf->metaObject(); metaObject; metaObject = metaObject->superClass()) { + auto *metaObject = metaObjectCandidate(cppSelf); + for (; metaObject != nullptr; metaObject = metaObject->superClass()) { const char *name = metaObject->className(); if (Shiboken::Conversions::getConverter(name)) { typeName = name; @@ -432,7 +735,21 @@ static const char *typeName(QObject *cppSelf) return typeName; } -PyObject *getWrapperForQObject(QObject *cppSelf, SbkObjectType *sbk_type) +PyTypeObject *getTypeForQObject(const QObject *cppSelf) +{ + // First check if there are any instances of Python implementations + // inheriting a PySide class. + auto *existing = Shiboken::BindingManager::instance().retrieveWrapper(cppSelf); + if (existing != nullptr) + return reinterpret_cast<PyObject *>(existing)->ob_type; + // Find the best match (will return a PySide type) + auto *sbkObjectType = Shiboken::ObjectType::typeForTypeName(typeName(cppSelf)); + if (sbkObjectType != nullptr) + return reinterpret_cast<PyTypeObject *>(sbkObjectType); + return nullptr; +} + +PyObject *getWrapperForQObject(QObject *cppSelf, PyTypeObject *sbk_type) { PyObject *pyOut = reinterpret_cast<PyObject *>(Shiboken::BindingManager::instance().retrieveWrapper(cppSelf)); if (pyOut) { @@ -445,8 +762,10 @@ PyObject *getWrapperForQObject(QObject *cppSelf, SbkObjectType *sbk_type) // set and check if it's created after the set call QVariant existing = cppSelf->property(invalidatePropertyName); if (!existing.isValid()) { - QSharedPointer<any_t> shared_with_del(reinterpret_cast<any_t *>(cppSelf), invalidatePtr); - cppSelf->setProperty(invalidatePropertyName, QVariant::fromValue(shared_with_del)); + if (cppSelf->thread() == QThread::currentThread()) { + std::shared_ptr<any_t> shared_with_del(reinterpret_cast<any_t *>(cppSelf), invalidatePtr); + cppSelf->setProperty(invalidatePropertyName, QVariant::fromValue(shared_with_del)); + } pyOut = reinterpret_cast<PyObject *>(Shiboken::BindingManager::instance().retrieveWrapper(cppSelf)); if (pyOut) { Py_INCREF(pyOut); @@ -454,35 +773,43 @@ PyObject *getWrapperForQObject(QObject *cppSelf, SbkObjectType *sbk_type) } } - pyOut = Shiboken::Object::newObject(sbk_type, cppSelf, false, false, typeName(cppSelf)); + pyOut = Shiboken::Object::newObjectWithHeuristics(sbk_type, cppSelf, false, typeName(cppSelf)); return pyOut; } -#ifdef PYSIDE_QML_SUPPORT -static QuickRegisterItemFunction quickRegisterItem; - -QuickRegisterItemFunction getQuickRegisterItemFunction() +QString pyUnicodeToQString(PyObject *str) { - return quickRegisterItem; + Q_ASSERT(PyUnicode_Check(str) != 0); + + const void *data = _PepUnicode_DATA(str); + const Py_ssize_t len = PyUnicode_GetLength(str); + switch (_PepUnicode_KIND(str)) { + case PepUnicode_1BYTE_KIND: + return QString::fromLatin1(reinterpret_cast<const char *>(data), len); + case PepUnicode_2BYTE_KIND: + return QString::fromUtf16(reinterpret_cast<const char16_t *>(data), len); + case PepUnicode_4BYTE_KIND: + break; + } + return QString::fromUcs4(reinterpret_cast<const char32_t *>(data), len); } -void setQuickRegisterItemFunction(QuickRegisterItemFunction function) +PyObject *qStringToPyUnicode(QStringView s) { - quickRegisterItem = function; + const QByteArray ba = s.toUtf8(); + return PyUnicode_FromStringAndSize(ba.constData(), ba.size()); } -#endif // PYSIDE_QML_SUPPORT // Inspired by Shiboken::String::toCString; -QString pyStringToQString(PyObject *str) { +QString pyStringToQString(PyObject *str) +{ if (str == Py_None) return QString(); - if (PyUnicode_Check(str)) { - const char *unicodeBuffer = _PepUnicode_AsString(str); - if (unicodeBuffer) - return QString::fromUtf8(unicodeBuffer); - } + if (PyUnicode_Check(str) != 0) + return pyUnicodeToQString(str); + if (PyBytes_Check(str)) { const char *asciiBuffer = PyBytes_AS_STRING(str); if (asciiBuffer) @@ -491,6 +818,32 @@ QString pyStringToQString(PyObject *str) { return QString(); } +// PySide-1499: Provide an efficient, correct PathLike interface +QString pyPathToQString(PyObject *path) +{ + // For empty constructors path can be nullptr + // fallback to an empty QString in that case. + if (!path) + return QString(); + + // str or bytes pass through + if (PyUnicode_Check(path) || PyBytes_Check(path)) + return pyStringToQString(path); + + // Let PyOS_FSPath do its work and then fix the result for Windows. + Shiboken::AutoDecRef strPath(PyOS_FSPath(path)); + if (strPath.isNull()) + return QString(); + return QDir::fromNativeSeparators(pyStringToQString(strPath)); +} + +bool isCompiledMethod(PyObject *callback) +{ + return PyObject_HasAttr(callback, PySide::PySideName::im_func()) + && PyObject_HasAttr(callback, PySide::PySideName::im_self()) + && PyObject_HasAttr(callback, PySide::PySideMagicName::code()); +} + static const unsigned char qt_resource_name[] = { // qt 0x0,0x2, @@ -524,9 +877,9 @@ bool registerInternalQtConf() { // Guard to ensure single registration. #ifdef PYSIDE_QT_CONF_PREFIX - static bool registrationAttempted = false; + static bool registrationAttempted = false; #else - static bool registrationAttempted = true; + static bool registrationAttempted = true; #endif static bool isRegistered = false; if (registrationAttempted) @@ -537,19 +890,33 @@ bool registerInternalQtConf() // PyInstaller executable. // This will disable the internal qt.conf which points to the PySide6 subdirectory (due to the // subdirectory not existing anymore). - QString executablePath = - QString::fromWCharArray(Py_GetProgramFullPath()); +#ifndef PYPY_VERSION + QString executablePath = QString::fromWCharArray(Py_GetProgramFullPath()); +#else + // PYSIDE-535: FIXME: Add this function when available. + QString executablePath = QLatin1StringView("missing Py_GetProgramFullPath"); +#endif // PYPY_VERSION + QString appDirPath = QFileInfo(executablePath).absolutePath(); - QString maybeQtConfPath = QDir(appDirPath).filePath(QStringLiteral("qt.conf")); - bool executableQtConfAvailable = QFileInfo::exists(maybeQtConfPath); + + QString maybeQtConfPath = QDir(appDirPath).filePath(u"qt.conf"_s); maybeQtConfPath = QDir::toNativeSeparators(maybeQtConfPath); + bool executableQtConfAvailable = QFileInfo::exists(maybeQtConfPath); + + QString maybeQt6ConfPath = QDir(appDirPath).filePath(u"qt6.conf"_s); + maybeQt6ConfPath = QDir::toNativeSeparators(maybeQt6ConfPath); + bool executableQt6ConfAvailable = QFileInfo::exists(maybeQt6ConfPath); // Allow disabling the usage of the internal qt.conf. This is necessary for tests to work, // because tests are executed before the package is installed, and thus the Prefix specified // in qt.conf would point to a not yet existing location. bool disableInternalQtConf = - qEnvironmentVariableIntValue("PYSIDE_DISABLE_INTERNAL_QT_CONF") > 0 ? true : false; - if (disableInternalQtConf || executableQtConfAvailable) { + qEnvironmentVariableIntValue("PYSIDE_DISABLE_INTERNAL_QT_CONF") > 0; + bool runsInConda = + qEnvironmentVariableIsSet("CONDA_DEFAULT_ENV") || qEnvironmentVariableIsSet("CONDA_PREFIX"); + + if ((!runsInConda && (disableInternalQtConf || executableQtConfAvailable)) + || (runsInConda && executableQt6ConfAvailable)) { registrationAttempted = true; return false; } @@ -579,28 +946,20 @@ bool registerInternalQtConf() #ifdef PYSIDE_QT_CONF_PREFIX setupPrefix = QStringLiteral(PYSIDE_QT_CONF_PREFIX); #endif - const QString prefixPathStr = pysideDir.absoluteFilePath(setupPrefix); -#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) - const QByteArray prefixPath = prefixPathStr.toLocal8Bit(); -#else - // PYSIDE-972, QSettings used by QtCore uses Latin1 - const QByteArray prefixPath = prefixPathStr.toLatin1(); -#endif + const QByteArray prefixPath = pysideDir.absoluteFilePath(setupPrefix).toUtf8(); // rccData needs to be static, otherwise when it goes out of scope, the Qt resource system // will point to invalid memory. - static QByteArray rccData = QByteArrayLiteral("[Paths]\nPrefix = ") + prefixPath + static QByteArray rccData = QByteArrayLiteral("[Paths]\nPrefix = ") + prefixPath + "\n"; #ifdef Q_OS_WIN - // LibraryExecutables needs to point to Prefix instead of ./bin because we don't - // currently conform to the Qt default directory layout on Windows. This is necessary - // for QtWebEngineCore to find the location of QtWebEngineProcess.exe. - + QByteArray("\nLibraryExecutables = ") + prefixPath + // LibraryExecutables needs to point to Prefix instead of ./bin because we don't + // currently conform to the Qt default directory layout on Windows. This is necessary + // for QtWebEngineCore to find the location of QtWebEngineProcess.exe. + rccData += QByteArrayLiteral("LibraryExecutables = ") + prefixPath + "\n"; #endif - ; - rccData.append('\n'); // The RCC data structure expects a 4-byte size value representing the actual data. - int size = rccData.size(); + qsizetype size = rccData.size(); for (int i = 0; i < 4; ++i) { rccData.prepend((size & 0xff)); @@ -615,7 +974,242 @@ bool registerInternalQtConf() return isRegistered; } +static PyTypeObject *qobjectType() +{ + static PyTypeObject * const result = Shiboken::Conversions::getPythonTypeObject("QObject*"); + return result; +} + +bool isQObjectDerived(PyTypeObject *pyType, bool raiseError) +{ + const bool result = PyType_IsSubtype(pyType, qobjectType()); + if (!result && raiseError) { + PyErr_Format(PyExc_TypeError, "A type inherited from %s expected, got %s.", + qobjectType()->tp_name, pyType->tp_name); + } + return result; +} + +QObject *convertToQObject(PyObject *object, bool raiseError) +{ + if (object == nullptr) { + if (raiseError) + PyErr_Format(PyExc_TypeError, "None passed for QObject"); + return nullptr; + } + + if (!isQObjectDerived(Py_TYPE(object), raiseError)) + return nullptr; + + auto *sbkObject = reinterpret_cast<SbkObject*>(object); + auto *ptr = Shiboken::Object::cppPointer(sbkObject, qobjectType()); + if (ptr == nullptr) { + if (raiseError) { + PyErr_Format(PyExc_TypeError, "Conversion of %s to QObject failed.", + Py_TYPE(object)->tp_name); + } + return nullptr; + } + return reinterpret_cast<QObject*>(ptr); +} + +QMetaType qMetaTypeFromPyType(PyTypeObject *pyType) +{ + if (Shiboken::String::checkType(pyType)) + return QMetaType(QMetaType::QString); + if (pyType == &PyFloat_Type) + return QMetaType(QMetaType::Double); + if (pyType == &PyLong_Type) + return QMetaType(QMetaType::Int); + if (Shiboken::ObjectType::checkType(pyType)) + return QMetaType::fromName(Shiboken::ObjectType::getOriginalName(pyType)); + return QMetaType::fromName(pyType->tp_name); +} + +debugPyTypeObject::debugPyTypeObject(const PyTypeObject *o) noexcept + : m_object(o) +{ +} + +QDebug operator<<(QDebug debug, const debugPyTypeObject &o) +{ + QDebugStateSaver saver(debug); + debug.noquote(); + debug.nospace(); + debug << "PyTypeObject("; + if (o.m_object) + debug << '"' << o.m_object->tp_name << '"'; + else + debug << '0'; + debug << ')'; + return debug; +} + +static void formatPyObject(PyObject *obj, QDebug &debug); + +static void formatPySequence(PyObject *obj, QDebug &debug) +{ + const Py_ssize_t size = PySequence_Size(obj); + debug << size << " ["; + for (Py_ssize_t i = 0; i < size; ++i) { + if (i) + debug << ", "; + Shiboken::AutoDecRef item(PySequence_GetItem(obj, i)); + formatPyObject(item.object(), debug); + } + debug << ']'; +} + +static void formatPyDict(PyObject *obj, QDebug &debug) +{ + PyObject *key; + PyObject *value; + Py_ssize_t pos = 0; + bool first = true; + debug << '{'; + while (PyDict_Next(obj, &pos, &key, &value) != 0) { + if (first) + first = false; + else + debug << ", "; + formatPyObject(key, debug); + debug << '='; + formatPyObject(value, debug); + } + debug << '}'; +} + +static inline const char *pyTypeName(PyObject *obj) +{ + return Py_TYPE(obj)->tp_name; +} + +static QString getQualName(PyObject *obj) +{ + Shiboken::AutoDecRef result(PyObject_GetAttr(obj, Shiboken::PyMagicName::qualname())); + return result.object() != nullptr + ? pyStringToQString(result.object()) : QString{}; +} + +static void formatPyFunction(PyObject *obj, QDebug &debug) +{ + debug << '"' << getQualName(obj) << "()\""; +} + +static void formatPyMethod(PyObject *obj, QDebug &debug) +{ + if (auto *func = PyMethod_Function(obj)) + formatPyFunction(func, debug); + debug << ", instance=" << PyMethod_Self(obj); +} + +static void formatPyObjectValue(PyObject *obj, QDebug &debug) +{ + if (PyType_Check(obj) != 0) + debug << "type: \"" << pyTypeName(obj) << '"'; + else if (PyLong_Check(obj) != 0) { + const auto llv = PyLong_AsLongLong(obj); + if (PyErr_Occurred() != PyExc_OverflowError) { + debug << llv; + } else { + PyErr_Clear(); + debug << "0x" << Qt::hex << PyLong_AsUnsignedLongLong(obj) << Qt::dec; + } + } else if (PyFloat_Check(obj) != 0) + debug << PyFloat_AsDouble(obj); + else if (PyUnicode_Check(obj) != 0) + debug << '"' << pyStringToQString(obj) << '"'; + else if (PyFunction_Check(obj) != 0) + formatPyFunction(obj, debug); + else if (PyMethod_Check(obj) != 0) + formatPyMethod(obj, debug); + else if (PySequence_Check(obj) != 0) + formatPySequence(obj, debug); + else if (PyDict_Check(obj) != 0) + formatPyDict(obj, debug); + else + debug << obj; +} + +static void formatPyObject(PyObject *obj, QDebug &debug) +{ + if (obj == nullptr) { + debug << '0'; + return; + } + if (obj == Py_None) { + debug << "None"; + return; + } + if (obj == Py_True) { + debug << "True"; + return; + } + if (obj == Py_False) { + debug << "False"; + return; + } + if (PyType_Check(obj) == 0) + debug << pyTypeName(obj) << ": "; + formatPyObjectValue(obj, debug); +} +debugPyObject::debugPyObject(PyObject *o) noexcept : m_object(o) +{ +} -} //namespace PySide +QDebug operator<<(QDebug debug, const debugPyObject &o) +{ + QDebugStateSaver saver(debug); + debug.noquote(); + debug.nospace(); + debug << "PyObject("; + formatPyObject(o.m_object, debug); + debug << ')'; + return debug; +} +debugPyBuffer::debugPyBuffer(Py_buffer *b) noexcept : m_buffer(b) +{ +} + +static void formatPy_ssizeArray(QDebug &debug, const char *name, const Py_ssize_t *array, int len) +{ + debug << ", " << name << '='; + if (array != nullptr) { + debug << '['; + for (int i = 0; i < len; ++i) + debug << array[i] << ' '; + debug << ']'; + } else { + debug << '0'; + } +} + +PYSIDE_API QDebug operator<<(QDebug debug, const debugPyBuffer &b) +{ + QDebugStateSaver saver(debug); + debug.noquote(); + debug.nospace(); + debug << "Py_buffer("; + if (b.m_buffer != nullptr) { + debug << "obj=" << b.m_buffer->obj + << ", buf=" << b.m_buffer->buf << ", len=" << b.m_buffer->len + << ", readonly=" << b.m_buffer->readonly + << ", itemsize=" << b.m_buffer->itemsize << ", format="; + if (b.m_buffer->format != nullptr) + debug << '"' << b.m_buffer->format << '"'; + else + debug << '0'; + debug << ", ndim=" << b.m_buffer->ndim; + formatPy_ssizeArray(debug, "shape", b.m_buffer->shape, b.m_buffer->ndim); + formatPy_ssizeArray(debug, "strides", b.m_buffer->strides, b.m_buffer->ndim); + formatPy_ssizeArray(debug, "suboffsets", b.m_buffer->suboffsets, b.m_buffer->ndim); + } else { + debug << '0'; + } + debug << ')'; + return debug; +} + +} // namespace PySide diff --git a/sources/pyside6/libpyside/pyside.h b/sources/pyside6/libpyside/pyside.h index 3c4ae92b2..c8793cbf0 100644 --- a/sources/pyside6/libpyside/pyside.h +++ b/sources/pyside6/libpyside/pyside.h @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of Qt for Python. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// 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 PYSIDE_H #define PYSIDE_H @@ -44,128 +8,9 @@ #include <pysidemacros.h> -#ifdef PYSIDE_QML_SUPPORT -# include <QtQml/qqml.h> -#endif - -#include <QtCore/QMetaType> -#include <QtCore/QHash> - -struct SbkObjectType; - -namespace PySide -{ - -PYSIDE_API void init(PyObject *module); - -/** - * Hash function used to enable hash on objects not supported on native Qt library which has toString function. - */ -template<class T> -inline Py_ssize_t hash(const T& value) -{ - return qHash(value.toString()); -} - -/** - * Fill QObject properties and do signal connections using the values found in \p kwds dictonary. - * \param qObj PyObject fot the QObject. - * \param metaObj QMetaObject of \p qObj. - * \param kwds key->value dictonary. - * \return True if everything goes well, false with a Python error setted otherwise. - */ -PYSIDE_API bool fillQtProperties(PyObject *qObj, const QMetaObject *metaObj, PyObject *kwds); - -/** -* If the type \p T was registered on Qt meta type system with Q_DECLARE_METATYPE macro, this class will initialize -* the meta type. -* -* Initialize a meta type means register it on Qt meta type system, Qt itself only do this on the first call of -* qMetaTypeId, and this is exactly what we do to init it. If we don't do that, calls to QMetaType::type("QMatrix2x2") -* could return zero, causing QVariant to not recognize some C++ types, like QMatrix2x2. -*/ -template<typename T, bool OK = QMetaTypeId<T>::Defined > -struct initQtMetaType { - initQtMetaType() - { - qMetaTypeId<T>(); - } -}; - -// Template specialization to do nothing when the type wasn't registered on Qt meta type system. -template<typename T> -struct initQtMetaType<T, false> { -}; - -PYSIDE_API void initDynamicMetaObject(SbkObjectType *type, const QMetaObject *base, - std::size_t cppObjSize); -PYSIDE_API void initQObjectSubType(SbkObjectType *type, PyObject *args, PyObject *kwds); -PYSIDE_API void initQApp(); - -/// Return the size in bytes of a type that inherits QObject. -PYSIDE_API std::size_t getSizeOfQObject(SbkObjectType *type); - -typedef void (*CleanupFunction)(void); - -/** - * Register a function to be called before python die - */ -PYSIDE_API void registerCleanupFunction(CleanupFunction func); -PYSIDE_API void runCleanupFunctions(); - -/** - * Destroy a QCoreApplication taking care of destroy all instances of QObject first. - */ -PYSIDE_API void destroyQCoreApplication(); - -/** - * Check for properties and signals registered on MetaObject and return these - * \param cppSelf Is the QObject which contains the metaobject - * \param self Python object of cppSelf - * \param name Name of the argument which the function will try retrieve from MetaData - * \return The Python object which contains the Data obtained in metaObject or the Python attribute related with name - */ -PYSIDE_API PyObject *getMetaDataFromQObject(QObject *cppSelf, PyObject *self, PyObject *name); - -/** - * Check if self inherits from class_name - * \param self Python object - * \param class_name strict with the class name - * \return Returns true if self object inherits from class_name, otherwise returns false - */ -PYSIDE_API bool inherits(PyTypeObject *self, const char *class_name); - -PYSIDE_API void *nextQObjectMemoryAddr(); -PYSIDE_API void setNextQObjectMemoryAddr(void *addr); - -PYSIDE_API PyObject *getWrapperForQObject(QObject *cppSelf, SbkObjectType *sbk_type); - -#ifdef PYSIDE_QML_SUPPORT -// Used by QtQuick module to notify QtQml that custom QtQuick items can be registered. -using QuickRegisterItemFunction = - bool (*)(PyObject *pyObj, const char *uri, int versionMajor, - int versionMinor, const char *qmlName, - bool creatable, const char *noCreationReason, - QQmlPrivate::RegisterType *); -PYSIDE_API QuickRegisterItemFunction getQuickRegisterItemFunction(); -PYSIDE_API void setQuickRegisterItemFunction(QuickRegisterItemFunction function); -#endif // PYSIDE_QML_SUPPORT - -/** - * Given A PyObject repesenting ASCII or Unicode data, returns an equivalent QString. - */ -PYSIDE_API QString pyStringToQString(PyObject *str); - -/** - * Registers a dynamic "qt.conf" file with the Qt resource system. - * - * This is used in a standalone build, to inform QLibraryInfo of the Qt prefix (where Qt libraries - * are installed) so that plugins can be successfully loaded. - */ -PYSIDE_API bool registerInternalQtConf(); - - -} //namespace PySide - +#include "pysideinit.h" +#include "pysideqapp.h" +#include "pysideqobject.h" +#include "pysideutils.h" #endif // PYSIDE_H diff --git a/sources/pyside6/libpyside/pyside_numpy.cpp b/sources/pyside6/libpyside/pyside_numpy.cpp new file mode 100644 index 000000000..5f43cc5ce --- /dev/null +++ b/sources/pyside6/libpyside/pyside_numpy.cpp @@ -0,0 +1,98 @@ +// 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 "pyside_numpy.h" +#include <sbknumpyview.h> + +// Convert X,Y of type T data to a list of points (QPoint, PointF) +template <class T, class Point> +static QList<Point> + xyDataToQPointHelper(const void *xData, const void *yData, qsizetype size) +{ + auto *x = reinterpret_cast<const T *>(xData); + auto *y = reinterpret_cast<const T *>(yData); + QList<Point> result; + result.reserve(size); + for (auto xEnd = x + size; x < xEnd; ++x, ++y) + result.append(Point(*x, *y)); + return result; +} + +// Convert X,Y of double/float type data to a list of QPoint (rounding) +template <class T> +static QList<QPoint> + xyFloatDataToQPointHelper(const void *xData, const void *yData, qsizetype size) +{ + auto *x = reinterpret_cast<const T *>(xData); + auto *y = reinterpret_cast<const T *>(yData); + QList<QPoint> result; + result.reserve(size); + for (auto xEnd = x + size; x < xEnd; ++x, ++y) + result.append(QPoint(qRound(*x), qRound(*y))); + return result; +} + +namespace PySide::Numpy +{ + +QList<QPointF> xyDataToQPointFList(PyObject *pyXIn, PyObject *pyYIn) +{ + auto xv = Shiboken::Numpy::View::fromPyObject(pyXIn); + auto yv = Shiboken::Numpy::View::fromPyObject(pyYIn); + if (!xv.sameLayout(yv)) + return {}; + const qsizetype size = qMin(xv.dimensions[0], yv.dimensions[0]); + if (size == 0) + return {}; + switch (xv.type) { + case Shiboken::Numpy::View::Int16: + return xyDataToQPointHelper<int16_t, QPointF>(xv.data, yv.data, size); + case Shiboken::Numpy::View::Unsigned16: + return xyDataToQPointHelper<uint16_t, QPointF>(xv.data, yv.data, size); + case Shiboken::Numpy::View::Int: + return xyDataToQPointHelper<int, QPointF>(xv.data, yv.data, size); + case Shiboken::Numpy::View::Unsigned: + return xyDataToQPointHelper<unsigned, QPointF>(xv.data, yv.data, size); + case Shiboken::Numpy::View::Int64: + return xyDataToQPointHelper<int64_t, QPointF>(xv.data, yv.data, size); + case Shiboken::Numpy::View::Unsigned64: + return xyDataToQPointHelper<uint64_t, QPointF>(xv.data, yv.data, size); + case Shiboken::Numpy::View::Float: + return xyDataToQPointHelper<float, QPointF>(xv.data, yv.data, size); + case Shiboken::Numpy::View::Double: + break; + } + return xyDataToQPointHelper<double, QPointF>(xv.data, yv.data, size); +} + +QList<QPoint> xyDataToQPointList(PyObject *pyXIn, PyObject *pyYIn) +{ + auto xv = Shiboken::Numpy::View::fromPyObject(pyXIn); + auto yv = Shiboken::Numpy::View::fromPyObject(pyYIn); + if (!xv.sameLayout(yv)) + return {}; + const qsizetype size = qMin(xv.dimensions[0], yv.dimensions[0]); + if (size == 0) + return {}; + switch (xv.type) { + case Shiboken::Numpy::View::Int16: + return xyDataToQPointHelper<int16_t, QPoint>(xv.data, yv.data, size); + case Shiboken::Numpy::View::Unsigned16: + return xyDataToQPointHelper<uint16_t, QPoint>(xv.data, yv.data, size); + case Shiboken::Numpy::View::Int: + return xyDataToQPointHelper<int, QPoint>(xv.data, yv.data, size); + case Shiboken::Numpy::View::Unsigned: + return xyDataToQPointHelper<unsigned, QPoint>(xv.data, yv.data, size); + case Shiboken::Numpy::View::Int64: + return xyDataToQPointHelper<int64_t, QPoint>(xv.data, yv.data, size); + case Shiboken::Numpy::View::Unsigned64: + return xyDataToQPointHelper<uint64_t, QPoint>(xv.data, yv.data, size); + case Shiboken::Numpy::View::Float: + return xyFloatDataToQPointHelper<float>(xv.data, yv.data, size); + case Shiboken::Numpy::View::Double: + break; + } + return xyFloatDataToQPointHelper<double>(xv.data, yv.data, size); +} + +} //namespace PySide::Numpy diff --git a/sources/pyside6/libpyside/pyside_numpy.h b/sources/pyside6/libpyside/pyside_numpy.h new file mode 100644 index 000000000..0925a6bd6 --- /dev/null +++ b/sources/pyside6/libpyside/pyside_numpy.h @@ -0,0 +1,37 @@ +// 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 PYSIDE_NUMPY_H +#define PYSIDE_NUMPY_H + +#include <sbkpython.h> +#include <sbknumpycheck.h> + +#include <pysidemacros.h> + +#include <QtCore/QList> +#include <QtCore/QPoint> +#include <QtCore/QPointF> + +namespace PySide::Numpy +{ + +/// Create a list of QPointF from 2 equally sized numpy array of x and y data +/// (float,double). +/// \param pyXIn X data array +/// \param pyYIn Y data array +/// \return List of QPointF + +PYSIDE_API QList<QPointF> xyDataToQPointFList(PyObject *pyXIn, PyObject *pyYIn); + +/// Create a list of QPoint from 2 equally sized numpy array of x and y data +/// (int). +/// \param pyXIn X data array +/// \param pyYIn Y data array +/// \return List of QPoint + +PYSIDE_API QList<QPoint> xyDataToQPointList(PyObject *pyXIn, PyObject *pyYIn); + +} //namespace PySide::Numpy + +#endif // PYSIDE_NUMPY_H diff --git a/sources/pyside6/libpyside/pyside_p.h b/sources/pyside6/libpyside/pyside_p.h index 1084a40a1..b13c1829a 100644 --- a/sources/pyside6/libpyside/pyside_p.h +++ b/sources/pyside6/libpyside/pyside_p.h @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2018 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of Qt for Python. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2018 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 PYSIDE_P_H #define PYSIDE_P_H @@ -44,8 +8,6 @@ #include <dynamicqmetaobject.h> -struct SbkObjectType; - namespace PySide { @@ -59,7 +21,6 @@ struct TypeUserData std::size_t cppObjSize; }; -TypeUserData *retrieveTypeUserData(SbkObjectType *sbkTypeObj); TypeUserData *retrieveTypeUserData(PyTypeObject *pyTypeObj); TypeUserData *retrieveTypeUserData(PyObject *pyObj); // For QML diff --git a/sources/pyside6/libpyside/pysideclassdecorator.cpp b/sources/pyside6/libpyside/pysideclassdecorator.cpp new file mode 100644 index 000000000..ec69c5fe7 --- /dev/null +++ b/sources/pyside6/libpyside/pysideclassdecorator.cpp @@ -0,0 +1,103 @@ +// 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 <sbkpython.h> + +#include "pysideclassdecorator_p.h" +#include "pysideqobject.h" + +#include <basewrapper.h> +#include <sbkstring.h> + +namespace PySide::ClassDecorator { + +DecoratorPrivate::DecoratorPrivate() noexcept = default; +DecoratorPrivate::~DecoratorPrivate() = default; + +DecoratorPrivate *DecoratorPrivate::getPrivate(PyObject *o) +{ + auto *decorator = reinterpret_cast<PySideClassDecorator *>(o); + return decorator->d; +} + +PyObject *DecoratorPrivate::tp_call_check(PyObject *args, CheckMode checkMode) const +{ + if (!PyTuple_Check(args) || PyTuple_Size(args) != 1) { + PyErr_Format(PyExc_TypeError, + "The %s decorator takes exactly 1 positional argument (%zd given)", + name(), PyTuple_Size(args)); + return nullptr; + } + + PyObject *arg = PyTuple_GetItem(args, 0); + + // This will sometimes segfault if you mistakenly use it on a function declaration + if (!PyType_Check(arg)) { + PyErr_Format(PyExc_TypeError, + "The %s decorator can only be used on class declarations", name()); + return nullptr; + } + + auto type = reinterpret_cast<PyTypeObject *>(arg); + + if (checkMode != CheckMode::None && !Shiboken::ObjectType::checkType(type)) { + PyErr_Format(PyExc_TypeError, + "The %s decorator can only be used on wrapped types.", name()); + return nullptr; + } + + if (checkMode == CheckMode::QObjectType && !isQObjectDerived(type, false)) { + PyErr_Format(PyExc_TypeError, + "The %s decorator can only be used on QObject-derived types.", name()); + return nullptr; + } + + return arg; +} + +int StringDecoratorPrivate::convertToString(PyObject *self, PyObject *args) +{ + int result = -1; + if (PyTuple_Size(args) == 1) { + PyObject *arg = PyTuple_GET_ITEM(args, 0); + if (PyUnicode_Check(arg)) { + auto *pData = DecoratorPrivate::get<StringDecoratorPrivate>(self); + result = 0; + pData->m_string.assign(Shiboken::String::toCString(arg)); + } + } + return result; +} + +int StringDecoratorPrivate::tp_init(PyObject *self, PyObject *args, PyObject *) +{ + const int result = convertToString(self, args); + if (result != 0) + PyErr_Format(PyExc_TypeError, "%s takes a single string argument.", name()); + return result; +} + +int TypeDecoratorPrivate::tp_init(PyObject *self, PyObject *args, PyObject *) +{ + const int result = convertToType(self, args); + if (result != 0) + PyErr_Format(PyExc_TypeError, "%s takes a single type argument.", name()); + return result; +} + +int TypeDecoratorPrivate::convertToType(PyObject *self, PyObject *args) +{ + int result = -1; + const auto argsCount = PyTuple_Size(args); + if (argsCount == 1) { + PyObject *arg = PyTuple_GET_ITEM(args, 0); + if (PyType_Check(arg)) { + result = 0; + auto *pData = DecoratorPrivate::get<TypeDecoratorPrivate>(self); + pData->m_type = reinterpret_cast<PyTypeObject *>(arg); + } + } + return result; +} + +} // namespace PySide::ClassDecorator diff --git a/sources/pyside6/libpyside/pysideclassdecorator_p.h b/sources/pyside6/libpyside/pysideclassdecorator_p.h new file mode 100644 index 000000000..6068f6a2e --- /dev/null +++ b/sources/pyside6/libpyside/pysideclassdecorator_p.h @@ -0,0 +1,164 @@ +// 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 CLASSDECORATOR_P_H +#define CLASSDECORATOR_P_H + +#include <pysidemacros.h> + +#include <sbkpython.h> +#include <pep384ext.h> + +#include <QtCore/QByteArray> + +#include <array> +#include <string> + +/// Helpers for class decorators with parameters +namespace PySide::ClassDecorator { + +/// Base class for private objects of class decorators with parameters +class PYSIDE_API DecoratorPrivate +{ +public: + Q_DISABLE_COPY_MOVE(DecoratorPrivate) + + virtual ~DecoratorPrivate(); + + /// Virtual function which is passed the decorated class type + /// \param args Decorated class type argument + /// \return class with reference count increased if the call was successful, + /// else nullptr + virtual PyObject *tp_call(PyObject *self, PyObject *args, PyObject * /* kw */) = 0; + + /// Virtual function which is passed the decorator parameters + /// \param args Decorator arguments + /// \return 0 if the parameters are correct + virtual int tp_init(PyObject *self, PyObject *args, PyObject *kwds) = 0; + virtual const char *name() const = 0; + + /// Helper that returns DecoratorPrivate instance from a PyObject + template <class DerivedPrivate> + static DerivedPrivate *get(PyObject *o) + { return static_cast<DerivedPrivate *>(DecoratorPrivate::getPrivate(o)); } + +protected: + /// Check mode for the arguments of the call operator + enum class CheckMode { None, WrappedType, QObjectType }; + + DecoratorPrivate() noexcept; + static DecoratorPrivate *getPrivate(PyObject *o); + + /// Helper for checking the arguments of the call operator + /// \param args Arguments + /// \param checkMode Type check mode + /// \return The type object extracted from args tuple (borrowed reference) + /// if the argument is a matching type + PyObject *tp_call_check(PyObject *args, + CheckMode checkMode = CheckMode::QObjectType) const; +}; + +/// Base class for private objects of class decorator with a string parameter +class PYSIDE_API StringDecoratorPrivate : public DecoratorPrivate +{ +public: + /// Init function that retrieves the string parameter using convertToString() + int tp_init(PyObject *self, PyObject *args, PyObject *kwds) override; + + QByteArray string() const { return m_string; } + +protected: + /// Helper function that retrieves the string parameter + /// \param self self + /// \param args Arguments + /// \return 0 if the parameter is correct, else -1 (for tp_init()) + int convertToString(PyObject *self, PyObject *args); + +private: + QByteArray m_string; +}; + +/// Base class for private objects of class decorator with a type parameter +class PYSIDE_API TypeDecoratorPrivate : public DecoratorPrivate +{ +public: + /// Init function that retrieves the type parameter using convertToType() + int tp_init(PyObject *self, PyObject *args, PyObject *kwds) override; + + PyTypeObject *type() const { return m_type; } + +protected: + /// Helper function that retrieves the type parameter + /// \param self self + /// \param args Arguments + /// \return 0 if the parameter is correct, else -1 (for tp_init()) + int convertToType(PyObject *self, PyObject *args); + +private: + PyTypeObject *m_type = nullptr; +}; + +} // namespace PySide::ClassDecorator + +extern "C" +{ +LIBSHIBOKEN_API void Sbk_object_dealloc(PyObject *self); + +/// Python type for class decorators with DecoratorPrivate +struct PYSIDE_API PySideClassDecorator +{ + PyObject_HEAD + PySide::ClassDecorator::DecoratorPrivate *d; +}; +}; + +namespace PySide::ClassDecorator { + +/// Helper template providing the methods (slots) for class decorators +template <class DecoratorPrivate> +struct Methods +{ + static PyObject *tp_new(PyTypeObject *subtype) + { + auto *result = PepExt_TypeCallAlloc<PySideClassDecorator>(subtype, 0); + result->d = new DecoratorPrivate; + return reinterpret_cast<PyObject *>(result); + } + + static void tp_free(void *self) + { + auto pySelf = reinterpret_cast<PyObject *>(self); + auto decorator = reinterpret_cast<PySideClassDecorator *>(self); + delete decorator->d; + PepExt_TypeCallFree(Py_TYPE(pySelf)->tp_base, self); + } + + static PyObject *tp_call(PyObject *self, PyObject *args, PyObject *kwds) + { + auto *decorator = reinterpret_cast<PySideClassDecorator *>(self); + return decorator->d->tp_call(self, args, kwds); + } + + static int tp_init(PyObject *self, PyObject *args, PyObject *kwds) + { + auto *decorator = reinterpret_cast<PySideClassDecorator *>(self); + return decorator->d->tp_init(self, args, kwds); + } + + using TypeSlots = std::array<PyType_Slot, 6>; + + static TypeSlots typeSlots() + { + return { {{Py_tp_call, reinterpret_cast<void *>(tp_call)}, + {Py_tp_init, reinterpret_cast<void *>(tp_init)}, + {Py_tp_new, reinterpret_cast<void *>(tp_new)}, + {Py_tp_free, reinterpret_cast<void *>(tp_free)}, + {Py_tp_dealloc, reinterpret_cast<void *>(Sbk_object_dealloc)}, + {0, nullptr}} + }; + } +}; + +} // namespace PySide::ClassDecorator + +#endif // CLASSDECORATOR_P_H diff --git a/sources/pyside6/libpyside/pysideclassinfo.cpp b/sources/pyside6/libpyside/pysideclassinfo.cpp index 2a9914a68..698cb1c76 100644 --- a/sources/pyside6/libpyside/pysideclassinfo.cpp +++ b/sources/pyside6/libpyside/pysideclassinfo.cpp @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of Qt for Python. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// 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 <sbkpython.h> @@ -50,158 +14,145 @@ extern "C" { -static PyObject *classInfoTpNew(PyTypeObject *subtype, PyObject *args, PyObject *kwds); -static int classInfoTpInit(PyObject *, PyObject *, PyObject *); -static void classInfoFree(void *); -static PyObject *classCall(PyObject *, PyObject *, PyObject *); - -static PyType_Slot PySideClassInfoType_slots[] = { - {Py_tp_call, (void *)classCall}, - {Py_tp_init, (void *)classInfoTpInit}, - {Py_tp_new, (void *)classInfoTpNew}, - {Py_tp_free, (void *)classInfoFree}, - {Py_tp_dealloc, (void *)Sbk_object_dealloc}, - {0, 0} -}; -static PyType_Spec PySideClassInfoType_spec = { - "2:PySide6.QtCore.ClassInfo", - sizeof(PySideClassInfo), - 0, - Py_TPFLAGS_DEFAULT, - PySideClassInfoType_slots, +static PyTypeObject *createClassInfoType() +{ + auto typeSlots = + PySide::ClassDecorator::Methods<PySide::ClassInfo::ClassInfoPrivate>::typeSlots(); + + PyType_Spec PySideClassInfoType_spec = { + "2:PySide6.QtCore.ClassInfo", + sizeof(PySideClassDecorator), + 0, + Py_TPFLAGS_DEFAULT, + typeSlots.data()}; + return SbkType_FromSpec(&PySideClassInfoType_spec); }; - -PyTypeObject *PySideClassInfoTypeF(void) +PyTypeObject *PySideClassInfo_TypeF(void) { - static PyTypeObject *type = - reinterpret_cast<PyTypeObject *>(SbkType_FromSpec(&PySideClassInfoType_spec)); + static auto *type = createClassInfoType(); return type; } -PyObject *classCall(PyObject *self, PyObject *args, PyObject * /* kw */) -{ - if (!PyTuple_Check(args) || PyTuple_Size(args) != 1) { - PyErr_Format(PyExc_TypeError, - "The ClassInfo decorator takes exactly 1 positional argument (%zd given)", - PyTuple_Size(args)); - return 0; - } +} // extern "C" - PySideClassInfo *data = reinterpret_cast<PySideClassInfo *>(self); - PySideClassInfoPrivate *pData = data->d; +namespace PySide::ClassInfo { - if (pData->m_alreadyWrapped) { - PyErr_SetString(PyExc_TypeError, "This instance of ClassInfo() was already used to wrap an object"); - return 0; - } +const char *ClassInfoPrivate::name() const +{ + return "ClassInfo"; +} + +PyObject *ClassInfoPrivate::tp_call(PyObject *self, PyObject *args, PyObject * /* kw */) +{ + PyObject *klass = tp_call_check(args, CheckMode::QObjectType); + if (klass == nullptr) + return nullptr; - PyObject *klass = PyTuple_GetItem(args, 0); - bool validClass = false; + auto *pData = DecoratorPrivate::get<ClassInfoPrivate>(self); - // This will sometimes segfault if you mistakenly use it on a function declaration - if (!PyType_Check(klass)) { - PyErr_SetString(PyExc_TypeError, "This decorator can only be used on class declarations"); - return 0; - } + if (pData->m_alreadyWrapped) + return PyErr_Format(PyExc_TypeError, "This instance of ClassInfo() was already used to wrap an object"); PyTypeObject *klassType = reinterpret_cast<PyTypeObject *>(klass); - if (Shiboken::ObjectType::checkType(klassType)) { - if (auto userData = PySide::retrieveTypeUserData(klassType)) { - PySide::MetaObjectBuilder &mo = userData->mo; - mo.addInfo(PySide::ClassInfo::getMap(data)); - pData->m_alreadyWrapped = true; - validClass = true; - } - } + if (!PySide::ClassInfo::setClassInfo(klassType, pData->m_data)) + return PyErr_Format(PyExc_TypeError, "This decorator can only be used on classes that are subclasses of QObject"); - if (!validClass) { - PyErr_SetString(PyExc_TypeError, "This decorator can only be used on classes that are subclasses of QObject"); - return 0; - } + pData->m_alreadyWrapped = true; Py_INCREF(klass); return klass; } -static PyObject *classInfoTpNew(PyTypeObject *subtype, PyObject * /* args */, PyObject * /* kwds */) +int ClassInfoPrivate::tp_init(PyObject *self, PyObject *args, PyObject *kwds) { - PySideClassInfo *me = reinterpret_cast<PySideClassInfo *>(subtype->tp_alloc(subtype, 0)); - me->d = new PySideClassInfoPrivate; - - me->d->m_alreadyWrapped = false; - - return reinterpret_cast<PyObject *>(me); -} + PyObject *infoDict = nullptr; + auto size = PyTuple_Size(args); + if (size == 1 && kwds == nullptr) { + PyObject *tmp = PyTuple_GET_ITEM(args, 0); + if (PyDict_Check(tmp)) + infoDict = tmp; + } else if (size == 0 && kwds && PyDict_Check(kwds)) { + infoDict = kwds; + } -int classInfoTpInit(PyObject *self, PyObject *args, PyObject *kwds) -{ - if (PyTuple_Check(args) && PyTuple_Size(args) > 0) { - PyErr_Format(PyExc_TypeError, "ClassInfo() takes exactly 0 positional arguments (%zd given)", PyTuple_Size(args)); + if (infoDict == nullptr) { + PyErr_Format(PyExc_TypeError, "ClassInfo() takes either keyword argument(s) or " + "a single dictionary argument"); return -1; } - PySideClassInfo *data = reinterpret_cast<PySideClassInfo *>(self); - PySideClassInfoPrivate *pData = data->d; + auto *pData = DecoratorPrivate::get<ClassInfoPrivate>(self); - PyObject *key; - PyObject *value; + PyObject *key{}; + PyObject *value{}; Py_ssize_t pos = 0; // PyDict_Next causes a segfault if kwds is empty - if (kwds && PyDict_Check(kwds) && PyDict_Size(kwds) > 0) { - while (PyDict_Next(kwds, &pos, &key, &value)) { + if (PyDict_Size(infoDict) > 0) { + while (PyDict_Next(infoDict, &pos, &key, &value)) { if (Shiboken::String::check(key) && Shiboken::String::check(value)) { - pData->m_data[Shiboken::String::toCString(key)] = Shiboken::String::toCString(value); + ClassInfo info{Shiboken::String::toCString(key), + Shiboken::String::toCString(value)}; + pData->m_data.append(info); } else { - PyErr_SetString(PyExc_TypeError, "All keys and values provided to ClassInfo() must be strings"); + PyErr_SetString(PyExc_TypeError, "All keys and values provided to ClassInfo() " + "must be strings"); return -1; } } } - return PyErr_Occurred() ? -1 : 0; -} - -void classInfoFree(void *self) -{ - auto pySelf = reinterpret_cast<PyObject *>(self); - auto data = reinterpret_cast<PySideClassInfo *>(self); - - delete data->d; - Py_TYPE(pySelf)->tp_base->tp_free(self); + return PyErr_Occurred() != nullptr ? -1 : 0; } - -} // extern "C" - - -namespace PySide { namespace ClassInfo { - static const char *ClassInfo_SignatureStrings[] = { "PySide6.QtCore.ClassInfo(self,**info:typing.Dict[str,str])", nullptr}; // Sentinel void init(PyObject *module) { - if (InitSignatureStrings(PySideClassInfoTypeF(), ClassInfo_SignatureStrings) < 0) + if (InitSignatureStrings(PySideClassInfo_TypeF(), ClassInfo_SignatureStrings) < 0) return; - Py_INCREF(PySideClassInfoTypeF()); - PyModule_AddObject(module, "ClassInfo", reinterpret_cast<PyObject *>(PySideClassInfoTypeF())); + Py_INCREF(PySideClassInfo_TypeF()); + PyModule_AddObject(module, "ClassInfo", reinterpret_cast<PyObject *>(PySideClassInfo_TypeF())); } bool checkType(PyObject *pyObj) { - if (pyObj) - return PyType_IsSubtype(Py_TYPE(pyObj), PySideClassInfoTypeF()); - return false; + return pyObj != nullptr + && PyType_IsSubtype(Py_TYPE(pyObj), PySideClassInfo_TypeF()) != 0; } -QMap<QByteArray, QByteArray> getMap(PySideClassInfo *obj) +ClassInfoList getClassInfoList(PyObject *decorator) { - return obj->d->m_data; + auto *pData = PySide::ClassDecorator::DecoratorPrivate::get<ClassInfoPrivate>(decorator); + return pData->m_data; +} + +bool setClassInfo(PyTypeObject *type, const QByteArray &key, + const QByteArray &value) +{ + auto *userData = PySide::retrieveTypeUserData(type); + const bool result = userData != nullptr; + if (result) { + PySide::MetaObjectBuilder &mo = userData->mo; + mo.addInfo(key, value); + } + return result; +} + +bool setClassInfo(PyTypeObject *type, const ClassInfoList &list) +{ + auto *userData = PySide::retrieveTypeUserData(type); + const bool result = userData != nullptr; + if (result) { + PySide::MetaObjectBuilder &mo = userData->mo; + for (const auto &info : list) + mo.addInfo(info.key.constData(), info.value.constData()); + } + return result; } -} //namespace Property -} //namespace PySide +} //namespace PySide::ClassInfo diff --git a/sources/pyside6/libpyside/pysideclassinfo.h b/sources/pyside6/libpyside/pysideclassinfo.h index ff60b91c3..e04865829 100644 --- a/sources/pyside6/libpyside/pysideclassinfo.h +++ b/sources/pyside6/libpyside/pysideclassinfo.h @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of Qt for Python. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// 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 PYSIDE_CLASSINFO_H #define PYSIDE_CLASSINFO_H @@ -44,27 +8,26 @@ #include <sbkpython.h> -#include <QtCore/QMap> #include <QtCore/QByteArray> +#include <QtCore/QList> -extern "C" -{ - extern PYSIDE_API PyTypeObject *PySideClassInfoTypeF(void); +namespace PySide::ClassInfo { - struct PySideClassInfoPrivate; - struct PYSIDE_API PySideClassInfo - { - PyObject_HEAD - PySideClassInfoPrivate* d; - }; +struct ClassInfo +{ + QByteArray key; + QByteArray value; }; -namespace PySide { namespace ClassInfo { +using ClassInfoList = QList<ClassInfo>; PYSIDE_API bool checkType(PyObject* pyObj); -PYSIDE_API QMap<QByteArray, QByteArray> getMap(PySideClassInfo* obj); +PYSIDE_API ClassInfoList getClassInfoList(PyObject *decorator); + +PYSIDE_API bool setClassInfo(PyTypeObject *type, const QByteArray &key, + const QByteArray &value); +PYSIDE_API bool setClassInfo(PyTypeObject *type, const ClassInfoList &list); -} //namespace ClassInfo -} //namespace PySide +} // namespace PySide::ClassInfo #endif diff --git a/sources/pyside6/libpyside/pysideclassinfo_p.h b/sources/pyside6/libpyside/pysideclassinfo_p.h index 021aa58e9..4ef456f76 100644 --- a/sources/pyside6/libpyside/pysideclassinfo_p.h +++ b/sources/pyside6/libpyside/pysideclassinfo_p.h @@ -1,62 +1,36 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of Qt for Python. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// 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 PYSIDE_CLASSINFO_P_H #define PYSIDE_CLASSINFO_P_H #include <sbkpython.h> -#include <QMetaObject> + +#include "pysideclassdecorator_p.h" #include "pysideclassinfo.h" +#include <QtCore/QMetaObject> + struct PySideClassInfo; extern "C" { - -struct PySideClassInfoPrivate { - QMap<QByteArray, QByteArray> m_data; - bool m_alreadyWrapped; -}; +extern PYSIDE_API PyTypeObject *PySideClassInfo_TypeF(void); } // extern "C" -namespace PySide { namespace ClassInfo { +namespace PySide::ClassInfo { + +class ClassInfoPrivate : public PySide::ClassDecorator::DecoratorPrivate +{ +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; + + ClassInfoList m_data; + bool m_alreadyWrapped = false; +}; /** * Init PySide QProperty support system @@ -64,7 +38,6 @@ namespace PySide { namespace ClassInfo { void init(PyObject* module); -} // namespace ClassInfo -} // namespace PySide +} // namespace PySide::ClassInfo #endif diff --git a/sources/pyside6/libpyside/pysidecleanup.h b/sources/pyside6/libpyside/pysidecleanup.h new file mode 100644 index 000000000..cc5bbb0e6 --- /dev/null +++ b/sources/pyside6/libpyside/pysidecleanup.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 PYSIDECLEANUP_H +#define PYSIDECLEANUP_H + +#include <pysidemacros.h> + +namespace PySide +{ + +using CleanupFunction = void(*)(); + +/// Register a function to be called before python dies +PYSIDE_API void registerCleanupFunction(CleanupFunction func); +PYSIDE_API void runCleanupFunctions(); + +} //namespace PySide + +#endif // PYSIDECLEANUP_H diff --git a/sources/pyside6/libpyside/pysideinit.h b/sources/pyside6/libpyside/pysideinit.h new file mode 100644 index 000000000..c623a0d27 --- /dev/null +++ b/sources/pyside6/libpyside/pysideinit.h @@ -0,0 +1,27 @@ +// 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 PYSIDEINIT_H +#define PYSIDEINIT_H + +#include <sbkpython.h> + +#include <pysidemacros.h> + +namespace PySide +{ + +PYSIDE_API void init(PyObject *module); + +/// Registers a dynamic "qt.conf" file with the Qt resource system. +/// +/// This is used in a standalone build, to inform QLibraryInfo of the Qt prefix +/// (where Qt libraries are installed) so that plugins can be successfully loaded. +/// +/// This is also used if PySide runs from inside a conda environment to solve +/// conflicts with the qt.conf installed by Anaconda Qt packages. +PYSIDE_API bool registerInternalQtConf(); + +} //namespace PySide + +#endif // PYSIDEINIT_H diff --git a/sources/pyside6/libpyside/pysidelogging_p.h b/sources/pyside6/libpyside/pysidelogging_p.h new file mode 100644 index 000000000..bf167264d --- /dev/null +++ b/sources/pyside6/libpyside/pysidelogging_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 PYSIDE_LOGGING_P_H +#define PYSIDE_LOGGING_P_H + +#include <QtCore/QLoggingCategory> + +Q_DECLARE_LOGGING_CATEGORY(lcPySide) + +#endif // PYSIDE_LOGGING_P_H diff --git a/sources/pyside6/libpyside/pysidemacros.h b/sources/pyside6/libpyside/pysidemacros.h index fcdfe3c6e..46f64b4ed 100644 --- a/sources/pyside6/libpyside/pysidemacros.h +++ b/sources/pyside6/libpyside/pysidemacros.h @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2020 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of Qt for Python. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2020 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 PYSIDEMACROS_H #define PYSIDEMACROS_H @@ -44,7 +8,6 @@ #define PYSIDE_EXPORT LIBSHIBOKEN_EXPORT #define PYSIDE_IMPORT LIBSHIBOKEN_IMPORT -#define PYSIDE_DEPRECATED(func) SBK_DEPRECATED(func) #ifdef BUILD_LIBPYSIDE # define PYSIDE_API PYSIDE_EXPORT diff --git a/sources/pyside6/libpyside/pysidemetafunction.cpp b/sources/pyside6/libpyside/pysidemetafunction.cpp index 939e52e12..e8173b97d 100644 --- a/sources/pyside6/libpyside/pysidemetafunction.cpp +++ b/sources/pyside6/libpyside/pysidemetafunction.cpp @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of Qt for Python. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// 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 "pysidemetafunction.h" #include "pysidemetafunction_p.h" @@ -58,26 +22,30 @@ struct PySideMetaFunctionPrivate static void functionFree(void *); static PyObject *functionCall(PyObject *, PyObject *, PyObject *); -static PyType_Slot PySideMetaFunctionType_slots[] = { - {Py_tp_call, (void *)functionCall}, - {Py_tp_new, (void *)PyType_GenericNew}, - {Py_tp_free, (void *)functionFree}, - {Py_tp_dealloc, (void *)Sbk_object_dealloc}, - {0, 0} -}; -static PyType_Spec PySideMetaFunctionType_spec = { - "2:PySide6.QtCore.MetaFunction", - sizeof(PySideMetaFunction), - 0, - Py_TPFLAGS_DEFAULT, - PySideMetaFunctionType_slots, -}; - +static PyTypeObject *createMetaFunctionType() +{ + PyType_Slot PySideMetaFunctionType_slots[] = { + {Py_tp_call, reinterpret_cast<void *>(functionCall)}, + {Py_tp_new, reinterpret_cast<void *>(PyType_GenericNew)}, + {Py_tp_free, reinterpret_cast<void *>(functionFree)}, + {Py_tp_dealloc, reinterpret_cast<void *>(Sbk_object_dealloc)}, + {0, nullptr} + }; + + PyType_Spec PySideMetaFunctionType_spec = { + "2:PySide6.QtCore.MetaFunction", + sizeof(PySideMetaFunction), + 0, + Py_TPFLAGS_DEFAULT, + PySideMetaFunctionType_slots, + }; + + return SbkType_FromSpec(&PySideMetaFunctionType_spec); +} -PyTypeObject *PySideMetaFunctionTypeF(void) +PyTypeObject *PySideMetaFunction_TypeF(void) { - static PyTypeObject *type = reinterpret_cast<PyTypeObject *>( - SbkType_FromSpec(&PySideMetaFunctionType_spec)); + static auto *type = createMetaFunctionType(); return type; } @@ -93,13 +61,13 @@ PyObject *functionCall(PyObject *self, PyObject *args, PyObject * /* kw */) PyObject *retVal; if (!PySide::MetaFunction::call(function->d->qobject, function->d->methodIndex, args, &retVal)) - return 0; + return nullptr; return retVal; } } // extern "C" -namespace PySide { namespace MetaFunction { +namespace PySide::MetaFunction { static const char *MetaFunction_SignatureStrings[] = { "PySide6.QtCore.MetaFunction.__call__(self,*args:typing.Any)->typing.Any", @@ -107,28 +75,28 @@ static const char *MetaFunction_SignatureStrings[] = { void init(PyObject *module) { - if (InitSignatureStrings(PySideMetaFunctionTypeF(), MetaFunction_SignatureStrings) < 0) + if (InitSignatureStrings(PySideMetaFunction_TypeF(), MetaFunction_SignatureStrings) < 0) return; - Py_INCREF(PySideMetaFunctionTypeF()); - PyModule_AddObject(module, "MetaFunction", reinterpret_cast<PyObject *>(PySideMetaFunctionTypeF())); + Py_INCREF(PySideMetaFunction_TypeF()); + PyModule_AddObject(module, "MetaFunction", reinterpret_cast<PyObject *>(PySideMetaFunction_TypeF())); } PySideMetaFunction *newObject(QObject *source, int methodIndex) { if (methodIndex >= source->metaObject()->methodCount()) - return 0; + return nullptr; QMetaMethod method = source->metaObject()->method(methodIndex); if ((method.methodType() == QMetaMethod::Slot) || (method.methodType() == QMetaMethod::Method)) { - PySideMetaFunction *function = PyObject_New(PySideMetaFunction, PySideMetaFunctionTypeF()); + PySideMetaFunction *function = PyObject_New(PySideMetaFunction, PySideMetaFunction_TypeF()); function->d = new PySideMetaFunctionPrivate(); function->d->qobject = source; function->d->methodIndex = methodIndex; return function; } - return 0; + return nullptr; } bool call(QObject *self, int methodIndex, PyObject *args, PyObject **retVal) @@ -138,20 +106,20 @@ bool call(QObject *self, int methodIndex, PyObject *args, PyObject **retVal) QList<QByteArray> argTypes = method.parameterTypes(); // args given plus return type - Shiboken::AutoDecRef sequence(PySequence_Fast(args, 0)); - int numArgs = PySequence_Fast_GET_SIZE(sequence.object()) + 1; + Shiboken::AutoDecRef sequence(PySequence_Fast(args, nullptr)); + qsizetype numArgs = PySequence_Fast_GET_SIZE(sequence.object()) + 1; - if (numArgs - 1 > argTypes.count()) { + if (numArgs - 1 > argTypes.size()) { PyErr_Format(PyExc_TypeError, "%s only accepts %d argument(s), %d given!", method.methodSignature().constData(), - argTypes.count(), numArgs - 1); + argTypes.size(), numArgs - 1); return false; } - if (numArgs - 1 < argTypes.count()) { + if (numArgs - 1 < argTypes.size()) { PyErr_Format(PyExc_TypeError, "%s needs %d argument(s), %d given!", method.methodSignature().constData(), - argTypes.count(), numArgs - 1); + argTypes.size(), numArgs - 1); return false; } @@ -165,12 +133,12 @@ bool call(QObject *self, int methodIndex, PyObject *args, PyObject **retVal) else argTypes.prepend(QByteArray()); - int i; - for (i = 0; i < numArgs; ++i) { + int i = 0; + for (; i < numArgs; ++i) { const QByteArray &typeName = argTypes.at(i); // This must happen only when the method hasn't return type. if (typeName.isEmpty()) { - methArgs[i] = 0; + methArgs[i] = nullptr; continue; } @@ -225,7 +193,5 @@ bool call(QObject *self, int methodIndex, PyObject *args, PyObject **retVal) return ok; } - -} //namespace MetaFunction -} //namespace PySide +} //namespace PySide::MetaFunction diff --git a/sources/pyside6/libpyside/pysidemetafunction.h b/sources/pyside6/libpyside/pysidemetafunction.h index f7cc5307b..26a2cfd68 100644 --- a/sources/pyside6/libpyside/pysidemetafunction.h +++ b/sources/pyside6/libpyside/pysidemetafunction.h @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of Qt for Python. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// 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 PYSIDE_METAFUNCTION_H #define PYSIDE_METAFUNCTION_H @@ -48,7 +12,7 @@ extern "C" { - extern PYSIDE_API PyTypeObject *PySideMetaFunctionTypeF(void); + extern PYSIDE_API PyTypeObject *PySideMetaFunction_TypeF(void); struct PySideMetaFunctionPrivate; struct PYSIDE_API PySideMetaFunction @@ -58,7 +22,7 @@ extern "C" }; }; //extern "C" -namespace PySide { namespace MetaFunction { +namespace PySide::MetaFunction { /** * This function creates a MetaFunction object @@ -69,7 +33,6 @@ namespace PySide { namespace MetaFunction { **/ PYSIDE_API PySideMetaFunction *newObject(QObject *obj, int methodIndex); -} //namespace MetaFunction -} //namespace PySide +} //namespace PySide::MetaFunction #endif diff --git a/sources/pyside6/libpyside/pysidemetafunction_p.h b/sources/pyside6/libpyside/pysidemetafunction_p.h index c67233857..0207ec3a2 100644 --- a/sources/pyside6/libpyside/pysidemetafunction_p.h +++ b/sources/pyside6/libpyside/pysidemetafunction_p.h @@ -1,54 +1,18 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of Qt for Python. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// 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 PYSIDE_METAFUNCTION_P_H #define PYSIDE_METAFUNCTION_P_H #include <sbkpython.h> -#include <QtCore/QtGlobal> +#include <QtCore/qtconfigmacros.h> QT_BEGIN_NAMESPACE class QObject; QT_END_NAMESPACE -namespace PySide { namespace MetaFunction { +namespace PySide::MetaFunction { void init(PyObject *module); /** @@ -56,7 +20,6 @@ namespace PySide { namespace MetaFunction { */ bool call(QObject *self, int methodIndex, PyObject *args, PyObject **retVal = nullptr); -} //namespace MetaFunction -} //namespace PySide +} //namespace PySide::MetaFunction #endif diff --git a/sources/pyside6/libpyside/pysidemetatype.h b/sources/pyside6/libpyside/pysidemetatype.h new file mode 100644 index 000000000..85e70f7c9 --- /dev/null +++ b/sources/pyside6/libpyside/pysidemetatype.h @@ -0,0 +1,26 @@ +// 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 PYSIDEMETATYPE_H +#define PYSIDEMETATYPE_H + +#include <sbkpython.h> + +#include <pysidemacros.h> + +#include <QtCore/qtconfigmacros.h> + +QT_FORWARD_DECLARE_CLASS(QMetaType) + +namespace PySide +{ + +/// Returns the QMetaType matching a PyTypeObject +/// \param +/// \param type TypeObject +/// \return QMetaType +PYSIDE_API QMetaType qMetaTypeFromPyType(PyTypeObject *type); + +} //namespace PySide + +#endif // PYSIDEMETATYPE_H diff --git a/sources/pyside6/libpyside/pysideproperty.cpp b/sources/pyside6/libpyside/pysideproperty.cpp index c00d90c2d..3720815db 100644 --- a/sources/pyside6/libpyside/pysideproperty.cpp +++ b/sources/pyside6/libpyside/pysideproperty.cpp @@ -1,50 +1,14 @@ -/**************************************************************************** -** -** Copyright (C) 2020 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of Qt for Python. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2020 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 <sbkpython.h> #include "pysideproperty.h" #include "pysideproperty_p.h" -#include "dynamicqmetaobject_p.h" #include "pysidesignal.h" #include "pysidesignal_p.h" #include <shiboken.h> +#include <pep384ext.h> #include <signature.h> using namespace Shiboken; @@ -74,14 +38,14 @@ static PyObject *qProperty_freset(PyObject *, void *); static PyObject *qProperty_fdel(PyObject *, void *); static PyMethodDef PySidePropertyMethods[] = { - {"getter", (PyCFunction)qPropertyGetter, METH_O, 0}, - {"setter", (PyCFunction)qPropertySetter, METH_O, 0}, - {"resetter", (PyCFunction)qPropertyResetter, METH_O, 0}, - {"deleter", (PyCFunction)qPropertyDeleter, METH_O, 0}, + {"getter", reinterpret_cast<PyCFunction>(qPropertyGetter), METH_O, nullptr}, + {"setter", reinterpret_cast<PyCFunction>(qPropertySetter), METH_O, nullptr}, + {"resetter", reinterpret_cast<PyCFunction>(qPropertyResetter), METH_O, nullptr}, + {"deleter", reinterpret_cast<PyCFunction>(qPropertyDeleter), METH_O, nullptr}, // Synonyms from Qt - {"read", (PyCFunction)qPropertyGetter, METH_O, 0}, - {"write", (PyCFunction)qPropertySetter, METH_O, 0}, - {0, 0, 0, 0} + {"read", reinterpret_cast<PyCFunction>(qPropertyGetter), METH_O, nullptr}, + {"write", reinterpret_cast<PyCFunction>(qPropertySetter), METH_O, nullptr}, + {nullptr, nullptr, 0, nullptr} }; static PyGetSetDef PySidePropertyType_getset[] = { @@ -95,97 +59,150 @@ static PyGetSetDef PySidePropertyType_getset[] = { {nullptr, nullptr, nullptr, nullptr, nullptr} }; -static PyType_Slot PySidePropertyType_slots[] = { - {Py_tp_dealloc, (void *)qpropertyDeAlloc}, - {Py_tp_call, (void *)qPropertyCall}, - {Py_tp_traverse, (void *)qpropertyTraverse}, - {Py_tp_clear, (void *)qpropertyClear}, - {Py_tp_methods, (void *)PySidePropertyMethods}, - {Py_tp_init, (void *)qpropertyTpInit}, - {Py_tp_new, (void *)qpropertyTpNew}, - {Py_tp_getset, PySidePropertyType_getset}, - {0, 0} -}; -// Dotted modulename is crucial for SbkType_FromSpec to work. Is this name right? -static PyType_Spec PySidePropertyType_spec = { - "2:PySide6.QtCore.Property", - sizeof(PySideProperty), - 0, - Py_TPFLAGS_DEFAULT|Py_TPFLAGS_HAVE_GC|Py_TPFLAGS_BASETYPE, - PySidePropertyType_slots, -}; +static PyTypeObject *createPropertyType() +{ + PyType_Slot PySidePropertyType_slots[] = { + {Py_tp_dealloc, reinterpret_cast<void *>(qpropertyDeAlloc)}, + {Py_tp_call, reinterpret_cast<void *>(qPropertyCall)}, + {Py_tp_traverse, reinterpret_cast<void *>(qpropertyTraverse)}, + {Py_tp_clear, reinterpret_cast<void *>(qpropertyClear)}, + {Py_tp_methods, reinterpret_cast<void *>(PySidePropertyMethods)}, + {Py_tp_init, reinterpret_cast<void *>(qpropertyTpInit)}, + {Py_tp_new, reinterpret_cast<void *>(qpropertyTpNew)}, + {Py_tp_getset, PySidePropertyType_getset}, + {Py_tp_del, reinterpret_cast<void *>(PyObject_GC_Del)}, + {0, nullptr} + }; + PyType_Spec PySidePropertyType_spec = { + "2:PySide6.QtCore.Property", + sizeof(PySideProperty), + 0, + Py_TPFLAGS_DEFAULT|Py_TPFLAGS_HAVE_GC|Py_TPFLAGS_BASETYPE, + PySidePropertyType_slots, + }; -PyTypeObject *PySidePropertyTypeF(void) + return SbkType_FromSpec(&PySidePropertyType_spec); +} + +PyTypeObject *PySideProperty_TypeF(void) { - static PyTypeObject *type = reinterpret_cast<PyTypeObject *>( - SbkType_FromSpec(&PySidePropertyType_spec)); + static auto *type = createPropertyType(); return type; } -static void qpropertyMetaCall(PySideProperty *pp, PyObject *self, QMetaObject::Call call, void **args) +PySidePropertyPrivate::PySidePropertyPrivate() noexcept = default; +PySidePropertyPrivate::~PySidePropertyPrivate() = default; + +PyObject *PySidePropertyPrivate::getValue(PyObject *source) +{ + if (fget) { + Shiboken::AutoDecRef args(PyTuple_New(1)); + Py_INCREF(source); + PyTuple_SET_ITEM(args, 0, source); + return PyObject_CallObject(fget, args); + } + return nullptr; +} + +int PySidePropertyPrivate::setValue(PyObject *source, PyObject *value) +{ + if (fset && value) { + Shiboken::AutoDecRef args(PyTuple_New(2)); + PyTuple_SET_ITEM(args, 0, source); + PyTuple_SET_ITEM(args, 1, value); + Py_INCREF(source); + Py_INCREF(value); + Shiboken::AutoDecRef result(PyObject_CallObject(fset, args)); + return (result.isNull() ? -1 : 0); + } + if (fdel) { + Shiboken::AutoDecRef args(PyTuple_New(1)); + PyTuple_SET_ITEM(args, 0, source); + Py_INCREF(source); + Shiboken::AutoDecRef result(PyObject_CallObject(fdel, args)); + return (result.isNull() ? -1 : 0); + } + PyErr_SetString(PyExc_AttributeError, "Attribute is read only"); + return -1; +} + +int PySidePropertyPrivate::reset(PyObject *source) { - Shiboken::Conversions::SpecificConverter converter(pp->d->typeName); - Q_ASSERT(converter); + if (freset) { + Shiboken::AutoDecRef args(PyTuple_New(1)); + Py_INCREF(source); + PyTuple_SET_ITEM(args, 0, source); + Shiboken::AutoDecRef result(PyObject_CallObject(freset, args)); + return (result.isNull() ? -1 : 0); + } + return -1; +} - switch(call) { - case QMetaObject::ReadProperty: - { - Shiboken::GilState gil; - PyObject *value = PySide::Property::getValue(pp, self); - if (value) { - converter.toCpp(value, args[0]); - Py_DECREF(value); +void PySidePropertyPrivate::metaCall(PyObject *source, QMetaObject::Call call, void **args) +{ + switch (call) { + case QMetaObject::ReadProperty: { + AutoDecRef value(getValue(source)); + auto *obValue = value.object(); + if (obValue) { + Conversions::SpecificConverter converter(typeName); + if (converter) { + converter.toCpp(obValue, args[0]); + } else { + // PYSIDE-2160: Report an unknown type name to the caller `qtPropertyMetacall`. + PyErr_SetObject(PyExc_StopIteration, obValue); } - break; } - - case QMetaObject::WriteProperty: - { - Shiboken::GilState gil; - Shiboken::AutoDecRef value(converter.toPython(args[0])); - PySide::Property::setValue(pp, self, value); - break; + } + break; + + case QMetaObject::WriteProperty: { + Conversions::SpecificConverter converter(typeName); + if (converter) { + AutoDecRef value(converter.toPython(args[0])); + setValue(source, value); + } else { + // PYSIDE-2160: Report an unknown type name to the caller `qtPropertyMetacall`. + PyErr_SetNone(PyExc_StopIteration); } + } + break; - case QMetaObject::ResetProperty: - { - Shiboken::GilState gil; - PySide::Property::reset(pp, self); - break; - } + case QMetaObject::ResetProperty: + reset(source); + break; - // just to avoid gcc warnings - case QMetaObject::BindableProperty: - case QMetaObject::InvokeMetaMethod: - case QMetaObject::CreateInstance: - case QMetaObject::IndexOfMethod: - case QMetaObject::RegisterPropertyMetaType: - case QMetaObject::RegisterMethodArgumentMetaType: - break; + default: + break; } } - static PyObject *qpropertyTpNew(PyTypeObject *subtype, PyObject * /* args */, PyObject * /* kwds */) { - PySideProperty *me = reinterpret_cast<PySideProperty *>(subtype->tp_alloc(subtype, 0)); + auto *me = PepExt_TypeCallAlloc<PySideProperty>(subtype, 0); me->d = new PySidePropertyPrivate; return reinterpret_cast<PyObject *>(me); } static int qpropertyTpInit(PyObject *self, PyObject *args, PyObject *kwds) { - PyObject *type = nullptr; + PyObject *type{}; auto data = reinterpret_cast<PySideProperty *>(self); PySidePropertyPrivate *pData = data->d; - pData->metaCallHandler = &qpropertyMetaCall; static const char *kwlist[] = {"type", "fget", "fset", "freset", "fdel", "doc", "notify", "designable", "scriptable", "stored", - "user", "constant", "final", 0}; + "user", "constant", "final", nullptr}; char *doc{}; + Py_CLEAR(pData->pyTypeObject); + Py_CLEAR(pData->fget); + Py_CLEAR(pData->fset); + Py_CLEAR(pData->freset); + Py_CLEAR(pData->fdel); + Py_CLEAR(pData->notify); + if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|OOOOsObbbbbb:QtCore.Property", const_cast<char **>(kwlist), @@ -207,6 +224,8 @@ static int qpropertyTpInit(PyObject *self, PyObject *args, PyObject *kwds) else pData->doc.clear(); + pData->pyTypeObject = type; + Py_XINCREF(pData->pyTypeObject); pData->typeName = PySide::Signal::getTypeName(type); if (pData->typeName.isEmpty()) @@ -239,13 +258,17 @@ static void qpropertyDeAlloc(PyObject *self) // This was not needed before Python 3.8 (Python issue 35810) Py_DECREF(Py_TYPE(self)); } - Py_TYPE(self)->tp_free(self); + PyObject_GC_UnTrack(self); + PepExt_TypeCallFree(self); } +// Create a copy of the property to prevent the @property.setter from modifying +// the property in place and avoid strange side effects in derived classes +// (cf https://bugs.python.org/issue1620). static PyObject * _property_copy(PyObject *old, PyObject *get, PyObject *set, PyObject *reset, PyObject *del) { - PySideProperty *pold = reinterpret_cast<PySideProperty *>(old); + auto *pold = reinterpret_cast<PySideProperty *>(old); PySidePropertyPrivate *pData = pold->d; AutoDecRef type(PyObject_Type(old)); @@ -277,9 +300,8 @@ _property_copy(PyObject *old, PyObject *get, PyObject *set, PyObject *reset, PyO auto notify = pData->notify ? pData->notify : Py_None; - PyObject *typeName = String::fromCString(pData->typeName); PyObject *obNew = PyObject_CallFunction(type, const_cast<char *>("OOOOOsO" "bbb" "bbb"), - typeName, get, set, reset, del, doc.data(), notify, + pData->pyTypeObject, get, set, reset, del, doc.data(), notify, pData->designable, pData->scriptable, pData->stored, pData->user, pData->constant, pData->final); @@ -357,10 +379,10 @@ static PyObject *qPropertyDocGet(PyObject *self, void *) if (pData->fget != nullptr) { // PYSIDE-1019: Fetch the default `__doc__` from fget. We do it late. AutoDecRef get_doc(PyObject_GetAttr(pData->fget, PyMagicName::doc())); - if (!get_doc.isNull()) { + if (!get_doc.isNull() && get_doc.object() != Py_None) { pData->doc = String::toCString(get_doc); pData->getter_doc = true; - if (Py_TYPE(self) == PySidePropertyTypeF()) + if (Py_TYPE(self) == PySideProperty_TypeF()) return qPropertyDocGet(self, nullptr); /* * If this is a property subclass, put __doc__ in dict of the @@ -400,6 +422,7 @@ static int qpropertyTraverse(PyObject *self, visitproc visit, void *arg) Py_VISIT(data->freset); Py_VISIT(data->fdel); Py_VISIT(data->notify); + Py_VISIT(data->pyTypeObject); return 0; } @@ -414,7 +437,7 @@ static int qpropertyClear(PyObject *self) Py_CLEAR(data->freset); Py_CLEAR(data->fdel); Py_CLEAR(data->notify); - + Py_CLEAR(data->pyTypeObject); delete data; reinterpret_cast<PySideProperty *>(self)->d = nullptr; @@ -423,16 +446,14 @@ static int qpropertyClear(PyObject *self) } // extern "C" -namespace { - static PyObject *getFromType(PyTypeObject *type, PyObject *name) { - PyObject *attr = nullptr; - attr = PyDict_GetItem(type->tp_dict, name); + AutoDecRef tpDict(PepType_GetDict(type)); + auto *attr = PyDict_GetItem(tpDict.object(), name); if (!attr) { PyObject *bases = type->tp_bases; - int size = PyTuple_GET_SIZE(bases); - for(int i=0; i < size; i++) { + const Py_ssize_t size = PyTuple_GET_SIZE(bases); + for (Py_ssize_t i = 0; i < size; ++i) { PyObject *base = PyTuple_GET_ITEM(bases, i); attr = getFromType(reinterpret_cast<PyTypeObject *>(base), name); if (attr) @@ -442,92 +463,51 @@ static PyObject *getFromType(PyTypeObject *type, PyObject *name) return attr; } -} //namespace - - -namespace PySide { namespace Property { +namespace PySide::Property { static const char *Property_SignatureStrings[] = { "PySide6.QtCore.Property(self,type:type,fget:typing.Callable=None,fset:typing.Callable=None," "freset:typing.Callable=None,fdel:typing.Callable=None,doc:str=None," "notify:typing.Callable=None,designable:bool=True,scriptable:bool=True," - "stored:bool=True,user:bool=False,constant:bool=False,final:bool=False)" - "->PySide6.QtCore.Property", - "PySide6.QtCore.Property.deleter(self,func:typing.Callable)", - "PySide6.QtCore.Property.fdel(self)->typing.Callable", - "PySide6.QtCore.Property.fget(self)->typing.Callable", - "PySide6.QtCore.Property.freset(self)->typing.Callable", - "PySide6.QtCore.Property.fset(self)->typing.Callable", - "PySide6.QtCore.Property.getter(self,func:typing.Callable)", - "PySide6.QtCore.Property.read(self,func:typing.Callable)", - "PySide6.QtCore.Property.setter(self,func:typing.Callable)", - "PySide6.QtCore.Property.write(self,func:typing.Callable)", + "stored:bool=True,user:bool=False,constant:bool=False,final:bool=False)", + "PySide6.QtCore.Property.deleter(self,fdel:typing.Callable)->PySide6.QtCore.Property", + "PySide6.QtCore.Property.getter(self,fget:typing.Callable)->PySide6.QtCore.Property", + "PySide6.QtCore.Property.read(self,fget:typing.Callable)->PySide6.QtCore.Property", + "PySide6.QtCore.Property.setter(self,fset:typing.Callable)->PySide6.QtCore.Property", + "PySide6.QtCore.Property.write(self,fset:typing.Callable)->PySide6.QtCore.Property", + "PySide6.QtCore.Property.__call__(self, func:typing.Callable)->PySide6.QtCore.Property", nullptr}; // Sentinel void init(PyObject *module) { - if (InitSignatureStrings(PySidePropertyTypeF(), Property_SignatureStrings) < 0) + if (InitSignatureStrings(PySideProperty_TypeF(), Property_SignatureStrings) < 0) return; - Py_INCREF(PySidePropertyTypeF()); - PyModule_AddObject(module, "Property", reinterpret_cast<PyObject *>(PySidePropertyTypeF())); + Py_INCREF(PySideProperty_TypeF()); + PyModule_AddObject(module, "Property", reinterpret_cast<PyObject *>(PySideProperty_TypeF())); } bool checkType(PyObject *pyObj) { if (pyObj) { - return PyType_IsSubtype(Py_TYPE(pyObj), PySidePropertyTypeF()); + return PyType_IsSubtype(Py_TYPE(pyObj), PySideProperty_TypeF()); } return false; } -int setValue(PySideProperty *self, PyObject *source, PyObject *value) +PyObject *getValue(PySideProperty *self, PyObject *source) { - PyObject *fset = self->d->fset; - if (fset && value) { - Shiboken::AutoDecRef args(PyTuple_New(2)); - PyTuple_SET_ITEM(args, 0, source); - PyTuple_SET_ITEM(args, 1, value); - Py_INCREF(source); - Py_INCREF(value); - Shiboken::AutoDecRef result(PyObject_CallObject(fset, args)); - return (result.isNull() ? -1 : 0); - } - PyObject *fdel = self->d->fdel; - if (fdel) { - Shiboken::AutoDecRef args(PyTuple_New(1)); - PyTuple_SET_ITEM(args, 0, source); - Py_INCREF(source); - Shiboken::AutoDecRef result(PyObject_CallObject(fdel, args)); - return (result.isNull() ? -1 : 0); - } - PyErr_SetString(PyExc_AttributeError, "Attibute read only"); - return -1; + return self->d->getValue(source); } -PyObject *getValue(PySideProperty *self, PyObject *source) +int setValue(PySideProperty *self, PyObject *source, PyObject *value) { - PyObject *fget = self->d->fget; - if (fget) { - Shiboken::AutoDecRef args(PyTuple_New(1)); - Py_INCREF(source); - PyTuple_SET_ITEM(args, 0, source); - return PyObject_CallObject(fget, args); - } - return 0; + return self->d->setValue(source, value); } int reset(PySideProperty *self, PyObject *source) { - PyObject *freset = self->d->freset; - if (freset) { - Shiboken::AutoDecRef args(PyTuple_New(1)); - Py_INCREF(source); - PyTuple_SET_ITEM(args, 0, source); - Shiboken::AutoDecRef result(PyObject_CallObject(freset, args)); - return (result.isNull() ? -1 : 0); - } - return -1; + return self->d->reset(source); } const char *getTypeName(const PySideProperty *self) @@ -548,7 +528,7 @@ PySideProperty *getObject(PyObject *source, PyObject *name) if (!attr) PyErr_Clear(); //Clear possible error caused by PyObject_GenericGetAttr - return 0; + return nullptr; } bool isReadable(const PySideProperty * /* self */) @@ -558,12 +538,12 @@ bool isReadable(const PySideProperty * /* self */) bool isWritable(const PySideProperty *self) { - return (self->d->fset != 0); + return self->d->fset != nullptr; } bool hasReset(const PySideProperty *self) { - return (self->d->freset != 0); + return self->d->freset != nullptr; } bool isDesignable(const PySideProperty *self) @@ -599,34 +579,22 @@ bool isFinal(const PySideProperty *self) const char *getNotifyName(PySideProperty *self) { if (self->d->notifySignature.isEmpty()) { - PyObject *str = PyObject_Str(self->d->notify); + AutoDecRef str(PyObject_Str(self->d->notify)); self->d->notifySignature = Shiboken::String::toCString(str); - Py_DECREF(str); } return self->d->notifySignature.isEmpty() ? nullptr : self->d->notifySignature.constData(); } -void setMetaCallHandler(PySideProperty *self, MetaCallHandler handler) -{ - self->d->metaCallHandler = handler; -} - void setTypeName(PySideProperty *self, const char *typeName) { self->d->typeName = typeName; } -void setUserData(PySideProperty *self, void *data) -{ - self->d->userData = data; -} - -void *userData(PySideProperty *self) +PyObject *getTypeObject(const PySideProperty *self) { - return self->d->userData; + return self->d->pyTypeObject; } -} //namespace Property -} //namespace PySide +} //namespace PySide::Property diff --git a/sources/pyside6/libpyside/pysideproperty.h b/sources/pyside6/libpyside/pysideproperty.h index 4a467b186..a572efe45 100644 --- a/sources/pyside6/libpyside/pysideproperty.h +++ b/sources/pyside6/libpyside/pysideproperty.h @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of Qt for Python. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// 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 PYSIDE_PROPERTY_H #define PYSIDE_PROPERTY_H @@ -46,11 +10,12 @@ #include <QtCore/QMetaObject> +class PySidePropertyPrivate; + extern "C" { - extern PYSIDE_API PyTypeObject *PySidePropertyTypeF(void); + extern PYSIDE_API PyTypeObject *PySideProperty_TypeF(void); - struct PySidePropertyPrivate; struct PYSIDE_API PySideProperty { PyObject_HEAD @@ -58,9 +23,7 @@ extern "C" }; }; -namespace PySide { namespace Property { - -typedef void (*MetaCallHandler)(PySideProperty*,PyObject*,QMetaObject::Call, void**); +namespace PySide::Property { PYSIDE_API bool checkType(PyObject *pyObj); @@ -103,14 +66,8 @@ PYSIDE_API const char *getNotifyName(PySideProperty *self); **/ PYSIDE_API PySideProperty *getObject(PyObject *source, PyObject *name); -PYSIDE_API void setMetaCallHandler(PySideProperty *self, MetaCallHandler handler); - PYSIDE_API void setTypeName(PySideProperty *self, const char *typeName); -PYSIDE_API void setUserData(PySideProperty *self, void *data); -PYSIDE_API void* userData(PySideProperty *self); - -} //namespace Property -} //namespace PySide +} //namespace PySide::Property #endif diff --git a/sources/pyside6/libpyside/pysideproperty_p.h b/sources/pyside6/libpyside/pysideproperty_p.h index e7b6e4d77..10cb3ce87 100644 --- a/sources/pyside6/libpyside/pysideproperty_p.h +++ b/sources/pyside6/libpyside/pysideproperty_p.h @@ -1,56 +1,39 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of Qt for Python. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// 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 PYSIDE_QPROPERTY_P_H #define PYSIDE_QPROPERTY_P_H #include <sbkpython.h> -#include <QtCore/QByteArray> -#include <QMetaObject> + #include "pysideproperty.h" +#include <pysidemacros.h> + +#include <QtCore/QByteArray> +#include <QtCore/qtclasshelpermacros.h> +#include <QtCore/QMetaObject> struct PySideProperty; -struct PySidePropertyPrivate +class PYSIDE_API PySidePropertyPrivate { +public: + + Q_DISABLE_COPY_MOVE(PySidePropertyPrivate) + + PySidePropertyPrivate() noexcept; + virtual ~PySidePropertyPrivate(); + + virtual void metaCall(PyObject *source, QMetaObject::Call call, void **args); + + PyObject *getValue(PyObject *source); + int setValue(PyObject *source, PyObject *value); + int reset(PyObject *source); + QByteArray typeName; - PySide::Property::MetaCallHandler metaCallHandler = nullptr; + // Type object: A real PyTypeObject ("@Property(int)") or a string + // "@Property('QVariant')". + PyObject *pyTypeObject = nullptr; PyObject *fget = nullptr; PyObject *fset = nullptr; PyObject *freset = nullptr; @@ -65,10 +48,9 @@ struct PySidePropertyPrivate bool user = false; bool constant = false; bool final = false; - void *userData = nullptr; }; -namespace PySide { namespace Property { +namespace PySide::Property { /** * Init PySide QProperty support system @@ -176,7 +158,12 @@ bool isConstant(const PySideProperty* self); **/ bool isFinal(const PySideProperty* self); -} // namespace Property -} // namespace PySide +/// This function returns the type object of the property. It is either a real +/// PyTypeObject ("@Property(int)") or a string "@Property('QVariant')". +/// @param self The property object +/// @return type object +PyObject *getTypeObject(const PySideProperty* self); + +} // namespace PySide::Property #endif diff --git a/sources/pyside6/libpyside/pysideqapp.h b/sources/pyside6/libpyside/pysideqapp.h new file mode 100644 index 000000000..5543d83d8 --- /dev/null +++ b/sources/pyside6/libpyside/pysideqapp.h @@ -0,0 +1,19 @@ +// 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 PYSIDEQAPP_H +#define PYSIDEQAPP_H + +#include <pysidemacros.h> + +namespace PySide +{ + +PYSIDE_API void initQApp(); + +/// Destroy a QCoreApplication taking care of destroy all instances of QObject first. +PYSIDE_API void destroyQCoreApplication(); + +} //namespace PySide + +#endif // PYSIDEQPP_H diff --git a/sources/pyside6/libpyside/pysideqenum.cpp b/sources/pyside6/libpyside/pysideqenum.cpp index 07a548cb6..c0479160f 100644 --- a/sources/pyside6/libpyside/pysideqenum.cpp +++ b/sources/pyside6/libpyside/pysideqenum.cpp @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2020 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of Qt for Python. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2020 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 <shiboken.h> @@ -53,7 +17,7 @@ extern "C" { using namespace Shiboken; -static PyObject *analyzePyEnum(PyObject *pyenum, PyObject *container = nullptr) +static PyObject *analyzePyEnum(PyObject *pyenum) { /* * This is the straight-forward implementation of QEnum/QFlag. It does no @@ -99,7 +63,7 @@ static PyObject *analyzePyEnum(PyObject *pyenum, PyObject *container = nullptr) AutoDecRef value(PyObject_GetAttr(member, Shiboken::PyName::value())); if (value.isNull()) return nullptr; - if (!PyInt_Check(value)) { // int/long cheating + if (!PyLong_Check(value)) { PyErr_Format(PyExc_TypeError, "QEnum expected an int value as '%.200s', got '%.200s'", Shiboken::String::toCString(key), Py_TYPE(value)->tp_name); @@ -113,9 +77,9 @@ static Py_ssize_t get_lineno() { PyObject *frame = reinterpret_cast<PyObject *>(PyEval_GetFrame()); // borrowed ref AutoDecRef ob_lineno(PyObject_GetAttr(frame, Shiboken::PyName::f_lineno())); - if (ob_lineno.isNull() || !PyInt_Check(ob_lineno)) // int/long cheating + if (ob_lineno.isNull() || !PyLong_Check(ob_lineno)) return -1; - return PyInt_AsSsize_t(ob_lineno); // int/long cheating + return PyLong_AsSsize_t(ob_lineno); } static bool is_module_code() @@ -133,7 +97,7 @@ static bool is_module_code() } // extern "C" -namespace PySide { namespace QEnum { +namespace PySide::QEnum { static std::map<int, PyObject *> enumCollector; @@ -147,38 +111,14 @@ int isFlag(PyObject *obType) * The function is called in MetaObjectBuilderPrivate::parsePythonType * again to obtain the flag value. */ - if (!PyType_Check(obType)) { - PyErr_Format(PyExc_TypeError, "a class argument was expected, not a '%.200s' instance", - Py_TYPE(obType)->tp_name); - return -1; - }; - auto *type = reinterpret_cast<PyTypeObject *>(obType); - PyObject *mro = type->tp_mro; - Py_ssize_t i, n = PyTuple_GET_SIZE(mro); - bool right_module = false; - bool have_enum = false; - bool have_flag = false; - bool have_members = PyObject_HasAttr(obType, PyMagicName::members()); - for (i = 0; i < n; i++) { - obType = PyTuple_GET_ITEM(mro, i); - type = reinterpret_cast<PyTypeObject *>(obType); - AutoDecRef mod(PyObject_GetAttr(obType, PyMagicName::module())); - QByteArray cmod = String::toCString(mod); - QByteArray cname = type->tp_name; - if (cmod == "enum") { - right_module = true; - if (cname == "Enum") - have_enum = true; - else if (cname == "Flag") - have_flag = true; - } - } - if (!right_module || !(have_enum || have_flag) || !have_members) { + int res = enumIsFlag(obType); + if (res < 0) { + auto *type = reinterpret_cast<PyTypeObject *>(obType); PyErr_Format(PyExc_TypeError, "type %.200s does not inherit from 'Enum' or 'Flag'", type->tp_name); return -1; } - return bool(have_flag); + return bool(res); } PyObject *QEnumMacro(PyObject *pyenum, bool flag) @@ -223,7 +163,7 @@ std::vector<PyObject *> resolveDelayedQEnums(PyTypeObject *containerType) { /* * This is the internal interface of 'QEnum'. - * It is called at the end of the meta class call 'SbkObjectTypeTpNew' via + * It is called at the end of the meta class call 'SbkObjectType_tp_new' via * MetaObjectBuilderPrivate::parsePythonType and resolves the collected * Python Enum arguments. The result is then registered. */ @@ -251,8 +191,7 @@ std::vector<PyObject *> resolveDelayedQEnums(PyTypeObject *containerType) return result; } -} // namespace Enum -} // namespace Shiboken +} // namespace Shiboken::Enum // /////////////////////////////////////////////////////////////// diff --git a/sources/pyside6/libpyside/pysideqenum.h b/sources/pyside6/libpyside/pysideqenum.h index fc4e55982..c3483e63e 100644 --- a/sources/pyside6/libpyside/pysideqenum.h +++ b/sources/pyside6/libpyside/pysideqenum.h @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2020 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of Qt for Python. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2020 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 PYSIDE_QENUM_H #define PYSIDE_QENUM_H @@ -43,7 +7,7 @@ #include <pysidemacros.h> #include <vector> -namespace PySide { namespace QEnum { +namespace PySide::QEnum { // PYSIDE-957: Support the QEnum macro PYSIDE_API PyObject *QEnumMacro(PyObject *, bool); @@ -51,7 +15,6 @@ PYSIDE_API int isFlag(PyObject *); PYSIDE_API std::vector<PyObject *> resolveDelayedQEnums(PyTypeObject *); PYSIDE_API void init(); -} // namespace QEnum -} // namespace PySide +} // namespace PySide::QEnum #endif diff --git a/sources/pyside6/libpyside/pysideqflags.cpp b/sources/pyside6/libpyside/pysideqflags.cpp deleted file mode 100644 index b07a73332..000000000 --- a/sources/pyside6/libpyside/pysideqflags.cpp +++ /dev/null @@ -1,205 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of Qt for Python. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#include "pysideqflags.h" - -#include <autodecref.h> -#include <sbkenum.h> - -extern "C" { - struct SbkConverter; - - struct PySideQFlagsTypePrivate - { - SbkConverter **converterPtr; - SbkConverter *converter; - }; - /** - * Type of all QFlags - */ - struct PySideQFlagsType - { - PyTypeObject type; - }; - - #define PYSIDE_QFLAGS(X) reinterpret_cast<PySideQFlagsObject *>(X) - - PyObject *PySideQFlagsNew(PyTypeObject *type, PyObject *args, PyObject * /* kwds */) - { - long val = 0; - if (PyTuple_GET_SIZE(args)) { - PyObject *arg = PyTuple_GET_ITEM(args, 0); - if (Shiboken::isShibokenEnum(arg)) {// faster call - val = Shiboken::Enum::getValue(arg); - } else if (PyNumber_Check(arg)) { - Shiboken::AutoDecRef number(PyNumber_Long(arg)); - val = PyLong_AsLong(number); - } else { - PyErr_SetString(PyExc_TypeError,"QFlags must be created using enums or numbers."); - return 0; - } - } - PySideQFlagsObject *self = PyObject_New(PySideQFlagsObject, type); - self->ob_value = val; - return reinterpret_cast<PyObject *>(self); - } - - static long getNumberValue(PyObject *v) - { - Shiboken::AutoDecRef number(PyNumber_Long(v)); - return PyLong_AsLong(number); - } - - static PyObject *qflag_int(PyObject *self) - { - return PyLong_FromLong(reinterpret_cast<PySideQFlagsObject*>(self)->ob_value); - } - - PyObject *PySideQFlagsRichCompare(PyObject *self, PyObject *other, int op) - { - int result = 0; - if (!PyNumber_Check(other)) { - PyErr_BadArgument(); - return NULL; - } - - long valA = PYSIDE_QFLAGS(self)->ob_value; - long valB = getNumberValue(other); - - if (self == other) { - result = 1; - } else { - switch (op) { - case Py_EQ: - result = (valA == valB); - break; - case Py_NE: - result = (valA != valB); - break; - case Py_LE: - result = (valA <= valB); - break; - case Py_GE: - result = (valA >= valB); - break; - case Py_LT: - result = (valA < valB); - break; - case Py_GT: - result = (valA > valB); - break; - default: - PyErr_BadArgument(); - return NULL; - } - } - if (result) - Py_RETURN_TRUE; - else - Py_RETURN_FALSE; - } -} - -namespace PySide -{ -namespace QFlags -{ - static PyType_Slot SbkNewQFlagsType_slots[] = { - {Py_nb_bool, 0}, - {Py_nb_invert, 0}, - {Py_nb_and, 0}, - {Py_nb_xor, 0}, - {Py_nb_or, 0}, - {Py_nb_int, reinterpret_cast<void*>(qflag_int)}, - {Py_nb_index, reinterpret_cast<void*>(qflag_int)}, - {Py_tp_new, (void *)PySideQFlagsNew}, - {Py_tp_richcompare, (void *)PySideQFlagsRichCompare}, - {Py_tp_dealloc, (void *)Sbk_object_dealloc}, - {0, 0} - }; - static PyType_Spec SbkNewQFlagsType_spec = { - "missing QFlags name", // to be inserted later - sizeof(PySideQFlagsObject), - 0, - Py_TPFLAGS_DEFAULT|Py_TPFLAGS_CHECKTYPES, - SbkNewQFlagsType_slots, - }; - - PyTypeObject *create(const char *name, PyType_Slot numberMethods[]) - { - char qualname[200]; - // PYSIDE-747: Here we insert now the full class name. - strcpy(qualname, name); - // Careful: SbkType_FromSpec does not allocate the string. - PyType_Spec newspec; - newspec.name = strdup(qualname); - newspec.basicsize = SbkNewQFlagsType_spec.basicsize; - newspec.itemsize = SbkNewQFlagsType_spec.itemsize; - newspec.flags = SbkNewQFlagsType_spec.flags; - int idx = -1; - while (numberMethods[++idx].slot) { - assert(SbkNewQFlagsType_slots[idx].slot == numberMethods[idx].slot); - SbkNewQFlagsType_slots[idx].pfunc = numberMethods[idx].pfunc; - } - newspec.slots = SbkNewQFlagsType_spec.slots; - PyTypeObject *type = (PyTypeObject *)SbkType_FromSpec(&newspec); - Py_TYPE(type) = &PyType_Type; - - PySideQFlagsType *flagsType = reinterpret_cast<PySideQFlagsType *>(type); - PepType_PFTP(flagsType)->converterPtr = &PepType_PFTP(flagsType)->converter; - - if (PyType_Ready(type) < 0) - return 0; - - return type; - } - - PySideQFlagsObject *newObject(long value, PyTypeObject *type) - { - PySideQFlagsObject *qflags = PyObject_New(PySideQFlagsObject, type); - qflags->ob_value = value; - return qflags; - } - - long getValue(PySideQFlagsObject *self) - { - return self->ob_value; - } -} -} diff --git a/sources/pyside6/libpyside/pysideqflags.h b/sources/pyside6/libpyside/pysideqflags.h deleted file mode 100644 index 71f30808d..000000000 --- a/sources/pyside6/libpyside/pysideqflags.h +++ /dev/null @@ -1,79 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of Qt for Python. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#ifndef PYSIDE_QFLAGS_H -#define PYSIDE_QFLAGS_H - -#include <sbkpython.h> -#include "pysidemacros.h" - - -extern "C" -{ - struct PYSIDE_API PySideQFlagsObject { - PyObject_HEAD - long ob_value; - }; - - PYSIDE_API PyObject* PySideQFlagsNew(PyTypeObject *type, PyObject *args, PyObject *kwds); - PYSIDE_API PyObject* PySideQFlagsRichCompare(PyObject *self, PyObject *other, int op); -} - - -namespace PySide -{ -namespace QFlags -{ - /** - * Creates a new QFlags type. - */ - PYSIDE_API PyTypeObject *create(const char* name, PyType_Slot *numberMethods); - /** - * Creates a new QFlags instance of type \p type and value \p value. - */ - PYSIDE_API PySideQFlagsObject* newObject(long value, PyTypeObject* type); - /** - * Returns the value held by a QFlag. - */ - PYSIDE_API long getValue(PySideQFlagsObject* self); -} -} - -#endif - diff --git a/sources/pyside6/libpyside/pysideqhash.h b/sources/pyside6/libpyside/pysideqhash.h new file mode 100644 index 000000000..ae2d295f6 --- /dev/null +++ b/sources/pyside6/libpyside/pysideqhash.h @@ -0,0 +1,24 @@ +// 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 PYSIDEQHASH_H +#define PYSIDEQHASH_H + +#include <sbkpython.h> + +#include <QtCore/QHash> + +namespace PySide +{ + +/// Hash function used to enable hash on objects not supported by the native Qt +/// library which have a toString() function. +template<class T> +[[deprecated]] inline Py_ssize_t hash(const T& value) +{ + return qHash(value.toString()); +} + +} //namespace PySide + +#endif // PYSIDEQHASH_H diff --git a/sources/pyside6/libpyside/pysideqmetatype.h b/sources/pyside6/libpyside/pysideqmetatype.h new file mode 100644 index 000000000..3782dc3bb --- /dev/null +++ b/sources/pyside6/libpyside/pysideqmetatype.h @@ -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 + +#ifndef PYSIDEQMETATYPE_H +#define PYSIDEQMETATYPE_H + +#include <QtCore/QMetaType> + +namespace PySide +{ + +/// If the type \p T was registered on Qt meta type system with Q_DECLARE_METATYPE macro, +/// this class will initialize the meta type. +/// +/// Initialize a meta type means register it on Qt meta type system, Qt itself only do this +/// on the first call of qMetaTypeId, and this is exactly what we do to init it. If we don't +/// do that, calls to QMetaType::type("QMatrix2x2") could return zero, causing QVariant to +/// not recognize some C++ types, like QMatrix2x2. + +template<typename T, bool OK = QMetaTypeId<T>::Defined > +struct initQtMetaType { + initQtMetaType() + { + qMetaTypeId<T>(); + } +}; + +// Template specialization to do nothing when the type wasn't registered on Qt meta type system. +template<typename T> +struct initQtMetaType<T, false> { +}; + +} //namespace PySide + +#endif // PYSIDEQMETATYPE_H diff --git a/sources/pyside6/libpyside/pysideqobject.h b/sources/pyside6/libpyside/pysideqobject.h new file mode 100644 index 000000000..f81c50399 --- /dev/null +++ b/sources/pyside6/libpyside/pysideqobject.h @@ -0,0 +1,70 @@ +// 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 PYSIDEQOBJECT_H +#define PYSIDEQOBJECT_H + +#include <sbkpython.h> + +#include <pysidemacros.h> + +#include <QtCore/qtclasshelpermacros.h> + +#include <cstddef> + +QT_FORWARD_DECLARE_CLASS(QObject) +QT_FORWARD_DECLARE_STRUCT(QMetaObject) +QT_FORWARD_DECLARE_CLASS(QMutex) + +namespace PySide +{ + +/// Fill QObject properties and do signal connections using the values found in \p kwds dictionary. +/// \param qObj PyObject fot the QObject. +/// \param metaObj QMetaObject of \p qObj. +/// \param kwds key->value dictonary. +/// \return True if everything goes well, false with a Python error set otherwise. +PYSIDE_API bool fillQtProperties(PyObject *qObj, const QMetaObject *metaObj, + PyObject *kwds, bool allowErrors); + +PYSIDE_API void initDynamicMetaObject(PyTypeObject *type, const QMetaObject *base, + std::size_t cppObjSize); +PYSIDE_API void initQObjectSubType(PyTypeObject *type, PyObject *args, PyObject *kwds); + +/// Return the size in bytes of a type that inherits QObject. +PYSIDE_API std::size_t getSizeOfQObject(PyTypeObject *type); + +/// Check if a PyTypeObject or its bases contains a QObject +/// \param pyType is the PyTypeObject to check +/// \param raiseError controls if a TypeError is raised when an object does not +/// inherit QObject +PYSIDE_API bool isQObjectDerived(PyTypeObject *pyType, bool raiseError); + +/// Convenience to convert a PyObject to QObject +PYSIDE_API QObject *convertToQObject(PyObject *object, bool raiseError); + +/// Check for properties and signals registered on MetaObject and return these. +/// Also handle Python properties when true_property was selected. +/// \param cppSelf Is the QObject which contains the metaobject +/// \param self Python object of cppSelf +/// \param name Name of the argument which the function will try retrieve from MetaData +/// \return The Python object which contains the Data obtained in metaObject or the Python +/// method pulled out of a Python property. +PYSIDE_API PyObject *getHiddenDataFromQObject(QObject *cppSelf, PyObject *self, PyObject *name); + +/// Mutex for accessing QObject memory helpers from multiple threads +PYSIDE_API QMutex &nextQObjectMemoryAddrMutex(); +PYSIDE_API void *nextQObjectMemoryAddr(); +/// Set the address where to allocate the next QObject (for QML) +PYSIDE_API void setNextQObjectMemoryAddr(void *addr); + +PYSIDE_API PyObject *getWrapperForQObject(QObject *cppSelf, PyTypeObject *sbk_type); + +/// Return the best-matching type for a QObject (Helper for QObject.findType()) +/// \param cppSelf QObject instance +/// \return type object +PYSIDE_API PyTypeObject *getTypeForQObject(const QObject *cppSelf); + +} //namespace PySide + +#endif // PYSIDEQOBJECT_H diff --git a/sources/pyside6/libpyside/pysideqslotobject_p.cpp b/sources/pyside6/libpyside/pysideqslotobject_p.cpp new file mode 100644 index 000000000..914be898a --- /dev/null +++ b/sources/pyside6/libpyside/pysideqslotobject_p.cpp @@ -0,0 +1,36 @@ +// Copyright (C) 2024 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 "pysideqslotobject_p.h" + +#include <autodecref.h> +#include <gilstate.h> + +namespace PySide +{ + +void PySideQSlotObject::impl(int which, QSlotObjectBase *this_, QObject *receiver, + void **args, bool *ret) +{ + auto self = static_cast<PySideQSlotObject *>(this_); + switch (which) { + case Destroy: + delete self; + break; + case Call: + { + Shiboken::GilState state; + Shiboken::AutoDecRef arglist(PyTuple_New(0)); + Shiboken::AutoDecRef ret(PyObject_CallObject(self->callable, arglist)); + break; + } + case Compare: + case NumOperations: + Q_UNUSED(receiver); + Q_UNUSED(args); + Q_UNUSED(ret); + break; + } +} + +} // namespace PySide diff --git a/sources/pyside6/libpyside/pysideqslotobject_p.h b/sources/pyside6/libpyside/pysideqslotobject_p.h new file mode 100644 index 000000000..d7d258505 --- /dev/null +++ b/sources/pyside6/libpyside/pysideqslotobject_p.h @@ -0,0 +1,39 @@ +// Copyright (C) 2024 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 PYSIDEQSLOTOBJECT_P_H +#define PYSIDEQSLOTOBJECT_P_H + +#include "pysidemacros.h" +#include <sbkpython.h> + +#include <QtCore/QObject> +#include <QtCore/qobjectdefs.h> + +namespace PySide +{ + +class PySideQSlotObject : public QtPrivate::QSlotObjectBase +{ + PyObject *callable; + + static void impl(int which, QSlotObjectBase *this_, QObject *receiver, void **args, bool *ret); + +public: + PySideQSlotObject(PyObject *callable) : QtPrivate::QSlotObjectBase(&impl), callable(callable) + { + Py_INCREF(callable); + } + + ~PySideQSlotObject() + { + auto gstate = PyGILState_Ensure(); + Py_DECREF(callable); + PyGILState_Release(gstate); + } +}; + + +} // namespace PySide + +#endif // PYSIDEQSLOTOBJECT_P_H diff --git a/sources/pyside6/libpyside/pysidesignal.cpp b/sources/pyside6/libpyside/pysidesignal.cpp index 63d5b0228..ed0cc5d0a 100644 --- a/sources/pyside6/libpyside/pysidesignal.cpp +++ b/sources/pyside6/libpyside/pysidesignal.cpp @@ -1,83 +1,114 @@ -/**************************************************************************** -** -** Copyright (C) 2020 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of Qt for Python. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2020 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 <sbkpython.h> #include "pysidesignal.h" #include "pysidesignal_p.h" +#include "pysideqobject.h" +#include "pysideutils.h" #include "pysidestaticstrings.h" +#include "pysideweakref.h" #include "signalmanager.h" #include <shiboken.h> +#include <QtCore/QByteArray> +#include <QtCore/QDebug> +#include <QtCore/QHash> #include <QtCore/QObject> #include <QtCore/QMetaMethod> #include <QtCore/QMetaObject> +#include <pep384ext.h> #include <signature.h> #include <algorithm> +#include <optional> #include <utility> +#include <cstring> #define QT_SIGNAL_SENTINEL '2' -namespace PySide { -namespace Signal { - //aux - class SignalSignature { - public: - SignalSignature() = default; - explicit SignalSignature(QByteArray parameterTypes) : - m_parameterTypes(std::move(parameterTypes)) {} - explicit SignalSignature(QByteArray parameterTypes, QMetaMethod::Attributes attributes) : - m_parameterTypes(std::move(parameterTypes)), - m_attributes(attributes) {} - - QByteArray m_parameterTypes; - QMetaMethod::Attributes m_attributes = QMetaMethod::Compatibility; - }; +using namespace Qt::StringLiterals; + +QDebug operator<<(QDebug debug, const PySideSignalData::Signature &s) +{ + QDebugStateSaver saver(debug); + debug.noquote(); + debug.nospace(); + debug << "Signature(\"" << s.signature << '"'; + if (s.attributes) + debug << ", attributes=" << s.attributes; + debug << ')'; + return debug; +} + +QDebug operator<<(QDebug debug, const PySideSignalData &d) +{ + QDebugStateSaver saver(debug); + debug.noquote(); + debug.nospace(); + debug << "PySideSignalData(\"" << d.signalName << "\", " + << d.signatures; + if (!d.signalArguments.isEmpty()) + debug << ", signalArguments=" << d.signalArguments; + debug << ')'; + return debug; +} + +QDebug operator<<(QDebug debug, const PySideSignalInstancePrivate &d) +{ + QDebugStateSaver saver(debug); + debug.noquote(); + debug.nospace(); + debug << "PySideSignalInstancePrivate(\"" << d.signalName + << "\", \"" << d.signature << '"'; + if (d.attributes) + debug << ", attributes=" << d.attributes; + if (d.homonymousMethod) + debug << ", homonymousMethod=" << d.homonymousMethod; + debug << ')'; + return debug; +} +static bool connection_Check(PyObject *o) +{ + if (o == nullptr || o == Py_None) + return false; + static QByteArray typeName = QByteArrayLiteral("PySide") + + QByteArray::number(QT_VERSION_MAJOR) + + QByteArrayLiteral(".QtCore.QMetaObject.Connection"); + return std::strcmp(o->ob_type->tp_name, typeName.constData()) == 0; +} + +static std::optional<QByteArrayList> parseArgumentNames(PyObject *argArguments) +{ + QByteArrayList result; + if (argArguments == nullptr) + return result; + // Prevent a string from being split into a sequence of characters + if (PySequence_Check(argArguments) == 0 || PyUnicode_Check(argArguments) != 0) + return std::nullopt; + const Py_ssize_t argumentSize = PySequence_Size(argArguments); + result.reserve(argumentSize); + for (Py_ssize_t i = 0; i < argumentSize; ++i) { + Shiboken::AutoDecRef item(PySequence_GetItem(argArguments, i)); + if (PyUnicode_Check(item.object()) == 0) + return std::nullopt; + Shiboken::AutoDecRef strObj(PyUnicode_AsUTF8String(item)); + const char *s = PyBytes_AsString(strObj); + if (s == nullptr) + return std::nullopt; + result.append(QByteArray(s)); + } + return result; +} + +namespace PySide::Signal { static QByteArray buildSignature(const QByteArray &, const QByteArray &); - static void appendSignature(PySideSignal *, const SignalSignature &); static void instanceInitialize(PySideSignalInstance *, PyObject *, PySideSignal *, PyObject *, int); - static QByteArray parseSignature(PyObject *); + static PySideSignalData::Signature parseSignature(PyObject *); static PyObject *buildQtCompatible(const QByteArray &); -} -} +} // PySide::Signal extern "C" { @@ -87,6 +118,7 @@ static int signalTpInit(PyObject *, PyObject *, PyObject *); static void signalFree(void *); static void signalInstanceFree(void *); static PyObject *signalGetItem(PyObject *self, PyObject *key); +static PyObject *signalGetAttr(PyObject *self, PyObject *name); static PyObject *signalToString(PyObject *self); static PyObject *signalDescrGet(PyObject *self, PyObject *obj, PyObject *type); @@ -102,209 +134,343 @@ static PyObject *signalCall(PyObject *, PyObject *, PyObject *); static PyObject *metaSignalCheck(PyObject *, PyObject *); -static PyMethodDef MetaSignal_methods[] = { - {"__instancecheck__", (PyCFunction)metaSignalCheck, METH_O|METH_STATIC, NULL}, - {0, 0, 0, 0} +static PyMethodDef MetaSignal_tp_methods[] = { + {"__instancecheck__", reinterpret_cast<PyCFunction>(metaSignalCheck), + METH_O|METH_STATIC, nullptr}, + {nullptr, nullptr, 0, nullptr} }; -static PyType_Slot PySideMetaSignalType_slots[] = { - {Py_tp_methods, reinterpret_cast<void *>(MetaSignal_methods)}, - {Py_tp_base, reinterpret_cast<void *>(&PyType_Type)}, - {Py_tp_free, reinterpret_cast<void *>(PyObject_GC_Del)}, - {Py_tp_dealloc, reinterpret_cast<void *>(Sbk_object_dealloc)}, - {0, 0} -}; -static PyType_Spec PySideMetaSignalType_spec = { - "2:PySide6.QtCore.MetaSignal", - 0, - // sizeof(PyHeapTypeObject) is filled in by SbkType_FromSpecWithBases - // which calls PyType_Ready which calls inherit_special. - 0, - Py_TPFLAGS_DEFAULT, - PySideMetaSignalType_slots, -}; +static PyTypeObject *createMetaSignalType() +{ + PyType_Slot PySideMetaSignalType_slots[] = { + {Py_tp_methods, reinterpret_cast<void *>(MetaSignal_tp_methods)}, + {Py_tp_base, reinterpret_cast<void *>(&PyType_Type)}, + {Py_tp_free, reinterpret_cast<void *>(PyObject_GC_Del)}, + {Py_tp_dealloc, reinterpret_cast<void *>(Sbk_object_dealloc)}, + {0, nullptr} + }; + PyType_Spec PySideMetaSignalType_spec = { + "2:PySide6.QtCore.MetaSignal", + 0, + // sizeof(PyHeapTypeObject) is filled in by SbkType_FromSpec + // which calls PyType_Ready which calls inherit_special. + 0, + Py_TPFLAGS_DEFAULT, + PySideMetaSignalType_slots, + }; + + return SbkType_FromSpec(&PySideMetaSignalType_spec); +} -static PyTypeObject *PySideMetaSignalTypeF(void) +static PyTypeObject *PySideMetaSignal_TypeF(void) { - static PyTypeObject *type = nullptr; - if (!type) { - PyObject *bases = Py_BuildValue("(O)", &PyType_Type); - type = (PyTypeObject *)SbkType_FromSpecWithBases(&PySideMetaSignalType_spec, bases); - Py_XDECREF(bases); - } + static auto *type = createMetaSignalType(); return type; } -static PyType_Slot PySideSignalType_slots[] = { - {Py_mp_subscript, reinterpret_cast<void *>(signalGetItem)}, - {Py_tp_descr_get, reinterpret_cast<void *>(signalDescrGet)}, - {Py_tp_call, reinterpret_cast<void *>(signalCall)}, - {Py_tp_str, reinterpret_cast<void *>(signalToString)}, - {Py_tp_init, reinterpret_cast<void *>(signalTpInit)}, - {Py_tp_new, reinterpret_cast<void *>(PyType_GenericNew)}, - {Py_tp_free, reinterpret_cast<void *>(signalFree)}, - {Py_tp_dealloc, reinterpret_cast<void *>(Sbk_object_dealloc)}, - {0, 0} -}; -static PyType_Spec PySideSignalType_spec = { - "2:PySide6.QtCore.Signal", - sizeof(PySideSignal), - 0, - Py_TPFLAGS_DEFAULT, - PySideSignalType_slots, -}; +static PyTypeObject *createSignalType() +{ + PyType_Slot PySideSignalType_slots[] = { + {Py_mp_subscript, reinterpret_cast<void *>(signalGetItem)}, + {Py_tp_getattro, reinterpret_cast<void *>(signalGetAttr)}, + {Py_tp_descr_get, reinterpret_cast<void *>(signalDescrGet)}, + {Py_tp_call, reinterpret_cast<void *>(signalCall)}, + {Py_tp_str, reinterpret_cast<void *>(signalToString)}, + {Py_tp_init, reinterpret_cast<void *>(signalTpInit)}, + {Py_tp_new, reinterpret_cast<void *>(PyType_GenericNew)}, + {Py_tp_free, reinterpret_cast<void *>(signalFree)}, + {Py_tp_dealloc, reinterpret_cast<void *>(Sbk_object_dealloc)}, + {0, nullptr} + }; + PyType_Spec PySideSignalType_spec = { + "2:PySide6.QtCore.Signal", + sizeof(PySideSignal), + 0, + Py_TPFLAGS_DEFAULT, + PySideSignalType_slots, + }; + + return SbkType_FromSpecWithMeta(&PySideSignalType_spec, PySideMetaSignal_TypeF()); +} -PyTypeObject *PySideSignalTypeF(void) +PyTypeObject *PySideSignal_TypeF(void) { - static PyTypeObject *type = nullptr; - if (!type) { - type = reinterpret_cast<PyTypeObject *>(SbkType_FromSpec(&PySideSignalType_spec)); - PyTypeObject *hold = Py_TYPE(type); - Py_TYPE(type) = PySideMetaSignalTypeF(); - Py_INCREF(Py_TYPE(type)); - Py_DECREF(hold); - } + static auto *type = createSignalType(); return type; } +static PyObject *signalInstanceRepr(PyObject *obSelf) +{ + auto *self = reinterpret_cast<PySideSignalInstance *>(obSelf); + auto *typeName = Py_TYPE(obSelf)->tp_name; + return Shiboken::String::fromFormat("<%s %s at %p>", typeName, + self->d ? self->d->signature.constData() + : "(no signature)", obSelf); +} + static PyMethodDef SignalInstance_methods[] = { - {"connect", (PyCFunction)signalInstanceConnect, METH_VARARGS|METH_KEYWORDS, 0}, - {"disconnect", signalInstanceDisconnect, METH_VARARGS, 0}, - {"emit", signalInstanceEmit, METH_VARARGS, 0}, - {0, 0, 0, 0} /* Sentinel */ + {"connect", reinterpret_cast<PyCFunction>(signalInstanceConnect), + METH_VARARGS|METH_KEYWORDS, nullptr}, + {"disconnect", signalInstanceDisconnect, METH_VARARGS, nullptr}, + {"emit", signalInstanceEmit, METH_VARARGS, nullptr}, + {nullptr, nullptr, 0, nullptr} /* Sentinel */ }; -static PyType_Slot PySideSignalInstanceType_slots[] = { - {Py_mp_subscript, reinterpret_cast<void *>(signalInstanceGetItem)}, - {Py_tp_call, reinterpret_cast<void *>(signalInstanceCall)}, - {Py_tp_methods, reinterpret_cast<void *>(SignalInstance_methods)}, - {Py_tp_new, reinterpret_cast<void *>(PyType_GenericNew)}, - {Py_tp_free, reinterpret_cast<void *>(signalInstanceFree)}, - {Py_tp_dealloc, reinterpret_cast<void *>(Sbk_object_dealloc)}, - {0, 0} -}; -static PyType_Spec PySideSignalInstanceType_spec = { - "2:PySide6.QtCore.SignalInstance", - sizeof(PySideSignalInstance), - 0, - Py_TPFLAGS_DEFAULT, - PySideSignalInstanceType_slots, -}; +static PyTypeObject *createSignalInstanceType() +{ + PyType_Slot PySideSignalInstanceType_slots[] = { + {Py_mp_subscript, reinterpret_cast<void *>(signalInstanceGetItem)}, + {Py_tp_call, reinterpret_cast<void *>(signalInstanceCall)}, + {Py_tp_methods, reinterpret_cast<void *>(SignalInstance_methods)}, + {Py_tp_repr, reinterpret_cast<void *>(signalInstanceRepr)}, + {Py_tp_new, reinterpret_cast<void *>(PyType_GenericNew)}, + {Py_tp_free, reinterpret_cast<void *>(signalInstanceFree)}, + {Py_tp_dealloc, reinterpret_cast<void *>(Sbk_object_dealloc)}, + {0, nullptr} + }; + PyType_Spec PySideSignalInstanceType_spec = { + "2:PySide6.QtCore.SignalInstance", + sizeof(PySideSignalInstance), + 0, + Py_TPFLAGS_DEFAULT, + PySideSignalInstanceType_slots, + }; + + return SbkType_FromSpec(&PySideSignalInstanceType_spec); +} -PyTypeObject *PySideSignalInstanceTypeF(void) +PyTypeObject *PySideSignalInstance_TypeF(void) { - static PyTypeObject *type = - reinterpret_cast<PyTypeObject *>(SbkType_FromSpec(&PySideSignalInstanceType_spec)); + static auto *type = createSignalInstanceType(); return type; } -static int signalTpInit(PyObject *self, PyObject *args, PyObject *kwds) +static int signalTpInit(PyObject *obSelf, PyObject *args, PyObject *kwds) { - static PyObject *emptyTuple = nullptr; + static PyObject * const emptyTuple = PyTuple_New(0); static const char *kwlist[] = {"name", "arguments", nullptr}; char *argName = nullptr; PyObject *argArguments = nullptr; - if (emptyTuple == 0) - emptyTuple = PyTuple_New(0); - if (!PyArg_ParseTupleAndKeywords(emptyTuple, kwds, - "|sO:QtCore.Signal", const_cast<char **>(kwlist), &argName, &argArguments)) + "|sO:QtCore.Signal{name, arguments}", + const_cast<char **>(kwlist), &argName, &argArguments)) return -1; bool tupledArgs = false; - PySideSignal *data = reinterpret_cast<PySideSignal *>(self); - if (!data->data) - data->data = new PySideSignalData; + PySideSignal *self = reinterpret_cast<PySideSignal *>(obSelf); + if (!self->data) + self->data = new PySideSignalData; if (argName) - data->data->signalName = argName; - - data->data->signalArguments = new QByteArrayList(); - if (argArguments && PySequence_Check(argArguments)) { - Py_ssize_t argument_size = PySequence_Size(argArguments); - for (Py_ssize_t i = 0; i < argument_size; ++i) { - PyObject *item = PySequence_GetItem(argArguments, i); - PyObject *strObj = PyUnicode_AsUTF8String(item); - char *s = PyBytes_AsString(strObj); - Py_DECREF(strObj); - Py_DECREF(item); - if (s != nullptr) - data->data->signalArguments->append(QByteArray(s)); - } + self->data->signalName = argName; + + auto argumentNamesOpt = parseArgumentNames(argArguments); + if (!argumentNamesOpt.has_value()) { + PyErr_SetString(PyExc_TypeError, "'arguments' must be a sequence of strings."); + return -1; } + self->data->signalArguments = argumentNamesOpt.value(); for (Py_ssize_t i = 0, i_max = PyTuple_Size(args); i < i_max; i++) { PyObject *arg = PyTuple_GET_ITEM(args, i); if (PySequence_Check(arg) && !Shiboken::String::check(arg) && !PyEnumMeta_Check(arg)) { tupledArgs = true; - const auto sig = PySide::Signal::parseSignature(arg); - PySide::Signal::appendSignature( - data, - PySide::Signal::SignalSignature(sig)); + self->data->signatures.append(PySide::Signal::parseSignature(arg)); } } - if (!tupledArgs) { - const auto sig = PySide::Signal::parseSignature(args); - PySide::Signal::appendSignature( - data, - PySide::Signal::SignalSignature(sig)); - } + if (!tupledArgs) + self->data->signatures.append(PySide::Signal::parseSignature(args)); return 0; } -static void signalFree(void *self) +static void signalFree(void *vself) { - auto pySelf = reinterpret_cast<PyObject *>(self); - auto data = reinterpret_cast<PySideSignal *>(self); - delete data->data; - data->data = nullptr; - Py_XDECREF(data->homonymousMethod); - data->homonymousMethod = 0; - - Py_TYPE(pySelf)->tp_base->tp_free(self); + auto pySelf = reinterpret_cast<PyObject *>(vself); + auto self = reinterpret_cast<PySideSignal *>(vself); + if (self->data) { + delete self->data; + self->data = nullptr; + } + Py_XDECREF(self->homonymousMethod); + self->homonymousMethod = nullptr; + + PepExt_TypeCallFree(Py_TYPE(pySelf)->tp_base, self); } -static PyObject *signalGetItem(PyObject *self, PyObject *key) +static PyObject *signalGetItem(PyObject *obSelf, PyObject *key) { - auto data = reinterpret_cast<PySideSignal *>(self); + auto self = reinterpret_cast<PySideSignal *>(obSelf); QByteArray sigKey; if (key) { - sigKey = PySide::Signal::parseSignature(key); + sigKey = PySide::Signal::parseSignature(key).signature; } else { - sigKey = data->data == nullptr || data->data->signatures.isEmpty() - ? PySide::Signal::voidType() : data->data->signatures.constFirst().signature; + sigKey = self->data == nullptr || self->data->signatures.isEmpty() + ? PySide::Signal::voidType() : self->data->signatures.constFirst().signature; } - auto sig = PySide::Signal::buildSignature(data->data->signalName, sigKey); + auto sig = PySide::Signal::buildSignature(self->data->signalName, sigKey); return Shiboken::String::fromCString(sig.constData()); } +static PyObject *signalToString(PyObject *obSelf) +{ + auto self = reinterpret_cast<PySideSignal *>(obSelf); + QByteArray result; + if (self->data == nullptr || self->data->signatures.isEmpty()) { + result = "<invalid>"_ba; + } else { + for (const auto &signature : std::as_const(self->data->signatures)) { + if (!result.isEmpty()) + result += "; "_ba; + result += PySide::Signal::buildSignature(self->data->signalName, + signature.signature); + } + } + return Shiboken::String::fromCString(result.constData()); +} -static PyObject *signalToString(PyObject *self) +static PyObject *signalGetAttr(PyObject *obSelf, PyObject *name) { - return signalGetItem(self, 0); + auto self = reinterpret_cast<PySideSignal *>(obSelf); + + if (PyUnicode_CompareWithASCIIString(name, "signatures") != 0) + return PyObject_GenericGetAttr(obSelf, name); + + auto nelems = self->data->signatures.count(); + PyObject *tuple = PyTuple_New(nelems); + + for (Py_ssize_t idx = 0; idx < nelems; ++idx) { + QByteArray sigKey = self->data->signatures.at(idx).signature; + auto sig = PySide::Signal::buildSignature(self->data->signalName, sigKey); + PyObject *entry = Shiboken::String::fromCString(sig.constData()); + PyTuple_SetItem(tuple, idx, entry); + } + return tuple; } -static void signalInstanceFree(void *self) +static void signalInstanceFree(void *vself) { - auto pySelf = reinterpret_cast<PyObject *>(self); - auto data = reinterpret_cast<PySideSignalInstance *>(self); + auto pySelf = reinterpret_cast<PyObject *>(vself); + auto self = reinterpret_cast<PySideSignalInstance *>(vself); - PySideSignalInstancePrivate *dataPvt = data->d; + PySideSignalInstancePrivate *dataPvt = self->d; + if (dataPvt) { + Py_XDECREF(dataPvt->homonymousMethod); - Py_XDECREF(dataPvt->homonymousMethod); + if (dataPvt->next) { + Py_DECREF(dataPvt->next); + dataPvt->next = nullptr; + } + delete dataPvt; + self->d = nullptr; + } + self->deleted = true; + PepExt_TypeCallFree(Py_TYPE(pySelf)->tp_base, self); +} - if (dataPvt->next) { - Py_DECREF(dataPvt->next); - dataPvt->next = 0; +// PYSIDE-1523: PyFunction_Check is not accepting compiled functions and +// PyMethod_Check is not allowing compiled methods, therefore also lookup +// "im_func" and "__code__" attributes, we allow for that with a dedicated +// function handling both. + +struct FunctionArgumentsResult +{ + PyObject *function = nullptr; + PepCodeObject *objCode = nullptr; + PyObject *functionName = nullptr; + bool isMethod = false; +}; + +static FunctionArgumentsResult extractFunctionArgumentsFromSlot(PyObject *slot) +{ + FunctionArgumentsResult ret; + ret.isMethod = PyMethod_Check(slot); + const bool isFunction = PyFunction_Check(slot); + + if (ret.isMethod || isFunction) { + ret.function = ret.isMethod ? PyMethod_GET_FUNCTION(slot) : slot; + ret.objCode = reinterpret_cast<PepCodeObject *>(PyFunction_GET_CODE(ret.function)); + ret.functionName = PepFunction_GetName(ret.function); + + } else if (PySide::isCompiledMethod(slot)) { + // PYSIDE-1523: PyFunction_Check and PyMethod_Check are not accepting compiled forms, we + // just go by attributes. + ret.isMethod = true; + + ret.function = PyObject_GetAttr(slot, PySide::PySideName::im_func()); + // Not retaining a reference inline with what PyMethod_GET_FUNCTION does. + Py_DECREF(ret.function); + + ret.functionName = PyObject_GetAttr(ret.function, PySide::PySideMagicName::name()); + // Not retaining a reference inline with what PepFunction_GetName does. + Py_DECREF(ret.functionName); + + ret.objCode = reinterpret_cast<PepCodeObject *>( + PyObject_GetAttr(ret.function, PySide::PySideMagicName::code())); + // Not retaining a reference inline with what PyFunction_GET_CODE does. + Py_XDECREF(ret.objCode); + + // Should not happen, but lets handle it gracefully, maybe Nuitka one day + // makes these optional, or somebody defined a type named like it without + // it being actually being that. + if (ret.objCode == nullptr) + ret.function = nullptr; + } else if (strcmp(Py_TYPE(slot)->tp_name, "compiled_function") == 0) { + ret.isMethod = false; + ret.function = slot; + + ret.functionName = PyObject_GetAttr(ret.function, PySide::PySideMagicName::name()); + // Not retaining a reference inline with what PepFunction_GetName does. + Py_DECREF(ret.functionName); + + ret.objCode = reinterpret_cast<PepCodeObject *>( + PyObject_GetAttr(ret.function, PySide::PySideMagicName::code())); + // Not retaining a reference inline with what PyFunction_GET_CODE does. + Py_XDECREF(ret.objCode); + + // Should not happen, but lets handle it gracefully, maybe Nuitka one day + // makes these optional, or somebody defined a type named like it without + // it being actually being that. + if (ret.objCode == nullptr) + ret.function = nullptr; + } + // any other callback + return ret; +} + +struct ArgCount +{ + int min; + int max; +}; + +// Return a pair of minimum / arg count "foo(p1, p2=0)" -> {1, 2} +ArgCount argCount(const FunctionArgumentsResult &args) +{ + Q_ASSERT(args.objCode); + ArgCount result{-1, -1}; + if ((PepCode_GET_FLAGS(args.objCode) & CO_VARARGS) == 0) { + result.min = result.max = PepCode_GET_ARGCOUNT(args.objCode); + if (args.function != nullptr) { + if (auto *defaultArgs = PepFunction_GetDefaults(args.function)) + result.min -= PyTuple_Size(defaultArgs); + } } - delete dataPvt; - data->d = 0; - Py_TYPE(pySelf)->tp_base->tp_free(self); + return result; +} + +// Find Signal Instance for argument count. +static PySideSignalInstance *findSignalInstance(PySideSignalInstance *source, int argCount) +{ + for (auto *si = source; si != nullptr; si = si->d->next) { + if (si->d->argCount == argCount) + return si; + } + return nullptr; } static PyObject *signalInstanceConnect(PyObject *self, PyObject *args, PyObject *kwds) @@ -315,21 +481,26 @@ static PyObject *signalInstanceConnect(PyObject *self, PyObject *args, PyObject if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|O:SignalInstance", const_cast<char **>(kwlist), &slot, &type)) - return 0; + return nullptr; PySideSignalInstance *source = reinterpret_cast<PySideSignalInstance *>(self); + if (!source->d) + return PyErr_Format(PyExc_RuntimeError, "cannot connect uninitialized SignalInstance"); + if (source->deleted) + return PyErr_Format(PyExc_RuntimeError, "Signal source has been deleted"); + Shiboken::AutoDecRef pyArgs(PyList_New(0)); bool match = false; - if (Py_TYPE(slot) == PySideSignalInstanceTypeF()) { + if (Py_TYPE(slot) == PySideSignalInstance_TypeF()) { PySideSignalInstance *sourceWalk = source; - PySideSignalInstance *targetWalk; //find best match while (sourceWalk && !match) { - targetWalk = reinterpret_cast<PySideSignalInstance *>(slot); + auto targetWalk = reinterpret_cast<PySideSignalInstance *>(slot); while (targetWalk && !match) { - if (QMetaObject::checkConnectArgs(sourceWalk->d->signature, targetWalk->d->signature)) { + if (QMetaObject::checkConnectArgs(sourceWalk->d->signature, + targetWalk->d->signature)) { PyList_Append(pyArgs, sourceWalk->d->source); Shiboken::AutoDecRef sourceSignature(PySide::Signal::buildQtCompatible(sourceWalk->d->signature)); PyList_Append(pyArgs, sourceSignature); @@ -346,54 +517,32 @@ static PyObject *signalInstanceConnect(PyObject *self, PyObject *args, PyObject } } else { // Check signature of the slot (method or function) to match signal - int slotArgs = -1; - bool useSelf = false; - bool isMethod = PyMethod_Check(slot); - bool isFunction = PyFunction_Check(slot); - bool matchedSlot = false; - - PySideSignalInstance *it = source; - - if (isMethod || isFunction) { - PyObject *function = isMethod ? PyMethod_GET_FUNCTION(slot) : slot; - auto *objCode = reinterpret_cast<PepCodeObject *>(PyFunction_GET_CODE(function)); - useSelf = isMethod; - slotArgs = PepCode_GET_FLAGS(objCode) & CO_VARARGS ? -1 : PepCode_GET_ARGCOUNT(objCode); - if (useSelf) - slotArgs -= 1; + const auto args = extractFunctionArgumentsFromSlot(slot); + PySideSignalInstance *matchedSlot = nullptr; + + if (args.function != nullptr) { + auto slotArgRange = argCount(args); + if (args.isMethod) { + slotArgRange.min -= 1; + slotArgRange.max -= 1; + } // Get signature args - bool isShortCircuit = false; - int signatureArgs = 0; - QStringList argsSignature; - - argsSignature = PySide::Signal::getArgsFromSignature(it->d->signature, - &isShortCircuit); - signatureArgs = argsSignature.length(); - // Iterate the possible types of connection for this signal and compare // it with slot arguments - if (signatureArgs != slotArgs) { - while (it->d->next != nullptr) { - it = it->d->next; - argsSignature = PySide::Signal::getArgsFromSignature(it->d->signature, - &isShortCircuit); - signatureArgs = argsSignature.length(); - if (signatureArgs == slotArgs) { - matchedSlot = true; - break; - } - } + for (int slotArgs = slotArgRange.max; + slotArgs >= slotArgRange.min && matchedSlot == nullptr; --slotArgs) { + matchedSlot = findSignalInstance(source, slotArgs); } } // Adding references to pyArgs PyList_Append(pyArgs, source->d->source); - if (matchedSlot) { + if (matchedSlot != nullptr) { // If a slot matching the same number of arguments was found, // include signature to the pyArgs - Shiboken::AutoDecRef signature(PySide::Signal::buildQtCompatible(it->d->signature)); + Shiboken::AutoDecRef signature(PySide::Signal::buildQtCompatible(matchedSlot->d->signature)); PyList_Append(pyArgs, signature); } else { // Try the first by default if the slot was not found @@ -410,20 +559,18 @@ static PyObject *signalInstanceConnect(PyObject *self, PyObject *args, PyObject if (match) { Shiboken::AutoDecRef tupleArgs(PyList_AsTuple(pyArgs)); Shiboken::AutoDecRef pyMethod(PyObject_GetAttr(source->d->source, - PySide::PyName::qtConnect())); - if (pyMethod.isNull()) { // PYSIDE-79: check if pyMethod exists. - PyErr_SetString(PyExc_RuntimeError, "method 'connect' vanished!"); - return 0; - } + PySide::PySideName::qtConnect())); + if (pyMethod.isNull()) // PYSIDE-79: check if pyMethod exists. + return PyErr_Format(PyExc_RuntimeError, "method 'connect' vanished!"); PyObject *result = PyObject_CallObject(pyMethod, tupleArgs); - if (result == Py_True || result == Py_False) + if (connection_Check(result)) return result; Py_XDECREF(result); } if (!PyErr_Occurred()) // PYSIDE-79: inverse the logic. A Null return needs an error. PyErr_Format(PyExc_RuntimeError, "Failed to connect signal %s.", source->d->signature.constData()); - return 0; + return nullptr; } static int argCountInSignature(const char *signature) @@ -434,6 +581,13 @@ static int argCountInSignature(const char *signature) static PyObject *signalInstanceEmit(PyObject *self, PyObject *args) { PySideSignalInstance *source = reinterpret_cast<PySideSignalInstance *>(self); + if (!source->d) + return PyErr_Format(PyExc_RuntimeError, "cannot emit uninitialized SignalInstance"); + + // PYSIDE-2201: Check if the object has vanished meanwhile. + // Tried to revive it without exception, but this gives problems. + if (source->deleted) + return PyErr_Format(PyExc_RuntimeError, "The SignalInstance object was already deleted"); Shiboken::AutoDecRef pyArgs(PyList_New(0)); int numArgsGiven = PySequence_Fast_GET_SIZE(args); @@ -464,45 +618,66 @@ static PyObject *signalInstanceEmit(PyObject *self, PyObject *args) PyList_Append(pyArgs, PyTuple_GetItem(args, i)); Shiboken::AutoDecRef pyMethod(PyObject_GetAttr(source->d->source, - PySide::PyName::qtEmit())); + PySide::PySideName::qtEmit())); Shiboken::AutoDecRef tupleArgs(PyList_AsTuple(pyArgs)); - return PyObject_CallObject(pyMethod, tupleArgs); + return PyObject_CallObject(pyMethod.object(), tupleArgs); } static PyObject *signalInstanceGetItem(PyObject *self, PyObject *key) { - auto data = reinterpret_cast<PySideSignalInstance *>(self); - const auto sigName = data->d->signalName; - const auto sigKey = PySide::Signal::parseSignature(key); + auto *firstSignal = reinterpret_cast<PySideSignalInstance *>(self); + const auto &sigName = firstSignal->d->signalName; + const auto sigKey = PySide::Signal::parseSignature(key).signature; const auto sig = PySide::Signal::buildSignature(sigName, sigKey); - while (data) { + for (auto *data = firstSignal; data != nullptr; data = data->d->next) { if (data->d->signature == sig) { PyObject *result = reinterpret_cast<PyObject *>(data); Py_INCREF(result); return result; } - data = data->d->next; } - PyErr_Format(PyExc_IndexError, "Signature %s not found for signal: %s", - sig.constData(), sigName.constData()); - return 0; + // Build error message with candidates + QByteArray message = "Signature \"" + sig + "\" not found for signal: \"" + + sigName + "\". Available candidates: "; + for (auto *data = firstSignal; data != nullptr; data = data->d->next) { + if (data != firstSignal) + message += ", "; + message += '"' + data->d->signature + '"'; + } + + return PyErr_Format(PyExc_IndexError, message.constData()); +} + +static inline void warnDisconnectFailed(PyObject *aSlot, const QByteArray &signature) +{ + if (PyErr_Occurred() != nullptr) { // avoid "%S" invoking str() when an error is set. + PyObject *exc{}, *inst{}, *tb{}; + PyErr_Fetch(&exc, &inst, &tb); + PyErr_WarnFormat(PyExc_RuntimeWarning, 0, "Failed to disconnect (%s) from signal \"%s\".", + Py_TYPE(aSlot)->tp_name, signature.constData()); + PyErr_Restore(exc, inst, tb); + } else { + PyErr_WarnFormat(PyExc_RuntimeWarning, 0, "Failed to disconnect (%S) from signal \"%s\".", + aSlot, signature.constData()); + } } static PyObject *signalInstanceDisconnect(PyObject *self, PyObject *args) { auto source = reinterpret_cast<PySideSignalInstance *>(self); + if (!source->d) + return PyErr_Format(PyExc_RuntimeError, "cannot disconnect uninitialized SignalInstance"); + Shiboken::AutoDecRef pyArgs(PyList_New(0)); - PyObject *slot; + PyObject *slot = Py_None; if (PyTuple_Check(args) && PyTuple_GET_SIZE(args)) slot = PyTuple_GET_ITEM(args, 0); - else - slot = Py_None; bool match = false; - if (Py_TYPE(slot) == PySideSignalInstanceTypeF()) { + if (Py_TYPE(slot) == PySideSignalInstance_TypeF()) { PySideSignalInstance *target = reinterpret_cast<PySideSignalInstance *>(slot); if (QMetaObject::checkConnectArgs(source->d->signature, target->d->signature)) { PyList_Append(pyArgs, source->d->source); @@ -514,6 +689,9 @@ static PyObject *signalInstanceDisconnect(PyObject *self, PyObject *args) PyList_Append(pyArgs, target_signature); match = true; } + } else if (connection_Check(slot)) { + PyList_Append(pyArgs, slot); + match = true; } else { //try the first signature PyList_Append(pyArgs, source->d->source); @@ -530,17 +708,15 @@ static PyObject *signalInstanceDisconnect(PyObject *self, PyObject *args) if (match) { Shiboken::AutoDecRef tupleArgs(PyList_AsTuple(pyArgs)); Shiboken::AutoDecRef pyMethod(PyObject_GetAttr(source->d->source, - PySide::PyName::qtDisconnect())); + PySide::PySideName::qtDisconnect())); PyObject *result = PyObject_CallObject(pyMethod, tupleArgs); - if (!result || result == Py_True) - return result; - else - Py_DECREF(result); + if (result != Py_True) + warnDisconnectFailed(slot, source->d->signature); + return result; } - PyErr_Format(PyExc_RuntimeError, "Failed to disconnect signal %s.", - source->d->signature.constData()); - return 0; + warnDisconnectFailed(slot, source->d->signature); + Py_RETURN_FALSE; } // PYSIDE-68: Supply the missing __get__ function @@ -548,12 +724,23 @@ static PyObject *signalDescrGet(PyObject *self, PyObject *obj, PyObject * /*type { auto signal = reinterpret_cast<PySideSignal *>(self); // Return the unbound signal if there is nothing to bind it to. - if (obj == nullptr || obj == Py_None) { + if (obj == nullptr || obj == Py_None + || !PySide::isQObjectDerived(Py_TYPE(obj), true)) { Py_INCREF(self); return self; } + + // PYSIDE-68-bis: It is important to respect the already cached instance. Shiboken::AutoDecRef name(Py_BuildValue("s", signal->data->signalName.data())); - return reinterpret_cast<PyObject *>(PySide::Signal::initialize(signal, name, obj)); + auto *dict = SbkObject_GetDict_NoRef(obj); + auto *inst = PyDict_GetItem(dict, name); + if (inst) { + Py_INCREF(inst); + return inst; + } + inst = reinterpret_cast<PyObject *>(PySide::Signal::initialize(signal, name, obj)); + PyObject_SetAttr(obj, name, inst); + return inst; } static PyObject *signalCall(PyObject *self, PyObject *args, PyObject *kw) @@ -564,148 +751,214 @@ static PyObject *signalCall(PyObject *self, PyObject *args, PyObject *kw) // The only way calling a signal can succeed (the Python equivalent of C++'s operator() ) // is when a method with the same name as the signal is attached to an object. // An example is QProcess::error() (don't check the docs, but the source code of qprocess.h). - if (!signal->homonymousMethod) { - PyErr_SetString(PyExc_TypeError, "native Qt signal is not callable"); - return 0; - } - - descrgetfunc getDescriptor = Py_TYPE(signal->homonymousMethod)->tp_descr_get; + if (!signal->homonymousMethod) + return PyErr_Format(PyExc_TypeError, "native Qt signal is not callable"); // Check if there exists a method with the same name as the signal, which is also a static // method in C++ land. - Shiboken::AutoDecRef homonymousMethod(getDescriptor(signal->homonymousMethod, 0, 0)); - if (PyCFunction_Check(homonymousMethod) - && (PyCFunction_GET_FLAGS(homonymousMethod.object()) & METH_STATIC)) { -#if PY_VERSION_HEX >= 0x03090000 + Shiboken::AutoDecRef homonymousMethod(PepExt_Type_CallDescrGet(signal->homonymousMethod, + nullptr, nullptr)); + if (PyCFunction_Check(homonymousMethod.object()) + && (PyCFunction_GET_FLAGS(homonymousMethod.object()) & METH_STATIC)) return PyObject_Call(homonymousMethod, args, kw); -#else - return PyCFunction_Call(homonymousMethod, args, kw); -#endif - } // Assumes homonymousMethod is not a static method. - ternaryfunc callFunc = Py_TYPE(signal->homonymousMethod)->tp_call; + ternaryfunc callFunc = PepExt_Type_GetCallSlot(Py_TYPE(signal->homonymousMethod)); return callFunc(homonymousMethod, args, kw); } +// This function returns a borrowed reference. +static inline PyObject *_getRealCallable(PyObject *func) +{ + static const auto *SignalType = PySideSignal_TypeF(); + static const auto *SignalInstanceType = PySideSignalInstance_TypeF(); + + // If it is a signal, use the (maybe empty) homonymous method. + if (Py_TYPE(func) == SignalType) { + auto *signal = reinterpret_cast<PySideSignal *>(func); + return signal->homonymousMethod; + } + // If it is a signal instance, use the (maybe empty) homonymous method. + if (Py_TYPE(func) == SignalInstanceType) { + auto *signalInstance = reinterpret_cast<PySideSignalInstance *>(func); + return signalInstance->d->homonymousMethod; + } + return func; +} + +// This function returns a borrowed reference. +static PyObject *_getHomonymousMethod(PySideSignalInstance *inst) +{ + if (inst->d->homonymousMethod) + return inst->d->homonymousMethod; + + // PYSIDE-1730: We are searching methods with the same name not only at the same place, + // but walk through the whole mro to find a hidden method with the same name. + auto signalName = inst->d->signalName; + Shiboken::AutoDecRef name(Shiboken::String::fromCString(signalName)); + auto *mro = Py_TYPE(inst->d->source)->tp_mro; + const Py_ssize_t n = PyTuple_GET_SIZE(mro); + + for (Py_ssize_t idx = 0; idx < n; idx++) { + auto *sub_type = reinterpret_cast<PyTypeObject *>(PyTuple_GET_ITEM(mro, idx)); + Shiboken::AutoDecRef tpDict(PepType_GetDict(sub_type)); + auto *hom = PyDict_GetItem(tpDict, name); + PyObject *realFunc{}; + if (hom && PyCallable_Check(hom) && (realFunc = _getRealCallable(hom))) + return realFunc; + } + return nullptr; +} + static PyObject *signalInstanceCall(PyObject *self, PyObject *args, PyObject *kw) { - auto PySideSignal = reinterpret_cast<PySideSignalInstance *>(self); - if (!PySideSignal->d->homonymousMethod) { - PyErr_SetString(PyExc_TypeError, "native Qt signal is not callable"); - return 0; + auto *PySideSignal = reinterpret_cast<PySideSignalInstance *>(self); + auto *hom = _getHomonymousMethod(PySideSignal); + if (!hom) { + PyErr_Format(PyExc_TypeError, "native Qt signal instance '%s' is not callable", + PySideSignal->d->signalName.constData()); + return nullptr; } - descrgetfunc getDescriptor = Py_TYPE(PySideSignal->d->homonymousMethod)->tp_descr_get; - Shiboken::AutoDecRef homonymousMethod(getDescriptor(PySideSignal->d->homonymousMethod, PySideSignal->d->source, 0)); -#if PY_VERSION_HEX >= 0x03090000 - return PyObject_Call(homonymousMethod, args, kw); -#else - return PyCFunction_Call(homonymousMethod, args, kw); -#endif + Shiboken::AutoDecRef homonymousMethod(PepExt_Type_CallDescrGet(hom, PySideSignal->d->source, + nullptr)); + return PyObject_Call(homonymousMethod, args, kw); } static PyObject *metaSignalCheck(PyObject * /* klass */, PyObject *arg) { - if (PyType_IsSubtype(Py_TYPE(arg), PySideSignalInstanceTypeF())) + if (PyType_IsSubtype(Py_TYPE(arg), PySideSignalInstance_TypeF())) Py_RETURN_TRUE; - else - Py_RETURN_FALSE; + Py_RETURN_FALSE; } } // extern "C" -namespace PySide { -namespace Signal { +namespace PySide::Signal { static const char *MetaSignal_SignatureStrings[] = { "PySide6.QtCore.MetaSignal.__instancecheck__(self,object:object)->bool", nullptr}; // Sentinel static const char *Signal_SignatureStrings[] = { - "PySide6.QtCore.Signal(self,*types:type,name:str=nullptr,arguments:str=nullptr)", + "PySide6.QtCore.Signal(self,*types:type,name:str=nullptr,arguments:typing.List[str]=nullptr)", + "1:PySide6.QtCore.Signal.__get__(self,instance:None,owner:Optional[typing.Any])->" + "PySide6.QtCore.Signal", + "0:PySide6.QtCore.Signal.__get__(self,instance:PySide6.QtCore.QObject," + "owner:Optional[typing.Any])->PySide6.QtCore.SignalInstance", nullptr}; // Sentinel static const char *SignalInstance_SignatureStrings[] = { - "PySide6.QtCore.SignalInstance.connect(self,slot:object,type:type=nullptr)", - "PySide6.QtCore.SignalInstance.disconnect(self,slot:object=nullptr)", + "PySide6.QtCore.SignalInstance.connect(self,slot:object," + "type:PySide6.QtCore.Qt.ConnectionType=PySide6.QtCore.Qt.ConnectionType.AutoConnection)" + "->PySide6.QtCore.QMetaObject.Connection", + "PySide6.QtCore.SignalInstance.disconnect(self,slot:object=nullptr)->bool", "PySide6.QtCore.SignalInstance.emit(self,*args:typing.Any)", nullptr}; // Sentinel void init(PyObject *module) { - if (InitSignatureStrings(PySideMetaSignalTypeF(), MetaSignal_SignatureStrings) < 0) + if (InitSignatureStrings(PySideMetaSignal_TypeF(), MetaSignal_SignatureStrings) < 0) return; - Py_INCREF(PySideMetaSignalTypeF()); - PyModule_AddObject(module, "MetaSignal", reinterpret_cast<PyObject *>(PySideMetaSignalTypeF())); + Py_INCREF(PySideMetaSignal_TypeF()); + auto *obMetaSignal_Type = reinterpret_cast<PyObject *>(PySideMetaSignal_TypeF()); + PyModule_AddObject(module, "MetaSignal", obMetaSignal_Type); - if (InitSignatureStrings(PySideSignalTypeF(), Signal_SignatureStrings) < 0) + if (InitSignatureStrings(PySideSignal_TypeF(), Signal_SignatureStrings) < 0) return; - Py_INCREF(PySideSignalTypeF()); - PyModule_AddObject(module, "Signal", reinterpret_cast<PyObject *>(PySideSignalTypeF())); + Py_INCREF(PySideSignal_TypeF()); + auto *obSignal_Type = reinterpret_cast<PyObject *>(PySideSignal_TypeF()); + PyModule_AddObject(module, "Signal", obSignal_Type); - if (InitSignatureStrings(PySideSignalInstanceTypeF(), SignalInstance_SignatureStrings) < 0) + if (InitSignatureStrings(PySideSignalInstance_TypeF(), SignalInstance_SignatureStrings) < 0) return; - Py_INCREF(PySideSignalInstanceTypeF()); - PyModule_AddObject(module, "SignalInstance", reinterpret_cast<PyObject *>(PySideSignalInstanceTypeF())); + Py_INCREF(PySideSignalInstance_TypeF()); + auto *obSignalInstance_Type = reinterpret_cast<PyObject *>(PySideSignalInstance_TypeF()); + PyModule_AddObject(module, "SignalInstance", obSignalInstance_Type); } bool checkType(PyObject *pyObj) { if (pyObj) - return PyType_IsSubtype(Py_TYPE(pyObj), PySideSignalTypeF()); + return PyType_IsSubtype(Py_TYPE(pyObj), PySideSignal_TypeF()); return false; } -void updateSourceObject(PyObject *source) +bool checkInstanceType(PyObject *pyObj) { - PyTypeObject *objType = reinterpret_cast<PyTypeObject *>(PyObject_Type(source)); - - Py_ssize_t pos = 0; - PyObject *value; - PyObject *key; + return pyObj != nullptr + && PyType_IsSubtype(Py_TYPE(pyObj), PySideSignalInstance_TypeF()) != 0; +} - while (PyDict_Next(objType->tp_dict, &pos, &key, &value)) { - if (PyObject_TypeCheck(value, PySideSignalTypeF())) { - Shiboken::AutoDecRef signalInstance(reinterpret_cast<PyObject *>(PyObject_New(PySideSignalInstance, PySideSignalInstanceTypeF()))); - instanceInitialize(signalInstance.cast<PySideSignalInstance *>(), key, reinterpret_cast<PySideSignal *>(value), source, 0); - PyObject_SetAttr(source, key, signalInstance); +void updateSourceObject(PyObject *source) +{ + // TODO: Provide for actual upstream exception handling. + // For now we'll just return early to avoid further issues. + + if (source == nullptr) // Bad input + return; + + Shiboken::AutoDecRef mroIterator(PyObject_GetIter(source->ob_type->tp_mro)); + + if (mroIterator.isNull()) // Not iterable + return; + + Shiboken::AutoDecRef mroItem{}; + auto *dict = SbkObject_GetDict_NoRef(source); + + // PYSIDE-1431: Walk the mro and update. But see PYSIDE-1751 below. + while ((mroItem.reset(PyIter_Next(mroIterator))), mroItem.object()) { + Py_ssize_t pos = 0; + PyObject *key, *value; + auto *type = reinterpret_cast<PyTypeObject *>(mroItem.object()); + Shiboken::AutoDecRef tpDict(PepType_GetDict(type)); + while (PyDict_Next(tpDict, &pos, &key, &value)) { + if (PyObject_TypeCheck(value, PySideSignal_TypeF())) { + // PYSIDE-1751: We only insert an instance into the instance dict, if a signal + // of the same name is in the mro. This is the equivalent action + // as PyObject_SetAttr, but filtered by existing signal names. + if (!PyDict_GetItem(dict, key)) { + auto *inst = PyObject_New(PySideSignalInstance, PySideSignalInstance_TypeF()); + Shiboken::AutoDecRef signalInstance(reinterpret_cast<PyObject *>(inst)); + auto *si = reinterpret_cast<PySideSignalInstance *>(signalInstance.object()); + instanceInitialize(si, key, reinterpret_cast<PySideSignal *>(value), + source, 0); + if (PyDict_SetItem(dict, key, signalInstance) == -1) + return; // An error occurred while setting the attribute + } + } } } - Py_XDECREF(objType); + if (PyErr_Occurred()) // An iteration error occurred + return; } -QByteArray getTypeName(PyObject *type) +QByteArray getTypeName(PyObject *obType) { - if (PyType_Check(type)) { - if (PyType_IsSubtype(reinterpret_cast<PyTypeObject *>(type), - reinterpret_cast<PyTypeObject *>(SbkObject_TypeF()))) { - auto objType = reinterpret_cast<SbkObjectType *>(type); - return Shiboken::ObjectType::getOriginalName(objType); - } - // Translate python types to Qt names - auto objType = reinterpret_cast<PyTypeObject *>(type); - if (Shiboken::String::checkType(objType)) + if (PyType_Check(obType)) { + auto *type = reinterpret_cast<PyTypeObject *>(obType); + if (PyType_IsSubtype(type, SbkObject_TypeF())) + return Shiboken::ObjectType::getOriginalName(type); + // Translate Python types to Qt names + if (Shiboken::String::checkType(type)) return QByteArrayLiteral("QString"); - if (objType == &PyInt_Type) + if (type == &PyLong_Type) return QByteArrayLiteral("int"); - if (objType == &PyLong_Type) - return QByteArrayLiteral("long"); - if (objType == &PyFloat_Type) + if (type == &PyFloat_Type) return QByteArrayLiteral("double"); - if (objType == &PyBool_Type) + if (type == &PyBool_Type) return QByteArrayLiteral("bool"); - if (objType == &PyList_Type) + if (type == &PyList_Type) return QByteArrayLiteral("QVariantList"); - if (Py_TYPE(objType) == SbkEnumType_TypeF()) - return Shiboken::Enum::getCppName(objType); + if (type == &PyDict_Type) + return QByteArrayLiteral("QVariantMap"); return QByteArrayLiteral("PyObject"); } - if (type == Py_None) // Must be checked before as Shiboken::String::check accepts Py_None + if (obType == Py_None) // Must be checked before as Shiboken::String::check accepts Py_None return voidType(); - if (Shiboken::String::check(type)) { - QByteArray result = Shiboken::String::toCString(type); + if (Shiboken::String::check(obType)) { + QByteArray result = Shiboken::String::toCString(obType); if (result == "qreal") result = sizeof(qreal) == sizeof(double) ? "double" : "float"; return result; @@ -718,59 +971,79 @@ static QByteArray buildSignature(const QByteArray &name, const QByteArray &signa return QMetaObject::normalizedSignature(name + '(' + signature + ')'); } -static QByteArray parseSignature(PyObject *args) +static PySideSignalData::Signature parseSignature(PyObject *args) { - if (args && (Shiboken::String::check(args) || !PySequence_Check(args))) - return getTypeName(args); + PySideSignalData::Signature result{{}, QMetaMethod::Compatibility, 0}; + if (args && (Shiboken::String::check(args) || !PyTuple_Check(args))) { + result.signature = getTypeName(args); + result.argCount = 1; + return result; + } - QByteArray signature; for (Py_ssize_t i = 0, i_max = PySequence_Size(args); i < i_max; i++) { Shiboken::AutoDecRef arg(PySequence_GetItem(args, i)); const auto typeName = getTypeName(arg); if (!typeName.isEmpty()) { - if (!signature.isEmpty()) - signature += ','; - signature += typeName; + if (!result.signature.isEmpty()) + result.signature += ','; + result.signature += typeName; + ++result.argCount; } } - return signature; + return result; } -static void appendSignature(PySideSignal *self, const SignalSignature &signature) +static void sourceGone(void *data) { - self->data->signatures.append({signature.m_parameterTypes, signature.m_attributes}); + auto *self = reinterpret_cast<PySideSignalInstance *>(data); + self->deleted = true; } -static void instanceInitialize(PySideSignalInstance *self, PyObject *name, PySideSignal *data, PyObject *source, int index) +static void instanceInitialize(PySideSignalInstance *self, PyObject *name, PySideSignal *signal, PyObject *source, int index) { self->d = new PySideSignalInstancePrivate; + self->deleted = false; PySideSignalInstancePrivate *selfPvt = self->d; selfPvt->next = nullptr; - if (data->data->signalName.isEmpty()) - data->data->signalName = Shiboken::String::toCString(name); - selfPvt->signalName = data->data->signalName; + if (signal->data->signalName.isEmpty()) + signal->data->signalName = Shiboken::String::toCString(name); + selfPvt->signalName = signal->data->signalName; selfPvt->source = source; - const auto &signature = data->data->signatures.at(index); + const auto &signature = signal->data->signatures.at(index); selfPvt->signature = buildSignature(self->d->signalName, signature.signature); + selfPvt->argCount = signature.argCount; selfPvt->attributes = signature.attributes; - selfPvt->homonymousMethod = 0; - if (data->homonymousMethod) { - selfPvt->homonymousMethod = data->homonymousMethod; + selfPvt->homonymousMethod = nullptr; + if (signal->homonymousMethod) { + selfPvt->homonymousMethod = signal->homonymousMethod; Py_INCREF(selfPvt->homonymousMethod); } + // PYSIDE-2201: We have no reference to source. Let's take a weakref to get + // notified when source gets deleted. + PySide::WeakRef::create(source, sourceGone, self); + index++; - if (index < data->data->signatures.size()) { - selfPvt->next = PyObject_New(PySideSignalInstance, PySideSignalInstanceTypeF()); - instanceInitialize(selfPvt->next, name, data, source, index); + if (index < signal->data->signatures.size()) { + selfPvt->next = PyObject_New(PySideSignalInstance, PySideSignalInstance_TypeF()); + instanceInitialize(selfPvt->next, name, signal, source, index); } } PySideSignalInstance *initialize(PySideSignal *self, PyObject *name, PyObject *object) { + static PyTypeObject *pyQObjectType = Shiboken::Conversions::getPythonTypeObject("QObject*"); + assert(pyQObjectType); + + if (!PyObject_TypeCheck(object, pyQObjectType)) { + PyErr_Format(PyExc_TypeError, "%s cannot be converted to %s", + Py_TYPE(object)->tp_name, pyQObjectType->tp_name); + return nullptr; + } + PySideSignalInstance *instance = PyObject_New(PySideSignalInstance, - PySideSignalInstanceTypeF()); + PySideSignalInstance_TypeF()); instanceInitialize(instance, name, self, object, 0); auto sbkObj = reinterpret_cast<SbkObject *>(object); if (!Shiboken::Object::wasCreatedByPython(sbkObj)) @@ -781,7 +1054,7 @@ PySideSignalInstance *initialize(PySideSignal *self, PyObject *name, PyObject *o bool connect(PyObject *source, const char *signal, PyObject *callback) { Shiboken::AutoDecRef pyMethod(PyObject_GetAttr(source, - PySide::PyName::qtConnect())); + PySide::PySideName::qtConnect())); if (pyMethod.isNull()) return false; @@ -791,7 +1064,7 @@ bool connect(PyObject *source, const char *signal, PyObject *callback) if (result == Py_False) { PyErr_Format(PyExc_RuntimeError, "Failed to connect signal %s, to python callable object.", signal); Py_DECREF(result); - result = 0; + result = nullptr; } return result; } @@ -801,7 +1074,7 @@ PySideSignalInstance *newObjectFromMethod(PyObject *source, const QList<QMetaMet PySideSignalInstance *root = nullptr; PySideSignalInstance *previous = nullptr; for (const QMetaMethod &m : methodList) { - PySideSignalInstance *item = PyObject_New(PySideSignalInstance, PySideSignalInstanceTypeF()); + PySideSignalInstance *item = PyObject_New(PySideSignalInstance, PySideSignalInstance_TypeF()); if (!root) root = item; @@ -809,44 +1082,26 @@ PySideSignalInstance *newObjectFromMethod(PyObject *source, const QList<QMetaMet previous->d->next = item; item->d = new PySideSignalInstancePrivate; + item->deleted = false; PySideSignalInstancePrivate *selfPvt = item->d; selfPvt->source = source; - Py_INCREF(selfPvt->source); // PYSIDE-79: an INCREF is missing. QByteArray cppName(m.methodSignature()); cppName.truncate(cppName.indexOf('(')); - // separe SignalName + // separate SignalName selfPvt->signalName = cppName; selfPvt->signature = m.methodSignature(); + selfPvt->argCount = int(m.parameterCount()); selfPvt->attributes = m.attributes(); - selfPvt->homonymousMethod = 0; - selfPvt->next = 0; + selfPvt->homonymousMethod = nullptr; + selfPvt->next = nullptr; } return root; } -template<typename T> -static typename T::value_type join(T t, const char *sep) -{ - typename T::value_type res; - if (t.isEmpty()) - return res; - - typename T::const_iterator it = t.begin(); - typename T::const_iterator end = t.end(); - res += *it; - ++it; - - while (it != end) { - res += sep; - res += *it; - ++it; - } - return res; -} - -static void _addSignalToWrapper(SbkObjectType *wrapperType, const char *signalName, PySideSignal *signal) +static void _addSignalToWrapper(PyTypeObject *wrapperType, const char *signalName, PySideSignal *signal) { - auto typeDict = reinterpret_cast<PyTypeObject *>(wrapperType)->tp_dict; + Shiboken::AutoDecRef tpDict(PepType_GetDict(wrapperType)); + auto typeDict = tpDict.object(); PyObject *homonymousMethod; if ((homonymousMethod = PyDict_GetItemString(typeDict, signalName))) { Py_INCREF(homonymousMethod); @@ -856,9 +1111,10 @@ static void _addSignalToWrapper(SbkObjectType *wrapperType, const char *signalNa } // This function is used by qStableSort to promote empty signatures -static bool compareSignals(const SignalSignature &sig1, const SignalSignature &) +static bool compareSignals(const PySideSignalData::Signature &sig1, + const PySideSignalData::Signature &sig2) { - return sig1.m_parameterTypes.isEmpty(); + return sig1.signature.isEmpty() && !sig2.signature.isEmpty(); } static PyObject *buildQtCompatible(const QByteArray &signature) @@ -867,41 +1123,48 @@ static PyObject *buildQtCompatible(const QByteArray &signature) return Shiboken::String::fromStringAndSize(ba, ba.size()); } -void registerSignals(SbkObjectType *pyObj, const QMetaObject *metaObject) +void registerSignals(PyTypeObject *pyObj, const QMetaObject *metaObject) { - typedef QHash<QByteArray, QList<SignalSignature> > SignalSigMap; - SignalSigMap signalsFound; + using Signature = PySideSignalData::Signature; + struct MetaSignal + { + QByteArray methodName; + QList<Signature> signatures; + }; + + QList<MetaSignal> signalsFound; for (int i = metaObject->methodOffset(), max = metaObject->methodCount(); i < max; ++i) { QMetaMethod method = metaObject->method(i); if (method.methodType() == QMetaMethod::Signal) { QByteArray methodName(method.methodSignature()); - methodName.chop(methodName.size() - methodName.indexOf('(')); - SignalSignature signature; - signature.m_parameterTypes = join(method.parameterTypes(), ","); + methodName.truncate(methodName.indexOf('(')); + Signature signature{method.parameterTypes().join(','), {}, + short(method.parameterCount())}; if (method.attributes() & QMetaMethod::Cloned) - signature.m_attributes = QMetaMethod::Cloned; - signalsFound[methodName] << signature; + signature.attributes = QMetaMethod::Cloned; + auto it = std::find_if(signalsFound.begin(), signalsFound.end(), + [methodName](const MetaSignal &ms) + { return ms.methodName == methodName; }); + if (it != signalsFound.end()) + it->signatures << signature; + else + signalsFound.append(MetaSignal{methodName, {signature}}); } } - SignalSigMap::Iterator it = signalsFound.begin(); - SignalSigMap::Iterator end = signalsFound.end(); - for (; it != end; ++it) { - PySideSignal *self = PyObject_New(PySideSignal, PySideSignalTypeF()); + for (const auto &metaSignal : std::as_const(signalsFound)) { + PySideSignal *self = PyObject_New(PySideSignal, PySideSignal_TypeF()); self->data = new PySideSignalData; - self->data->signalName = it.key(); - self->homonymousMethod = 0; + self->data->signalName = metaSignal.methodName; + self->homonymousMethod = nullptr; // Empty signatures comes first! So they will be the default signal signature - std::stable_sort(it.value().begin(), it.value().end(), &compareSignals); - const auto endJ = it.value().cend(); - for (auto j = it.value().cbegin(); j != endJ; ++j) { - const SignalSignature &sig = *j; - appendSignature(self, sig); - } + self->data->signatures = metaSignal.signatures; + std::stable_sort(self->data->signatures.begin(), + self->data->signatures.end(), &compareSignals); - _addSignalToWrapper(pyObj, it.key(), self); + _addSignalToWrapper(pyObj, metaSignal.methodName, self); Py_DECREF(reinterpret_cast<PyObject *>(self)); } } @@ -916,54 +1179,82 @@ const char *getSignature(PySideSignalInstance *signal) return signal->d->signature; } -QStringList getArgsFromSignature(const char *signature, bool *isShortCircuit) +EmitterData getEmitterData(PySideSignalInstance *signal) { - QString qsignature = QString::fromLatin1(signature).trimmed(); - QStringList result; + EmitterData result; + result.emitter = PySide::convertToQObject(getObject(signal), false); + if (result.emitter != nullptr) { + auto *mo = result.emitter->metaObject(); + result.methodIndex = mo->indexOfMethod(getSignature(signal)); + } + return result; +} + +QByteArrayList getArgsFromSignature(const char *signature) +{ + QByteArray qsignature = QByteArray(signature).trimmed(); + QByteArrayList result; - if (isShortCircuit) - *isShortCircuit = !qsignature.contains(QLatin1Char('(')); - if (qsignature.contains(QLatin1String("()")) || qsignature.contains(QLatin1String("(void)"))) + if (qsignature.contains("()") || qsignature.contains("(void)")) return result; - if (qsignature.endsWith(QLatin1Char(')'))) { - const int paren = qsignature.indexOf(QLatin1Char('(')); + if (qsignature.endsWith(')')) { + const auto paren = qsignature.indexOf('('); if (paren >= 0) { qsignature.chop(1); qsignature.remove(0, paren + 1); - result = qsignature.split(QLatin1Char(',')); - for (QString &type : result) + result = qsignature.split(u','); + for (auto &type : result) type = type.trimmed(); } } return result; } -QString getCallbackSignature(const char *signal, QObject *receiver, PyObject *callback, bool encodeName) +QByteArray getCallbackSignature(const char *signal, QObject *receiver, + PyObject *callback, bool encodeName) { QByteArray functionName; - int numArgs = -1; - bool useSelf = false; - bool isMethod = PyMethod_Check(callback); - bool isFunction = PyFunction_Check(callback); - - if (isMethod || isFunction) { - PyObject *function = isMethod ? PyMethod_GET_FUNCTION(callback) : callback; - auto objCode = reinterpret_cast<PepCodeObject *>(PyFunction_GET_CODE(function)); - functionName = Shiboken::String::toCString(PepFunction_GetName(function)); - useSelf = isMethod; - numArgs = PepCode_GET_FLAGS(objCode) & CO_VARARGS ? -1 : PepCode_GET_ARGCOUNT(objCode); + qsizetype numArgs = -1; + + const auto slotArgs = extractFunctionArgumentsFromSlot(callback); + qsizetype useSelf = slotArgs.isMethod ? 1 : 0; + + if (slotArgs.function != nullptr) { + numArgs = argCount(slotArgs).max; +#ifdef PYPY_VERSION + } else if (Py_TYPE(callback) == PepBuiltinMethod_TypePtr) { + // PYSIDE-535: PyPy has a special builtin method that acts almost like PyCFunction. + Shiboken::AutoDecRef temp(PyObject_GetAttr(callback, Shiboken::PyMagicName::name())); + functionName = Shiboken::String::toCString(temp); + useSelf = true; + + if (receiver) { + // Search for signature on metaobject + const QMetaObject *mo = receiver->metaObject(); + QByteArray prefix(functionName); + prefix += '('; + for (int i = 0; i < mo->methodCount(); i++) { + QMetaMethod me = mo->method(i); + if ((strncmp(me.methodSignature(), prefix, prefix.size()) == 0) && + QMetaObject::checkConnectArgs(signal, me.methodSignature())) { + numArgs = me.parameterTypes().size() + useSelf; + break; + } + } + } +#endif } else if (PyCFunction_Check(callback)) { const PyCFunctionObject *funcObj = reinterpret_cast<const PyCFunctionObject *>(callback); functionName = PepCFunction_GET_NAMESTR(funcObj); - useSelf = PyCFunction_GET_SELF(funcObj); + useSelf = PyCFunction_GET_SELF(funcObj) != nullptr ? 1 : 0; const int flags = PyCFunction_GET_FLAGS(funcObj); if (receiver) { - //Search for signature on metaobject + // Search for signature on metaobject const QMetaObject *mo = receiver->metaObject(); QByteArray prefix(functionName); prefix += '('; - for (int i = 0; i < mo->methodCount(); i++) { + for (int i = 0, count = mo->methodCount(); i < count; ++i) { QMetaMethod me = mo->method(i); if ((strncmp(me.methodSignature(), prefix, prefix.size()) == 0) && QMetaObject::checkConnectArgs(signal, me.methodSignature())) { @@ -980,27 +1271,28 @@ QString getCallbackSignature(const char *signal, QObject *receiver, PyObject *ca numArgs = 0; } } else if (PyCallable_Check(callback)) { - functionName = "__callback" + QByteArray::number((qlonglong)callback); + functionName = "__callback" + QByteArray::number(quintptr(callback)); } + if (functionName.isEmpty() && slotArgs.functionName != nullptr) + functionName = Shiboken::String::toCString(slotArgs.functionName); Q_ASSERT(!functionName.isEmpty()); - bool isShortCircuit = false; - - const QString functionNameS = QLatin1String(functionName); - QString signature = encodeName ? codeCallbackName(callback, functionNameS) : functionNameS; - QStringList args = getArgsFromSignature(signal, &isShortCircuit); + if (functionName.startsWith('<') && functionName.endsWith('>')) { // fix "<lambda>" + functionName[0] = '_'; + functionName[functionName.size() - 1] = '_'; + } + QByteArray signature = encodeName ? codeCallbackName(callback, functionName) : functionName; + QByteArrayList args = getArgsFromSignature(signal); - if (!isShortCircuit) { - signature.append(QLatin1Char('(')); - if (numArgs == -1) - numArgs = std::numeric_limits<int>::max(); - while (args.count() && (args.count() > (numArgs - useSelf))) { + signature.append(u'('); + if (numArgs != -1) { + while (!args.isEmpty() && (args.size() > (numArgs - useSelf))) args.removeLast(); - } - signature.append(args.join(QLatin1Char(','))); - signature.append(QLatin1Char(')')); } + signature.append(args.join(',')); + signature.append(')'); + return signature; } @@ -1018,14 +1310,21 @@ bool checkQtSignal(const char *signal) return true; } -QString codeCallbackName(PyObject *callback, const QString &funcName) +QByteArray codeCallbackName(PyObject *callback, const QByteArray &funcName) { if (PyMethod_Check(callback)) { PyObject *self = PyMethod_GET_SELF(callback); PyObject *func = PyMethod_GET_FUNCTION(callback); - return funcName + QString::number(quint64(self), 16) + QString::number(quint64(func), 16); + return funcName + QByteArray::number(quint64(self), 16) + QByteArray::number(quint64(func), 16); } - return funcName + QString::number(quint64(callback), 16); + // PYSIDE-1523: Handle the compiled case. + if (PySide::isCompiledMethod(callback)) { + // Not retaining references inline with what PyMethod_GET_(SELF|FUNC) does. + Shiboken::AutoDecRef self(PyObject_GetAttr(callback, PySide::PySideName::im_self())); + Shiboken::AutoDecRef func(PyObject_GetAttr(callback, PySide::PySideName::im_func())); + return funcName + QByteArray::number(quint64(self), 16) + QByteArray::number(quint64(func), 16); + } + return funcName + QByteArray::number(quint64(callback), 16); } QByteArray voidType() @@ -1033,6 +1332,4 @@ QByteArray voidType() return QByteArrayLiteral("void"); } -} //namespace Signal -} //namespace PySide - +} //namespace PySide::Signal diff --git a/sources/pyside6/libpyside/pysidesignal.h b/sources/pyside6/libpyside/pysidesignal.h index 55e1ee405..7493f94b5 100644 --- a/sources/pyside6/libpyside/pysidesignal.h +++ b/sources/pyside6/libpyside/pysidesignal.h @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2020 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of Qt for Python. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2020 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 PYSIDE_SIGNAL_H #define PYSIDE_SIGNAL_H @@ -55,8 +19,8 @@ QT_END_NAMESPACE extern "C" { - extern PYSIDE_API PyTypeObject *PySideSignalTypeF(void); - extern PYSIDE_API PyTypeObject *PySideSignalInstanceTypeF(void); + extern PYSIDE_API PyTypeObject *PySideSignal_TypeF(void); + extern PYSIDE_API PyTypeObject *PySideSignalInstance_TypeF(void); // Internal object struct PYSIDE_API PySideSignal; @@ -66,18 +30,32 @@ extern "C" { PyObject_HEAD PySideSignalInstancePrivate *d; + bool deleted; }; }; // extern "C" -namespace PySide { -namespace Signal { +namespace PySide::Signal { -PYSIDE_API bool checkType(PyObject *type); +/** + * This function checks for the PySideSignal type. + * + * @param pyObj + * @return whether pyObj is a PySideSignal + **/ +PYSIDE_API bool checkType(PyObject *pyObj); + +/** + * This function checks for the PySideSignalInstanceType type. + * + * @param pyObj + * @return Whether pyObj is a PySideSignalInstance + **/ +PYSIDE_API bool checkInstanceType(PyObject *pyObj); /** * Register all C++ signals of a QObject on Python type. */ -PYSIDE_API void registerSignals(SbkObjectType *pyObj, const QMetaObject *metaObject); +PYSIDE_API void registerSignals(PyTypeObject *pyObj, const QMetaObject *metaObject); /** * This function creates a Signal object which stays attached to QObject class based on a list of QMetaMethods @@ -114,6 +92,18 @@ PYSIDE_API PyObject *getObject(PySideSignalInstance *signal); **/ PYSIDE_API const char *getSignature(PySideSignalInstance *signal); +struct EmitterData +{ + QObject *emitter = nullptr; + int methodIndex = -1; +}; + +/// A convenience to retrieve the emitter data from a signal instance +/// +/// @param signal The Signal object +/// @return Data structure +PYSIDE_API EmitterData getEmitterData(PySideSignalInstance *signal); + /** * This function is used to retrieve the signal signature * @@ -145,20 +135,18 @@ PYSIDE_API bool checkQtSignal(const char *signature); * @param encodeName Used to specify if the returned signature will be encoded with Qt signal/slot style * @return Return the callback signature **/ -PYSIDE_API QString getCallbackSignature(const char *signal, QObject *receiver, PyObject *callback, bool encodeName); +PYSIDE_API QByteArray getCallbackSignature(const char *signal, QObject *receiver, + PyObject *callback, bool encodeName); /** * This function parses the signature and then returns a list of argument types. * * @param signature The signal signature - * @param isShortCircuit If this is a shortCircuit(python<->python) signal * @return Return true if this is a Qt Signal, otherwise return false * @todo replace return type by QList<QByteArray> **/ -QStringList getArgsFromSignature(const char *signature, - bool *isShortCircuit = nullptr); +QByteArrayList getArgsFromSignature(const char *signature); -} // namespace Signal -} // namespace PySide +} // namespace PySide::Signal #endif diff --git a/sources/pyside6/libpyside/pysidesignal_p.h b/sources/pyside6/libpyside/pysidesignal_p.h index 345c9e565..55a9a7a70 100644 --- a/sources/pyside6/libpyside/pysidesignal_p.h +++ b/sources/pyside6/libpyside/pysidesignal_p.h @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of Qt for Python. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// 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 PYSIDE_QSIGNAL_P_H #define PYSIDE_QSIGNAL_P_H @@ -49,18 +13,19 @@ struct PySideSignalData { struct Signature { - QByteArray signature; - int attributes; + QByteArray signature; // ','-separated list of parameter types + unsigned short attributes; + short argCount; }; QByteArray signalName; QList<Signature> signatures; - QByteArrayList *signalArguments; + QByteArrayList signalArguments; }; extern "C" { - extern PyTypeObject *PySideSignalTypeF(void); + extern PyTypeObject *PySideSignal_TypeF(void); struct PySideSignal { PyObject_HEAD @@ -75,20 +40,21 @@ struct PySideSignalInstancePrivate { QByteArray signalName; QByteArray signature; - int attributes = 0; PyObject *source = nullptr; PyObject *homonymousMethod = nullptr; PySideSignalInstance *next = nullptr; + unsigned short attributes = 0; + short argCount = 0; }; -namespace PySide { namespace Signal { +namespace PySide::Signal { void init(PyObject *module); bool connect(PyObject *source, const char *signal, PyObject *callback); QByteArray getTypeName(PyObject *); - QString codeCallbackName(PyObject *callback, const QString &funcName); + QByteArray codeCallbackName(PyObject *callback, const QByteArray &funcName); QByteArray voidType(); -}} //namespace PySide +} // namespace PySide::Signal #endif diff --git a/sources/pyside6/libpyside/pysideslot.cpp b/sources/pyside6/libpyside/pysideslot.cpp index 81f93c555..fa7e89f42 100644 --- a/sources/pyside6/libpyside/pysideslot.cpp +++ b/sources/pyside6/libpyside/pysideslot.cpp @@ -1,45 +1,9 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of Qt for Python. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#include "dynamicqmetaobject_p.h" +// 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 "pysidesignal_p.h" #include "pysideslot_p.h" +#include "pysidestaticstrings.h" #include <shiboken.h> @@ -54,56 +18,69 @@ struct SlotData QByteArray name; QByteArray args; QByteArray resultType; + QByteArray tag; // QMetaMethod::tag() }; -typedef struct +struct PySideSlot { PyObject_HEAD SlotData *slotData; -} PySideSlot; +}; extern "C" { +static void slotDataListDestructor(PyObject *o) +{ + delete PySide::Slot::dataListFromCapsule(o); +} + static int slotTpInit(PyObject *, PyObject *, PyObject *); static PyObject *slotCall(PyObject *, PyObject *, PyObject *); // Class Definition ----------------------------------------------- -static PyType_Slot PySideSlotType_slots[] = { - {Py_tp_call, (void *)slotCall}, - {Py_tp_init, (void *)slotTpInit}, - {Py_tp_new, (void *)PyType_GenericNew}, - {Py_tp_dealloc, (void *)Sbk_object_dealloc}, - {0, 0} -}; -static PyType_Spec PySideSlotType_spec = { - "2:PySide6.QtCore.Slot", - sizeof(PySideSlot), - 0, - Py_TPFLAGS_DEFAULT, - PySideSlotType_slots, -}; +static PyTypeObject *createSlotType() +{ + PyType_Slot PySideSlotType_slots[] = { + {Py_tp_call, reinterpret_cast<void *>(slotCall)}, + {Py_tp_init, reinterpret_cast<void *>(slotTpInit)}, + {Py_tp_new, reinterpret_cast<void *>(PyType_GenericNew)}, + {Py_tp_dealloc, reinterpret_cast<void *>(Sbk_object_dealloc)}, + {0, nullptr} + }; + + PyType_Spec PySideSlotType_spec = { + "2:PySide6.QtCore.Slot", + sizeof(PySideSlot), + 0, + Py_TPFLAGS_DEFAULT, + PySideSlotType_slots, + }; + + return SbkType_FromSpec(&PySideSlotType_spec); +} -static PyTypeObject *PySideSlotTypeF(void) +static PyTypeObject *PySideSlot_TypeF() { - static PyTypeObject *type = reinterpret_cast<PyTypeObject *>( - SbkType_FromSpec(&PySideSlotType_spec)); + static auto *type = createSlotType(); return type; } int slotTpInit(PyObject *self, PyObject *args, PyObject *kw) { static PyObject *emptyTuple = nullptr; - static const char *kwlist[] = {"name", "result", nullptr}; + static const char *kwlist[] = {"name", "result", "tag", nullptr}; char *argName = nullptr; PyObject *argResult = nullptr; + char *tag = nullptr; - if (emptyTuple == 0) + if (emptyTuple == nullptr) emptyTuple = PyTuple_New(0); - if (!PyArg_ParseTupleAndKeywords(emptyTuple, kw, "|sO:QtCore.Slot", - const_cast<char **>(kwlist), &argName, &argResult)) { + if (!PyArg_ParseTupleAndKeywords(emptyTuple, kw, "|sOs:QtCore.Slot", + const_cast<char **>(kwlist), + &argName, &argResult, &tag)) { return -1; } @@ -125,6 +102,9 @@ int slotTpInit(PyObject *self, PyObject *args, PyObject *kw) if (argName) data->slotData->name = argName; + if (tag) + data->slotData->tag = tag; + data->slotData->resultType = argResult ? PySide::Signal::getTypeName(argResult) : PySide::Signal::voidType(); @@ -133,12 +113,13 @@ int slotTpInit(PyObject *self, PyObject *args, PyObject *kw) PyObject *slotCall(PyObject *self, PyObject *args, PyObject * /* kw */) { - static PyObject *pySlotName = nullptr; - PyObject *callback; - callback = PyTuple_GetItem(args, 0); + PyObject *callback = nullptr; + + if (!PyArg_UnpackTuple(args, "Slot.__call__", 1, 1, &callback)) + return nullptr; Py_INCREF(callback); - if (Py_TYPE(callback)->tp_call != nullptr) { + if (PyCallable_Check(callback)) { PySideSlot *data = reinterpret_cast<PySideSlot *>(self); if (!data->slotData) @@ -147,53 +128,56 @@ PyObject *slotCall(PyObject *self, PyObject *args, PyObject * /* kw */) if (data->slotData->name.isEmpty()) { // PYSIDE-198: Use PyObject_GetAttr instead of PepFunction_GetName to support Nuitka. AutoDecRef funcName(PyObject_GetAttr(callback, PyMagicName::name())); - data->slotData->name = String::toCString(funcName); + data->slotData->name = funcName.isNull() ? "<no name>" : String::toCString(funcName); } const QByteArray returnType = QMetaObject::normalizedType(data->slotData->resultType); - const QByteArray signature = - returnType + ' ' + data->slotData->name + '(' + data->slotData->args + ')'; + const QByteArray signature = data->slotData->name + '(' + data->slotData->args + ')'; - if (!pySlotName) - pySlotName = String::fromCString(PYSIDE_SLOT_LIST_ATTR); - - PyObject *pySignature = String::fromCString(signature); - PyObject *signatureList = nullptr; + PyObject *pySlotName = PySide::PySideMagicName::slot_list_attr(); + PySide::Slot::DataList *entryList = nullptr; if (PyObject_HasAttr(callback, pySlotName)) { - signatureList = PyObject_GetAttr(callback, pySlotName); + auto *capsule = PyObject_GetAttr(callback, pySlotName); + entryList = PySide::Slot::dataListFromCapsule(capsule); } else { - signatureList = PyList_New(0); - PyObject_SetAttr(callback, pySlotName, signatureList); - Py_DECREF(signatureList); + entryList = new PySide::Slot::DataList{}; + auto *capsule = PyCapsule_New(entryList, nullptr /* name */, slotDataListDestructor); + Py_INCREF(capsule); + PyObject_SetAttr(callback, pySlotName, capsule); } - - PyList_Append(signatureList, pySignature); - Py_DECREF(pySignature); + entryList->append({signature, returnType, data->slotData->tag}); //clear data delete data->slotData; data->slotData = nullptr; - return callback; } return callback; } } // extern "C" -namespace PySide { -namespace Slot { +namespace PySide::Slot { + +DataList *dataListFromCapsule(PyObject *capsule) +{ + if (capsule != nullptr && PyCapsule_CheckExact(capsule) != 0) { + if (void *v = PyCapsule_GetPointer(capsule, nullptr)) + return reinterpret_cast<DataList *>(v); + } + return nullptr; +} static const char *Slot_SignatureStrings[] = { - "PySide6.QtCore.Slot(self,*types:type,name:str=nullptr,result:str=nullptr)->typing.Callable[...,typing.Optional[str]]", + "PySide6.QtCore.Slot(self,*types:type,name:str=nullptr,result:type=nullptr)", + "PySide6.QtCore.Slot.__call__(self,function:typing.Callable)->typing.Any", nullptr}; // Sentinel void init(PyObject *module) { - if (InitSignatureStrings(PySideSlotTypeF(), Slot_SignatureStrings) < 0) + if (InitSignatureStrings(PySideSlot_TypeF(), Slot_SignatureStrings) < 0) return; - Py_INCREF(PySideSlotTypeF()); - PyModule_AddObject(module, "Slot", reinterpret_cast<PyObject *>(PySideSlotTypeF())); + Py_INCREF(PySideSlot_TypeF()); + PyModule_AddObject(module, "Slot", reinterpret_cast<PyObject *>(PySideSlot_TypeF())); } -} // namespace Slot -} // namespace PySide +} // namespace PySide::Slot diff --git a/sources/pyside6/libpyside/pysideslot_p.h b/sources/pyside6/libpyside/pysideslot_p.h index 3d98e15c4..9852301ee 100644 --- a/sources/pyside6/libpyside/pysideslot_p.h +++ b/sources/pyside6/libpyside/pysideslot_p.h @@ -1,49 +1,28 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of Qt for Python. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// 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 PYSIDE_SLOT_P_H #define PYSIDE_SLOT_P_H #include <sbkpython.h> -#define PYSIDE_SLOT_LIST_ATTR "_slots" -namespace PySide { namespace Slot { - void init(PyObject* module); -}} +#include <QtCore/qbytearray.h> +#include <QtCore/qlist.h> -#endif +namespace PySide::Slot { + +struct Data { + QByteArray signature; + QByteArray resultType; + QByteArray tag; // QMetaMethod::tag() +}; + +// This list is set as an attribute named PySide::PySideMagicName::slot_list_attr() +// by the decorator for usage by MetaObjectBuilder. +using DataList = QList<Data>; + +DataList *dataListFromCapsule(PyObject *capsule); + +void init(PyObject* module); +} // namespace PySide::Slot + +#endif // PYSIDE_SLOT_P_H diff --git a/sources/pyside6/libpyside/pysidestaticstrings.cpp b/sources/pyside6/libpyside/pysidestaticstrings.cpp index 7705fd058..3bddc84c2 100644 --- a/sources/pyside6/libpyside/pysidestaticstrings.cpp +++ b/sources/pyside6/libpyside/pysidestaticstrings.cpp @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2019 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of Qt for Python. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2019 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 "pysidestaticstrings.h" #include <sbkstring.h> @@ -49,22 +13,28 @@ PyObject *funcName() \ namespace PySide { -namespace PyName +namespace PySideName { -STATIC_STRING_IMPL(qtStaticMetaObject, "staticMetaObject") STATIC_STRING_IMPL(qtConnect, "connect") STATIC_STRING_IMPL(qtDisconnect, "disconnect") STATIC_STRING_IMPL(qtEmit, "emit") STATIC_STRING_IMPL(dict_ring, "dict_ring") +STATIC_STRING_IMPL(fset, "fset") +STATIC_STRING_IMPL(im_func, "im_func") +STATIC_STRING_IMPL(im_self, "im_self") STATIC_STRING_IMPL(name, "name") +STATIC_STRING_IMPL(orig_dict, "orig_dict") +STATIC_STRING_IMPL(parameters, "parameters") STATIC_STRING_IMPL(property, "property") STATIC_STRING_IMPL(select_id, "select_id") } // namespace PyName -namespace PyMagicName +namespace PySideMagicName { +STATIC_STRING_IMPL(code, "__code__") STATIC_STRING_IMPL(doc, "__doc__") STATIC_STRING_IMPL(func, "__func__") STATIC_STRING_IMPL(name, "__name__") STATIC_STRING_IMPL(property_methods, "__property_methods__") +STATIC_STRING_IMPL(slot_list_attr, "_slots") } // namespace PyMagicName } // namespace PySide diff --git a/sources/pyside6/libpyside/pysidestaticstrings.h b/sources/pyside6/libpyside/pysidestaticstrings.h index 6774e936a..b4bc61800 100644 --- a/sources/pyside6/libpyside/pysidestaticstrings.h +++ b/sources/pyside6/libpyside/pysidestaticstrings.h @@ -1,66 +1,37 @@ -/**************************************************************************** -** -** Copyright (C) 2019 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of Qt for Python. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2019 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 PYSIDESTRINGS_H #define PYSIDESTRINGS_H #include <sbkpython.h> +#include <pysidemacros.h> namespace PySide { -namespace PyName +namespace PySideName { -PyObject *qtStaticMetaObject(); -PyObject *qtConnect(); -PyObject *qtDisconnect(); -PyObject *qtEmit(); -PyObject *dict_ring(); -PyObject *name(); -PyObject *property(); -PyObject *select_id(); +PYSIDE_API PyObject *qtConnect(); +PYSIDE_API PyObject *qtDisconnect(); +PYSIDE_API PyObject *qtEmit(); +PYSIDE_API PyObject *dict_ring(); +PYSIDE_API PyObject *fset(); +PYSIDE_API PyObject *im_func(); +PYSIDE_API PyObject *im_self(); +PYSIDE_API PyObject *name(); +PYSIDE_API PyObject *orig_dict(); +PYSIDE_API PyObject *parameters(); +PYSIDE_API PyObject *property(); +PYSIDE_API PyObject *select_id(); } // namespace PyName -namespace PyMagicName +namespace PySideMagicName { -PyObject *doc(); -PyObject *func(); -PyObject *name(); -PyObject *property_methods(); +PYSIDE_API PyObject *code(); +PYSIDE_API PyObject *doc(); +PYSIDE_API PyObject *func(); +PYSIDE_API PyObject *name(); +PYSIDE_API PyObject *property_methods(); +PYSIDE_API PyObject *slot_list_attr(); } // namespace PyMagicName } // namespace PySide diff --git a/sources/pyside6/libpyside/pysideutils.h b/sources/pyside6/libpyside/pysideutils.h new file mode 100644 index 000000000..47c2f2c1b --- /dev/null +++ b/sources/pyside6/libpyside/pysideutils.h @@ -0,0 +1,69 @@ +// 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 PYSIDEUTILS_H +#define PYSIDEUTILS_H + +#include <sbkpython.h> + +#include <pysidemacros.h> + +#include <QtCore/qtclasshelpermacros.h> + +QT_FORWARD_DECLARE_CLASS(QDebug) +QT_FORWARD_DECLARE_CLASS(QString) +QT_FORWARD_DECLARE_CLASS(QStringView) + +namespace PySide +{ + +/// Check if self inherits from class_name +/// \param self Python object +/// \param class_name strict with the class name +/// \return Returns true if self object inherits from class_name, otherwise returns false +PYSIDE_API bool inherits(PyTypeObject *self, const char *class_name); + +/// Given A PyObject representing Unicode data, returns an equivalent QString. +PYSIDE_API QString pyUnicodeToQString(PyObject *str); + +/// Given a QString, return the PyObject repeesenting Unicode data. +PYSIDE_API PyObject *qStringToPyUnicode(QStringView s); + +/// Given A PyObject representing ASCII or Unicode data, returns an equivalent QString. +PYSIDE_API QString pyStringToQString(PyObject *str); + +/// Provide an efficient, correct PathLike interface. +PYSIDE_API QString pyPathToQString(PyObject *path); + +PYSIDE_API bool isCompiledMethod(PyObject *callback); + +struct debugPyTypeObject +{ + PYSIDE_API explicit debugPyTypeObject(const PyTypeObject *o) noexcept; + + const PyTypeObject *m_object; +}; + +PYSIDE_API QDebug operator<<(QDebug debug, const debugPyTypeObject &o); + +struct debugPyObject +{ + PYSIDE_API explicit debugPyObject(PyObject *o) noexcept; + + PyObject *m_object; +}; + +PYSIDE_API QDebug operator<<(QDebug debug, const debugPyObject &o); + +struct debugPyBuffer +{ + PYSIDE_API explicit debugPyBuffer(Py_buffer *b) noexcept; + + Py_buffer *m_buffer; +}; + +PYSIDE_API QDebug operator<<(QDebug debug, const debugPyBuffer &b); + +} //namespace PySide + +#endif // PYSIDESTRING_H diff --git a/sources/pyside6/libpyside/pysideweakref.cpp b/sources/pyside6/libpyside/pysideweakref.cpp index cd90634bd..5f3ca59e4 100644 --- a/sources/pyside6/libpyside/pysideweakref.cpp +++ b/sources/pyside6/libpyside/pysideweakref.cpp @@ -1,74 +1,42 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of Qt for Python. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// 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 "pysideweakref.h" #include <sbkpython.h> #include <shiboken.h> -typedef struct { +struct PySideCallableObject { PyObject_HEAD /* Type-specific fields go here. */ PySideWeakRefFunction weakref_func; void *user_data; -} PySideCallableObject; +}; static PyObject *CallableObject_call(PyObject *callable_object, PyObject *args, PyObject *kw); -static PyType_Slot PySideCallableObjectType_slots[] = { - {Py_tp_call, (void *)CallableObject_call}, - {Py_tp_dealloc, (void *)Sbk_object_dealloc}, - {0, 0} -}; -static PyType_Spec PySideCallableObjectType_spec = { - "1:PySide.Callable", - sizeof(PySideCallableObject), - 0, - Py_TPFLAGS_DEFAULT, - PySideCallableObjectType_slots, -}; - +static PyTypeObject *createCallableObjectType() +{ + PyType_Slot PySideCallableObjectType_slots[] = { + {Py_tp_call, reinterpret_cast<void *>(CallableObject_call)}, + {Py_tp_dealloc, reinterpret_cast<void *>(Sbk_object_dealloc)}, + {0, nullptr} + }; + + PyType_Spec PySideCallableObjectType_spec = { + "1:PySide.Callable", + sizeof(PySideCallableObject), + 0, + Py_TPFLAGS_DEFAULT, + PySideCallableObjectType_slots, + }; + + return SbkType_FromSpec(&PySideCallableObjectType_spec); +} -static PyTypeObject *PySideCallableObjectTypeF() +static PyTypeObject *PySideCallableObject_TypeF() { - static PyTypeObject *type = - reinterpret_cast<PyTypeObject *>(SbkType_FromSpec(&PySideCallableObjectType_spec)); + static auto *type = createCallableObjectType(); return type; } @@ -81,27 +49,28 @@ static PyObject *CallableObject_call(PyObject *callable_object, PyObject *args, Py_RETURN_NONE; } -namespace PySide { namespace WeakRef { +namespace PySide::WeakRef { PyObject *create(PyObject *obj, PySideWeakRefFunction func, void *userData) { if (obj == Py_None) - return 0; + return nullptr; - if (Py_TYPE(PySideCallableObjectTypeF()) == 0) - { - Py_TYPE(PySideCallableObjectTypeF()) = &PyType_Type; - PyType_Ready(PySideCallableObjectTypeF()); + auto *callableObject_Type = PySideCallableObject_TypeF(); + auto *callableObject_PyObject = reinterpret_cast<PyObject *>(callableObject_Type); + if (callableObject_PyObject->ob_type == nullptr) { + callableObject_PyObject->ob_type = &PyType_Type; + PyType_Ready(callableObject_Type); } - PyTypeObject *type = PySideCallableObjectTypeF(); + PyTypeObject *type = PySideCallableObject_TypeF(); PySideCallableObject *callable = PyObject_New(PySideCallableObject, type); if (!callable || PyErr_Occurred()) - return 0; + return nullptr; PyObject *weak = PyWeakref_NewRef(obj, reinterpret_cast<PyObject *>(callable)); if (!weak || PyErr_Occurred()) - return 0; + return nullptr; callable->weakref_func = func; callable->user_data = userData; @@ -110,5 +79,4 @@ PyObject *create(PyObject *obj, PySideWeakRefFunction func, void *userData) return reinterpret_cast<PyObject *>(weak); } -} } //namespace - +} // namespace PySide::WeakRef diff --git a/sources/pyside6/libpyside/pysideweakref.h b/sources/pyside6/libpyside/pysideweakref.h index 628c1eda4..e29c73455 100644 --- a/sources/pyside6/libpyside/pysideweakref.h +++ b/sources/pyside6/libpyside/pysideweakref.h @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of Qt for Python. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// 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 __PYSIDEWEAKREF__ #define __PYSIDEWEAKREF__ @@ -43,14 +7,12 @@ #include <pysidemacros.h> #include <sbkpython.h> -typedef void (*PySideWeakRefFunction)(void* userData); +using PySideWeakRefFunction = void (*)(void *userData); -namespace PySide { namespace WeakRef { +namespace PySide::WeakRef { PYSIDE_API PyObject* create(PyObject* ob, PySideWeakRefFunction func, void* userData); -} //PySide -} //WeakRef - +} // namespace PySide::WeakRef #endif diff --git a/sources/pyside6/libpyside/qobjectconnect.cpp b/sources/pyside6/libpyside/qobjectconnect.cpp new file mode 100644 index 000000000..3c5b75953 --- /dev/null +++ b/sources/pyside6/libpyside/qobjectconnect.cpp @@ -0,0 +1,336 @@ +// 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 "qobjectconnect.h" +#include "pysideqobject.h" +#include "pysideqslotobject_p.h" +#include "pysidesignal.h" +#include "pysideutils.h" +#include "signalmanager.h" + +#include "shiboken.h" +#include "basewrapper.h" +#include "autodecref.h" + +#include <QtCore/QDebug> +#include <QtCore/QMetaMethod> +#include <QtCore/QObject> + +#include <QtCore/private/qobject_p.h> + +#include <string_view> + +static bool isMethodDecorator(PyObject *method, bool is_pymethod, PyObject *self) +{ + Shiboken::AutoDecRef methodName(PyObject_GetAttr(method, Shiboken::PyMagicName::name())); + if (!PyObject_HasAttr(self, methodName)) + return true; + Shiboken::AutoDecRef otherMethod(PyObject_GetAttr(self, methodName)); + + // PYSIDE-1523: Each could be a compiled method or a normal method here, for the + // compiled ones we can use the attributes. + PyObject *function1; + if (PyMethod_Check(otherMethod.object())) { + function1 = PyMethod_GET_FUNCTION(otherMethod.object()); + } else { + function1 = PyObject_GetAttr(otherMethod.object(), Shiboken::PyName::im_func()); + if (function1 == nullptr) + return false; + Py_DECREF(function1); + // Not retaining a reference in line with what PyMethod_GET_FUNCTION does. + } + + PyObject *function2; + if (is_pymethod) { + function2 = PyMethod_GET_FUNCTION(method); + } else { + function2 = PyObject_GetAttr(method, Shiboken::PyName::im_func()); + Py_DECREF(function2); + // Not retaining a reference in line with what PyMethod_GET_FUNCTION does. + } + + return function1 != function2; +} + +struct GetReceiverResult +{ + QObject *receiver = nullptr; + PyObject *self = nullptr; + QByteArray callbackSig; + bool usingGlobalReceiver = false; + int slotIndex = -1; +}; + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug d, const GetReceiverResult &r) +{ + QDebugStateSaver saver(d); + d.noquote(); + d.nospace(); + d << "GetReceiverResult(receiver=" << r.receiver << ", self=" << r.self + << ", sig=\"" << r.callbackSig << "\", slotIndex=" << r.slotIndex + << ", usingGlobalReceiver=" << r.usingGlobalReceiver << ')'; + return d; +} +#endif // QT_NO_DEBUG_STREAM + +static const char *getQualifiedName(PyObject *ob) +{ + Shiboken::AutoDecRef qualNameP(PyObject_GetAttr(ob, Shiboken::PyMagicName::qualname())); + return qualNameP.isNull() + ? nullptr : Shiboken::String::toCString(qualNameP.object()); +} + +// Determine whether a method is declared in a class using qualified name lookup. +static bool isDeclaredIn(PyObject *method, const char *className) +{ + bool result = false; + if (auto *qualifiedNameC = getQualifiedName(PyMethod_Function(method))) { + std::string_view qualifiedName(qualifiedNameC); + if (const auto dot = qualifiedName.rfind('.'); dot != std::string::npos) + result = qualifiedName.substr(0, dot) == className; + } + return result; +} + +static GetReceiverResult getReceiver(QObject *source, const char *signal, + PyObject *callback) +{ + GetReceiverResult result; + + bool forceGlobalReceiver = false; + if (PyMethod_Check(callback)) { + result.self = PyMethod_GET_SELF(callback); + result.receiver = PySide::convertToQObject(result.self, false); + forceGlobalReceiver = isMethodDecorator(callback, true, result.self); +#ifdef PYPY_VERSION + } else if (Py_TYPE(callback) == PepBuiltinMethod_TypePtr) { + result.self = PyObject_GetAttrString(callback, "__self__"); + Py_DECREF(result.self); + result.receiver = PySide::convertToQObject(result.self, false); +#endif + } else if (PyCFunction_Check(callback)) { + result.self = PyCFunction_GET_SELF(callback); + result.receiver = PySide::convertToQObject(result.self, false); + } else if (PySide::isCompiledMethod(callback)) { + result.self = PyObject_GetAttr(callback, Shiboken::PyName::im_self()); + Py_DECREF(result.self); + result.receiver = PySide::convertToQObject(result.self, false); + forceGlobalReceiver = isMethodDecorator(callback, false, result.self); + } else if (PyCallable_Check(callback)) { + // Ok, just a callable object + result.receiver = nullptr; + result.self = nullptr; + } + + result.usingGlobalReceiver = !result.receiver || forceGlobalReceiver; + + // Check if this callback is a overwrite of a non-virtual Qt slot (pre-Jira bug 1019). + // Make it possible to connect to a MyWidget.show() although QWidget.show() + // is a non-virtual slot which would be found by QMetaObject search. + // FIXME PYSIDE7: This is arguably a bit of a misguided "feature", remove? + if (!result.usingGlobalReceiver && result.receiver && result.self) { + result.callbackSig = + PySide::Signal::getCallbackSignature(signal, result.receiver, callback, + result.usingGlobalReceiver); + const QMetaObject *metaObject = result.receiver->metaObject(); + result.slotIndex = metaObject->indexOfSlot(result.callbackSig.constData()); + if (PyMethod_Check(callback) != 0 && result.slotIndex != -1 + && result.slotIndex < metaObject->methodOffset()) { + // Find the class in which the slot is declared. + while (result.slotIndex < metaObject->methodOffset()) + metaObject = metaObject->superClass(); + // If the Python callback is not declared in the same class, assume it is + // a Python override. Resort to global receiver (PYSIDE-2418). + if (!isDeclaredIn(callback, metaObject->className())) + result.usingGlobalReceiver = true; + } + } + + const auto receiverThread = result.receiver ? result.receiver->thread() : nullptr; + + if (result.usingGlobalReceiver) { + PySide::SignalManager &signalManager = PySide::SignalManager::instance(); + result.receiver = signalManager.globalReceiver(source, callback, result.receiver); + // PYSIDE-1354: Move the global receiver to the original receivers's thread + // so that autoconnections work correctly. + if (receiverThread && receiverThread != result.receiver->thread()) + result.receiver->moveToThread(receiverThread); + result.callbackSig = + PySide::Signal::getCallbackSignature(signal, result.receiver, callback, + result.usingGlobalReceiver); + const QMetaObject *metaObject = result.receiver->metaObject(); + result.slotIndex = metaObject->indexOfSlot(result.callbackSig.constData()); + } + + return result; +} + +namespace PySide +{ +class FriendlyQObject : public QObject // Make protected connectNotify() accessible. +{ +public: + using QObject::connectNotify; + using QObject::disconnectNotify; +}; + +QMetaObject::Connection qobjectConnect(QObject *source, const char *signal, + QObject *receiver, const char *slot, + Qt::ConnectionType type) +{ + if (!signal || !slot || !PySide::Signal::checkQtSignal(signal)) + return {}; + + if (!PySide::SignalManager::registerMetaMethod(source, signal + 1, QMetaMethod::Signal)) + return {}; + + const auto methodType = PySide::Signal::isQtSignal(slot) + ? QMetaMethod::Signal : QMetaMethod::Slot; + PySide::SignalManager::registerMetaMethod(receiver, slot + 1, methodType); + return QObject::connect(source, signal, receiver, slot, type); +} + +QMetaObject::Connection qobjectConnect(QObject *source, QMetaMethod signal, + QObject *receiver, QMetaMethod slot, + Qt::ConnectionType type) +{ + return qobjectConnect(source, signal.methodSignature().constData(), + receiver, slot.methodSignature().constData(), type); +} + +QMetaObject::Connection qobjectConnectCallback(QObject *source, const char *signal, + PyObject *callback, Qt::ConnectionType type) +{ + if (!signal || !PySide::Signal::checkQtSignal(signal)) + return {}; + + const int signalIndex = + PySide::SignalManager::registerMetaMethodGetIndex(source, signal + 1, + QMetaMethod::Signal); + if (signalIndex == -1) + return {}; + + // Extract receiver from callback + const GetReceiverResult receiver = getReceiver(source, signal + 1, callback); + if (receiver.receiver == nullptr && receiver.self == nullptr) + return {}; + + int slotIndex = receiver.slotIndex; + + PySide::SignalManager &signalManager = PySide::SignalManager::instance(); + if (slotIndex == -1) { + if (!receiver.usingGlobalReceiver && receiver.self + && !Shiboken::Object::hasCppWrapper(reinterpret_cast<SbkObject *>(receiver.self))) { + qWarning("You can't add dynamic slots on an object originated from C++."); + if (receiver.usingGlobalReceiver) + signalManager.releaseGlobalReceiver(source, receiver.receiver); + + return {}; + } + + const char *slotSignature = receiver.callbackSig.constData(); + slotIndex = receiver.usingGlobalReceiver + ? signalManager.globalReceiverSlotIndex(receiver.receiver, slotSignature) + : PySide::SignalManager::registerMetaMethodGetIndex(receiver.receiver, slotSignature, + QMetaMethod::Slot); + + if (slotIndex == -1) { + if (receiver.usingGlobalReceiver) + signalManager.releaseGlobalReceiver(source, receiver.receiver); + + return {}; + } + } + + QMetaObject::Connection connection{}; + Py_BEGIN_ALLOW_THREADS // PYSIDE-2367, prevent threading deadlocks with connectNotify() + connection = QMetaObject::connect(source, signalIndex, receiver.receiver, slotIndex, type); + Py_END_ALLOW_THREADS + if (!connection) { + if (receiver.usingGlobalReceiver) + signalManager.releaseGlobalReceiver(source, receiver.receiver); + return {}; + } + + Q_ASSERT(receiver.receiver); + if (receiver.usingGlobalReceiver) + signalManager.notifyGlobalReceiver(receiver.receiver); + + const QMetaMethod signalMethod = receiver.receiver->metaObject()->method(signalIndex); + static_cast<FriendlyQObject *>(source)->connectNotify(signalMethod); + return connection; +} + +QMetaObject::Connection qobjectConnectCallback(QObject *source, const char *signal, QObject *context, + PyObject *callback, Qt::ConnectionType type) +{ + if (!signal || !PySide::Signal::checkQtSignal(signal)) + return {}; + + const int signalIndex = + PySide::SignalManager::registerMetaMethodGetIndex(source, signal + 1, + QMetaMethod::Signal); + if (signalIndex == -1) + return {}; + + // Extract receiver from callback + const GetReceiverResult receiver = getReceiver(source, signal + 1, callback); + if (receiver.receiver == nullptr && receiver.self == nullptr) + return {}; + + PySide::SignalManager &signalManager = PySide::SignalManager::instance(); + + PySideQSlotObject *slotObject = new PySideQSlotObject(callback); + + QMetaObject::Connection connection{}; + Py_BEGIN_ALLOW_THREADS // PYSIDE-2367, prevent threading deadlocks with connectNotify() + connection = QObjectPrivate::connect(source, signalIndex, context, slotObject, type); + Py_END_ALLOW_THREADS + if (!connection) { + if (receiver.usingGlobalReceiver) + signalManager.releaseGlobalReceiver(source, receiver.receiver); + return {}; + } + + Q_ASSERT(receiver.receiver); + if (receiver.usingGlobalReceiver) + signalManager.notifyGlobalReceiver(receiver.receiver); + + const QMetaMethod signalMethod = receiver.receiver->metaObject()->method(signalIndex); + static_cast<FriendlyQObject *>(source)->connectNotify(signalMethod); + return connection; +} + +bool qobjectDisconnectCallback(QObject *source, const char *signal, PyObject *callback) +{ + if (!PySide::Signal::checkQtSignal(signal)) + return false; + + // Extract receiver from callback + const GetReceiverResult receiver = getReceiver(nullptr, signal, callback); + if (receiver.receiver == nullptr && receiver.self == nullptr) + return false; + + const int signalIndex = source->metaObject()->indexOfSignal(signal + 1); + const int slotIndex = receiver.slotIndex; + + bool ok{}; + Py_BEGIN_ALLOW_THREADS // PYSIDE-2367, prevent threading deadlocks with disconnectNotify() + ok = QMetaObject::disconnectOne(source, signalIndex, receiver.receiver, slotIndex); + Py_END_ALLOW_THREADS + if (!ok) + return false; + + Q_ASSERT(receiver.receiver); + const QMetaMethod slotMethod = receiver.receiver->metaObject()->method(slotIndex); + static_cast<FriendlyQObject *>(source)->disconnectNotify(slotMethod); + + if (receiver.usingGlobalReceiver) { // might delete the receiver + PySide::SignalManager &signalManager = PySide::SignalManager::instance(); + signalManager.releaseGlobalReceiver(source, receiver.receiver); + } + return true; +} + +} // namespace PySide diff --git a/sources/pyside6/libpyside/qobjectconnect.h b/sources/pyside6/libpyside/qobjectconnect.h new file mode 100644 index 000000000..c99b8006e --- /dev/null +++ b/sources/pyside6/libpyside/qobjectconnect.h @@ -0,0 +1,47 @@ +// 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 QOBJECTCONNECT_H +#define QOBJECTCONNECT_H + +#include "pysidemacros.h" + +#include <sbkpython.h> + +#include <QtCore/QMetaObject> + +QT_FORWARD_DECLARE_CLASS(QObject) +QT_FORWARD_DECLARE_CLASS(QMetaMethod) + +namespace PySide +{ + +/// Helpers for QObject::connect(): Make a string-based connection +PYSIDE_API QMetaObject::Connection + qobjectConnect(QObject *source, const char *signal, + QObject *receiver, const char *slot, + Qt::ConnectionType type); + +/// Helpers for QObject::connect(): Make a connection based on QMetaMethod +PYSIDE_API QMetaObject::Connection + qobjectConnect(QObject *source, QMetaMethod signal, + QObject *receiver, QMetaMethod slot, + Qt::ConnectionType type); + +/// Helpers for QObject::connect(): Make a connection to a Python callback +PYSIDE_API QMetaObject::Connection + qobjectConnectCallback(QObject *source, const char *signal, + PyObject *callback, Qt::ConnectionType type); + +/// Helpers for QObject::connect(): Make a connection to a Python callback and a context object +PYSIDE_API QMetaObject::Connection + qobjectConnectCallback(QObject *source, const char *signal, QObject *context, + PyObject *callback, Qt::ConnectionType type); + +/// Helpers for QObject::disconnect(): Disconnect a Python callback +PYSIDE_API bool qobjectDisconnectCallback(QObject *source, const char *signal, + PyObject *callback); + +} // namespace PySide + +#endif // QOBJECTCONNECT_H diff --git a/sources/pyside6/libpyside/signalmanager.cpp b/sources/pyside6/libpyside/signalmanager.cpp index 2c08f4328..f4c2bbf43 100644 --- a/sources/pyside6/libpyside/signalmanager.cpp +++ b/sources/pyside6/libpyside/signalmanager.cpp @@ -1,51 +1,16 @@ -// -*- mode: cpp;-*- -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of Qt for Python. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// 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 "signalmanager.h" #include "pysidesignal.h" +#include "pysidelogging_p.h" #include "pysideproperty.h" #include "pysideproperty_p.h" -#include "pyside.h" +#include "pysidecleanup.h" #include "pyside_p.h" #include "dynamicqmetaobject.h" #include "pysidemetafunction_p.h" +#include "pysidestaticstrings.h" #include <autodecref.h> #include <basewrapper.h> @@ -54,19 +19,20 @@ #include <sbkconverter.h> #include <sbkstring.h> #include <sbkstaticstrings.h> +#include <sbkerrors.h> +#include <QtCore/QCoreApplication> +#include <QtCore/QByteArrayView> #include <QtCore/QDebug> #include <QtCore/QHash> +#include <QtCore/QScopedPointer> +#include <QtCore/QTimerEvent> #include <algorithm> #include <limits> +#include <memory> -// These private headers are needed to throw JavaScript exceptions -#if PYSIDE_QML_PRIVATE_API_SUPPORT - #include <private/qv4engine_p.h> - #include <private/qv4context_p.h> - #include <private/qqmldata_p.h> -#endif +using namespace Qt::StringLiterals; #if QSLOT_CODE != 1 || QSIGNAL_CODE != 2 #error QSLOT_CODE and/or QSIGNAL_CODE changed! change the hardcoded stuff to the correct value! @@ -75,26 +41,63 @@ #define PYSIDE_SIGNAL '2' #include "globalreceiverv2.h" -namespace { - static PyObject *metaObjectAttr = nullptr; - - static int callMethod(QObject *object, int id, void **args); - static PyObject *parseArguments(const QList< QByteArray >& paramTypes, void **args); - static bool emitShortCircuitSignal(QObject *source, int signalIndex, PyObject *args); - - static void destroyMetaObject(PyObject *obj) - { - void *ptr = PyCapsule_GetPointer(obj, nullptr); - auto meta = reinterpret_cast<PySide::MetaObjectBuilder *>(ptr); - SbkObject *wrapper = Shiboken::BindingManager::instance().retrieveWrapper(meta); - if (wrapper) - Shiboken::BindingManager::instance().releaseWrapper(wrapper); - delete meta; +static PyObject *metaObjectAttr = nullptr; +static PyObject *parseArguments(const QMetaMethod &method, void **args); + +static bool qAppRunning = false; + +static void destroyMetaObject(PyObject *obj) +{ + void *ptr = PyCapsule_GetPointer(obj, nullptr); + auto meta = reinterpret_cast<PySide::MetaObjectBuilder *>(ptr); + SbkObject *wrapper = Shiboken::BindingManager::instance().retrieveWrapper(meta); + if (wrapper) + Shiboken::BindingManager::instance().releaseWrapper(wrapper); + delete meta; +} + +static const char *metaCallName(QMetaObject::Call call) +{ + static const QHash<QMetaObject::Call, const char *> mapping = { + {QMetaObject::InvokeMetaMethod, "InvokeMetaMethod"}, + {QMetaObject::ReadProperty, "ReadProperty"}, + {QMetaObject::WriteProperty, "WriteProperty"}, + {QMetaObject::ResetProperty, "ResetProperty"}, + {QMetaObject::CreateInstance, "CreateInstance"}, + {QMetaObject::IndexOfMethod, "IndexOfMethod"}, + {QMetaObject::RegisterPropertyMetaType, "RegisterPropertyMetaType"}, + {QMetaObject::RegisterMethodArgumentMetaType, "RegisterMethodArgumentMetaType"}, + {QMetaObject::BindableProperty, "BindableProperty"}, + {QMetaObject::CustomCall, "CustomCall"} + }; + auto it = mapping.constFind(call); + return it != mapping.constEnd() ? it.value() : "<Unknown>"; +} + +static QByteArray methodSignature(const QMetaMethod &method) +{ + QByteArray result; + if (auto *t = method.typeName()) { + result += t; + result += ' '; } + result += method.methodSignature(); + return result; } -namespace PySide { +static QByteArray msgCannotConvertParameter(const QMetaMethod &method, qsizetype p) +{ + return "Cannot call meta function \""_ba + methodSignature(method) + + "\" because parameter " + QByteArray::number(p) + " of type \""_ba + + method.parameterTypeName(p) + "\" cannot be converted."_ba; +} + +static QByteArray msgCannotConvertReturn(const QMetaMethod &method) +{ + return "The return value of \""_ba + methodSignature(method) + "\" cannot be converted."_ba; +} +namespace PySide { PyObjectWrapper::PyObjectWrapper() :m_me(Py_None) @@ -141,7 +144,8 @@ void PyObjectWrapper::reset(PyObject *o) PyObjectWrapper &PyObjectWrapper::operator=(const PySide::PyObjectWrapper &other) { - reset(other.m_me); + if (this != &other) + reset(other.m_me); return *this; } @@ -150,6 +154,14 @@ PyObjectWrapper::operator PyObject *() const return m_me; } + +int PyObjectWrapper::toInt() const +{ + // hold the GIL + Shiboken::GilState state; + return Shiboken::Enum::check(m_me) ? Shiboken::Enum::getValue(m_me) : -1; +} + QDataStream &operator<<(QDataStream &out, const PyObjectWrapper &myObj) { if (Py_IsInitialized() == 0) { @@ -164,7 +176,8 @@ QDataStream &operator<<(QDataStream &out, const PyObjectWrapper &myObj) Shiboken::AutoDecRef pickleModule(PyImport_ImportModule("pickle")); reduce_func = PyObject_GetAttr(pickleModule, Shiboken::PyName::dumps()); } - Shiboken::AutoDecRef repr(PyObject_CallFunctionObjArgs(reduce_func, (PyObject *)myObj, NULL)); + PyObject *pyObj = myObj; + Shiboken::AutoDecRef repr(PyObject_CallFunctionObjArgs(reduce_func, pyObj, nullptr)); if (repr.object()) { const char *buff = nullptr; Py_ssize_t size = 0; @@ -208,29 +221,71 @@ QDataStream &operator>>(QDataStream &in, PyObjectWrapper &myObj) }; +namespace PySide { +using GlobalReceiverV2Ptr = std::shared_ptr<GlobalReceiverV2>; +using GlobalReceiverV2Map = QHash<PySide::GlobalReceiverKey, GlobalReceiverV2Ptr>; +} + using namespace PySide; -struct SignalManager::SignalManagerPrivate +// Listen for destroy() of main thread objects and ensure cleanup +class SignalManagerDestroyListener : public QObject { - GlobalReceiverV2MapPtr m_globalReceivers; + Q_OBJECT +public: + Q_DISABLE_COPY_MOVE(SignalManagerDestroyListener) - SignalManagerPrivate() : m_globalReceivers(new GlobalReceiverV2Map{}) - { - } + using QObject::QObject; - ~SignalManagerPrivate() - { - if (!m_globalReceivers.isNull()) { - // Delete receivers by always retrieving the current first element, because deleting a - // receiver can indirectly delete another one, and if we use qDeleteAll, that could - // cause either a double delete, or iterator invalidation, and thus undefined behavior. - while (!m_globalReceivers->isEmpty()) - delete *m_globalReceivers->cbegin(); - Q_ASSERT(m_globalReceivers->isEmpty()); - } +public Q_SLOTS: + void destroyNotify(const QObject *); + +protected: + void timerEvent(QTimerEvent *event) override; + +private: + int m_timerId = -1; +}; + +void SignalManagerDestroyListener::destroyNotify(const QObject *) +{ + if (qAppRunning && m_timerId == -1) + m_timerId = startTimer(0); +} + +void SignalManagerDestroyListener::timerEvent(QTimerEvent *event) +{ + if (event->timerId() == m_timerId) { + killTimer(std::exchange(m_timerId, -1)); + SignalManager::instance().purgeEmptyGlobalReceivers(); } +} + +struct SignalManager::SignalManagerPrivate +{ + Q_DISABLE_COPY_MOVE(SignalManagerPrivate) + + SignalManagerPrivate() noexcept = default; + ~SignalManagerPrivate() { clear(); } + + void deleteGlobalReceiver(const QObject *gr); + void clear(); + void purgeEmptyGlobalReceivers(); + + GlobalReceiverV2Map m_globalReceivers; + static SignalManager::QmlMetaCallErrorHandler m_qmlMetaCallErrorHandler; + + static void handleMetaCallError(QObject *object, int *result); + static int qtPropertyMetacall(QObject *object, QMetaObject::Call call, + int id, void **args); + static int qtMethodMetacall(QObject *object, int id, void **args); + + QPointer<SignalManagerDestroyListener> m_listener; }; +SignalManager::QmlMetaCallErrorHandler + SignalManager::SignalManagerPrivate::m_qmlMetaCallErrorHandler = nullptr; + static void clearSignalManager() { PySide::SignalManager::instance().clear(); @@ -240,7 +295,7 @@ static void PyObject_PythonToCpp_PyObject_PTR(PyObject *pyIn, void *cppOut) { *reinterpret_cast<PyObject **>(cppOut) = pyIn; } -static PythonToCppFunc is_PyObject_PythonToCpp_PyObject_PTR_Convertible(PyObject *pyIn) +static PythonToCppFunc is_PyObject_PythonToCpp_PyObject_PTR_Convertible(PyObject * /* pyIn */) { return PyObject_PythonToCpp_PyObject_PTR; } @@ -259,6 +314,8 @@ SignalManager::SignalManager() : m_d(new SignalManagerPrivate) // Register PyObject type to use in queued signal and slot connections qRegisterMetaType<PyObjectWrapper>("PyObject"); + // Register QVariant(enum) conversion to QVariant(int) + QMetaType::registerConverter<PyObjectWrapper, int>(&PyObjectWrapper::toInt); SbkConverter *converter = Shiboken::Conversions::createConverter(&PyBaseObject_Type, nullptr); Shiboken::Conversions::setCppPointerToPythonFunction(converter, PyObject_PTR_CppToPython_PyObject); @@ -276,8 +333,7 @@ SignalManager::SignalManager() : m_d(new SignalManagerPrivate) void SignalManager::clear() { - delete m_d; - m_d = new SignalManagerPrivate(); + m_d->clear(); } SignalManager::~SignalManager() @@ -291,52 +347,123 @@ SignalManager &SignalManager::instance() return me; } -QObject *SignalManager::globalReceiver(QObject *sender, PyObject *callback) +void SignalManager::setQmlMetaCallErrorHandler(QmlMetaCallErrorHandler handler) +{ + SignalManagerPrivate::m_qmlMetaCallErrorHandler = handler; +} + +static void qAppAboutToQuit() { - GlobalReceiverV2MapPtr globalReceivers = m_d->m_globalReceivers; - GlobalReceiverKey key = GlobalReceiverV2::key(callback); - GlobalReceiverV2 *gr = nullptr; - auto it = globalReceivers->find(key); - if (it == globalReceivers->end()) { - gr = new GlobalReceiverV2(callback, globalReceivers); - globalReceivers->insert(key, gr); - if (sender) { - gr->incRef(sender); // create a link reference - gr->decRef(); // remove extra reference + qAppRunning = false; + SignalManager::instance().purgeEmptyGlobalReceivers(); +} + +static bool isInMainThread(const QObject *o) +{ + if (o->isWidgetType() || o->isWindowType() || o->isQuickItemType()) + return true; + auto *app = QCoreApplication::instance(); + return app != nullptr && app->thread() == o->thread(); +} + +QObject *SignalManager::globalReceiver(QObject *sender, PyObject *callback, QObject *receiver) +{ + if (m_d->m_listener.isNull() && !QCoreApplication::closingDown()) { + if (auto *app = QCoreApplication::instance()) { + // The signal manager potentially outlives QCoreApplication, ensure deletion + m_d->m_listener = new SignalManagerDestroyListener(app); + m_d->m_listener->setObjectName("qt_pyside_signalmanagerdestroylistener"); + QObject::connect(app, &QCoreApplication::aboutToQuit, qAppAboutToQuit); + qAppRunning = true; } - } else { - gr = it.value(); - if (sender) - gr->incRef(sender); } - return reinterpret_cast<QObject *>(gr); + auto &globalReceivers = m_d->m_globalReceivers; + const GlobalReceiverKey key = GlobalReceiverV2::key(callback); + auto it = globalReceivers.find(key); + if (it == globalReceivers.end()) { + auto gr = std::make_shared<GlobalReceiverV2>(callback, receiver); + it = globalReceivers.insert(key, gr); + } + + if (sender != nullptr) { + it.value()->incRef(sender); // create a link reference + + // For main thread-objects, add a notification for destroy (PYSIDE-2646, 2141) + if (qAppRunning && !m_d->m_listener.isNull() && isInMainThread(sender)) { + QObject::connect(sender, &QObject::destroyed, + m_d->m_listener, &SignalManagerDestroyListener::destroyNotify, + Qt::UniqueConnection); + } + } + + return it.value().get(); } -int SignalManager::countConnectionsWith(const QObject *object) +void SignalManager::purgeEmptyGlobalReceivers() { - int count = 0; - for (GlobalReceiverV2Map::const_iterator it = m_d->m_globalReceivers->cbegin(), end = m_d->m_globalReceivers->cend(); it != end; ++it) { - if (it.value()->refCount(object)) - count++; - } - return count; + m_d->purgeEmptyGlobalReceivers(); } void SignalManager::notifyGlobalReceiver(QObject *receiver) { reinterpret_cast<GlobalReceiverV2 *>(receiver)->notify(); + purgeEmptyGlobalReceivers(); } void SignalManager::releaseGlobalReceiver(const QObject *source, QObject *receiver) { - auto gr = reinterpret_cast<GlobalReceiverV2 *>(receiver); + auto gr = static_cast<GlobalReceiverV2 *>(receiver); gr->decRef(source); + if (gr->isEmpty()) + m_d->deleteGlobalReceiver(gr); +} + +void SignalManager::deleteGlobalReceiver(const QObject *gr) +{ + SignalManager::instance().m_d->deleteGlobalReceiver(gr); +} + +void SignalManager::SignalManagerPrivate::deleteGlobalReceiver(const QObject *gr) +{ + for (auto it = m_globalReceivers.begin(), end = m_globalReceivers.end(); it != end; ++it) { + if (it.value().get() == gr) { + m_globalReceivers.erase(it); + break; + } + } +} + +void SignalManager::SignalManagerPrivate::clear() +{ + // Delete receivers by always retrieving the current first element, + // because deleting a receiver can indirectly delete another one + // via ~DynamicSlotDataV2(). Using ~QHash/clear() could cause an + // iterator invalidation, and thus undefined behavior. + while (!m_globalReceivers.isEmpty()) + m_globalReceivers.erase(m_globalReceivers.cbegin()); +} + +static bool isEmptyGlobalReceiver(const GlobalReceiverV2Ptr &g) +{ + return g->isEmpty(); +} + +void SignalManager::SignalManagerPrivate::purgeEmptyGlobalReceivers() +{ + // Delete repetitively (see comment in clear()). + while (true) { + auto it = std::find_if(m_globalReceivers.cbegin(), m_globalReceivers.cend(), + isEmptyGlobalReceiver); + if (it == m_globalReceivers.cend()) + break; + m_globalReceivers.erase(it); + } } int SignalManager::globalReceiverSlotIndex(QObject *receiver, const char *signature) const { - return reinterpret_cast<GlobalReceiverV2 *>(receiver)->addSlot(signature); + return static_cast<GlobalReceiverV2 *>(receiver)->addSlot(signature); } bool SignalManager::emitSignal(QObject *source, const char *signal, PyObject *args) @@ -346,162 +473,182 @@ bool SignalManager::emitSignal(QObject *source, const char *signal, PyObject *ar signal++; int signalIndex = source->metaObject()->indexOfSignal(signal); - if (signalIndex != -1) { - // cryptic but works! - // if the signature doesn't have a '(' it's a shor circuited signal, i.e. std::find - // returned the string null terminator. - bool isShortCircuit = !*std::find(signal, signal + std::strlen(signal), '('); - if (isShortCircuit) - return emitShortCircuitSignal(source, signalIndex, args); - else - return MetaFunction::call(source, signalIndex, args); + return signalIndex != -1 && MetaFunction::call(source, signalIndex, args); +} + +// Handle errors from meta calls. Requires GIL and PyErr_Occurred() +void SignalManager::SignalManagerPrivate::handleMetaCallError(QObject *object, int *result) +{ + // Bubbles Python exceptions up to the Javascript engine, if called from one + if (m_qmlMetaCallErrorHandler) { + auto idOpt = m_qmlMetaCallErrorHandler(object); + if (idOpt.has_value()) + *result = idOpt.value(); } - return false; + + const int reclimit = Py_GetRecursionLimit(); + // Inspired by Python's errors.c: PyErr_GivenExceptionMatches() function. + // Temporarily bump the recursion limit, so that PyErr_Print will not raise a recursion + // error again. Don't do it when the limit is already insanely high, to avoid overflow. + if (reclimit < (1 << 30)) + Py_SetRecursionLimit(reclimit + 5); + PyErr_Print(); + Py_SetRecursionLimit(reclimit); } -int SignalManager::qt_metacall(QObject *object, QMetaObject::Call call, int id, void **args) +// Handler for QMetaObject::ReadProperty/WriteProperty/ResetProperty: +int SignalManager::SignalManagerPrivate::qtPropertyMetacall(QObject *object, + QMetaObject::Call call, + int id, void **args) { const QMetaObject *metaObject = object->metaObject(); - PySideProperty *pp = nullptr; - PyObject *pp_name = nullptr; - QMetaProperty mp; - PyObject *pySelf = nullptr; - int methodCount = metaObject->methodCount(); - int propertyCount = metaObject->propertyCount(); - - if (call != QMetaObject::InvokeMetaMethod) { - mp = metaObject->property(id); - if (!mp.isValid()) { - return id - methodCount; - } + int result = id - metaObject->propertyCount(); + + const QMetaProperty mp = metaObject->property(id); + + qCDebug(lcPySide).noquote().nospace() << __FUNCTION__ + << ' ' << metaCallName(call) << " #" << id << ' ' << mp.typeName() + << "/\"" << mp.name() << "\" " << object; - Shiboken::GilState gil; - pySelf = reinterpret_cast<PyObject *>(Shiboken::BindingManager::instance().retrieveWrapper(object)); - Q_ASSERT(pySelf); - pp_name = Shiboken::String::fromCString(mp.name()); - pp = Property::getObject(pySelf, pp_name); - if (!pp) { - qWarning("Invalid property: %s.", mp.name()); - Py_XDECREF(pp_name); - return id - methodCount; + if (!mp.isValid()) + return result; + + Shiboken::GilState gil; + auto *pySbkSelf = Shiboken::BindingManager::instance().retrieveWrapper(object); + Q_ASSERT(pySbkSelf); + auto *pySelf = reinterpret_cast<PyObject *>(pySbkSelf); + Shiboken::AutoDecRef pp_name(Shiboken::String::fromCString(mp.name())); + PySideProperty *pp = Property::getObject(pySelf, pp_name); + if (!pp) { + qWarning("Invalid property: %s.", mp.name()); + return false; + } + pp->d->metaCall(pySelf, call, args); + Py_DECREF(pp); + if (PyErr_Occurred()) { + // PYSIDE-2160: An unknown type was reported. Indicated by StopIteration. + if (PyErr_ExceptionMatches(PyExc_StopIteration)) { + PyObject *excType, *excValue, *excTraceback; + PyErr_Fetch(&excType, &excValue, &excTraceback); + bool ign = call == QMetaObject::WriteProperty; + PyErr_WarnFormat(PyExc_RuntimeWarning, 0, + ign ? "Unknown property type '%s' of QObject '%s' used in fset" + : "Unknown property type '%s' of QObject '%s' used in fget with %R", + pp->d->typeName.constData(), metaObject->className(), excValue); + if (PyErr_Occurred()) + Shiboken::Errors::storeErrorOrPrint(); + Py_DECREF(excType); + Py_DECREF(excValue); + Py_XDECREF(excTraceback); + return result; } + + qWarning().noquote().nospace() + << "An error occurred executing the property metacall " << call + << " on property \"" << mp.name() << "\" of " << object; + handleMetaCallError(object, &result); } + return result; +} - switch(call) { -#ifndef QT_NO_PROPERTIES - case QMetaObject::ReadProperty: - case QMetaObject::WriteProperty: - case QMetaObject::ResetProperty: - pp->d->metaCallHandler(pp, pySelf, call, args); - break; -#endif - case QMetaObject::InvokeMetaMethod: - id = callMethod(object, id, args); - break; +// Handler for QMetaObject::InvokeMetaMethod +int SignalManager::SignalManagerPrivate::qtMethodMetacall(QObject *object, + int id, void **args) +{ + const QMetaObject *metaObject = object->metaObject(); + const QMetaMethod method = metaObject->method(id); + int result = id - metaObject->methodCount(); - default: - qWarning("Unsupported meta invocation type."); - } + std::unique_ptr<Shiboken::GilState> gil; - // WARNING Isn't safe to call any metaObject and/or object methods beyond this point - // because the object can be deleted inside the called slot. + qCDebug(lcPySide).noquote().nospace() << __FUNCTION__ << " #" << id + << " \"" << method.methodSignature() << '"'; - if (call == QMetaObject::InvokeMetaMethod) { - id = id - methodCount; + if (method.methodType() == QMetaMethod::Signal) { + // emit python signal + QMetaObject::activate(object, id, args); } else { - id = id - propertyCount; + gil.reset(new Shiboken::GilState); + auto *pySbkSelf = Shiboken::BindingManager::instance().retrieveWrapper(object); + Q_ASSERT(pySbkSelf); + auto *pySelf = reinterpret_cast<PyObject *>(pySbkSelf); + QByteArray methodName = method.methodSignature(); + methodName.truncate(methodName.indexOf('(')); + Shiboken::AutoDecRef pyMethod(PyObject_GetAttrString(pySelf, methodName)); + if (pyMethod.isNull()) { + PyErr_Format(PyExc_AttributeError, "Slot '%s::%s' not found.", + metaObject->className(), method.methodSignature().constData()); + } else { + SignalManager::callPythonMetaMethod(method, args, pyMethod); + } } + // WARNING Isn't safe to call any metaObject and/or object methods beyond this point + // because the object can be deleted inside the called slot. - if (pp || pp_name) { - Shiboken::GilState gil; - Py_XDECREF(pp); - Py_XDECREF(pp_name); - } + if (gil.get() == nullptr) + gil.reset(new Shiboken::GilState); - // Bubbles Python exceptions up to the Javascript engine, if called from one - { - Shiboken::GilState gil; - - if (PyErr_Occurred()) { - -#if PYSIDE_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()) { - QV4::ExecutionEngine *engine = data->jsWrapper.engine(); - if (engine->currentStackFrame != nullptr) { - 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 = QLatin1String(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); - } else if (isTypeError) { - return engine->throwTypeError(errString); - } else { - return engine->throwError(errString); - } - } - } -#endif // PYSIDE_QML_PRIVATE_API_SUPPORT - - int reclimit = Py_GetRecursionLimit(); - // Inspired by Python's errors.c: PyErr_GivenExceptionMatches() function. - // Temporarily bump the recursion limit, so that PyErr_Print will not raise a recursion - // error again. Don't do it when the limit is already insanely high, to avoid overflow. - if (reclimit < (1 << 30)) - Py_SetRecursionLimit(reclimit + 5); - PyErr_Print(); - Py_SetRecursionLimit(reclimit); - } - } + if (PyErr_Occurred()) + handleMetaCallError(object, &result); + + return result; +} +int SignalManager::qt_metacall(QObject *object, QMetaObject::Call call, int id, void **args) +{ + switch (call) { + case QMetaObject::ReadProperty: + case QMetaObject::WriteProperty: + case QMetaObject::ResetProperty: + id = SignalManagerPrivate::qtPropertyMetacall(object, call, id, args); + break; + case QMetaObject::RegisterPropertyMetaType: + case QMetaObject::BindableProperty: + id -= object->metaObject()->propertyCount(); + break; + case QMetaObject::InvokeMetaMethod: + id = SignalManagerPrivate::qtMethodMetacall(object, id, args); + break; + case QMetaObject::CreateInstance: + case QMetaObject::IndexOfMethod: + case QMetaObject::RegisterMethodArgumentMetaType: + case QMetaObject::CustomCall: + qCDebug(lcPySide).noquote().nospace() << __FUNCTION__ << ' ' + << metaCallName(call) << " #" << id << ' ' << object; + id -= object->metaObject()->methodCount(); + break; +#if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0) + case QMetaObject::ConstructInPlace: + break; +#endif + } return id; } -int SignalManager::callPythonMetaMethod(const QMetaMethod &method, void **args, PyObject *pyMethod, bool isShortCuit) +int SignalManager::callPythonMetaMethod(const QMetaMethod &method, void **args, PyObject *pyMethod) { Q_ASSERT(pyMethod); Shiboken::GilState gil; - PyObject *pyArguments = nullptr; - - if (isShortCuit){ - pyArguments = reinterpret_cast<PyObject *>(args[1]); - } else { - pyArguments = parseArguments(method.parameterTypes(), args); - } + PyObject *pyArguments = parseArguments(method, args); if (pyArguments) { - Shiboken::Conversions::SpecificConverter *retConverter = nullptr; + QScopedPointer<Shiboken::Conversions::SpecificConverter> retConverter; const char *returnType = method.typeName(); - if (returnType && std::strcmp("", returnType) && std::strcmp("void", returnType)) { - retConverter = new Shiboken::Conversions::SpecificConverter(returnType); - if (!retConverter || !*retConverter) { - PyErr_Format(PyExc_RuntimeError, "Can't find converter for '%s' to call Python meta method.", returnType); + if (returnType != nullptr && returnType[0] != 0 && std::strcmp("void", returnType) != 0) { + retConverter.reset(new Shiboken::Conversions::SpecificConverter(returnType)); + if (!retConverter->isValid()) { + PyErr_SetString(PyExc_RuntimeError, msgCannotConvertReturn(method).constData()); return -1; } } Shiboken::AutoDecRef retval(PyObject_CallObject(pyMethod, pyArguments)); - if (!isShortCuit && pyArguments){ - Py_DECREF(pyArguments); - } + Py_DECREF(pyArguments); - if (!retval.isNull() && retval != Py_None && !PyErr_Occurred() && retConverter) { + if (!retval.isNull() && retval != Py_None && !PyErr_Occurred() && retConverter) retConverter->toCpp(retval, args[0]); - } - delete retConverter; } return -1; @@ -531,6 +678,57 @@ static MetaObjectBuilder *metaBuilderFromDict(PyObject *dict) return reinterpret_cast<MetaObjectBuilder *>(PyCapsule_GetPointer(pyBuilder, nullptr)); } +// Helper to format a method signature "foo(QString)" into +// Slot decorator "@Slot(str)" + +struct slotSignature +{ + explicit slotSignature(const char *signature) : m_signature(signature) {} + + const char *m_signature; +}; + +QDebug operator<<(QDebug debug, const slotSignature &sig) +{ + QDebugStateSaver saver(debug); + debug.noquote(); + debug.nospace(); + debug << "@Slot("; + QByteArrayView signature(sig.m_signature); + const auto len = signature.size(); + auto pos = signature.indexOf('('); + if (pos != -1 && pos < len - 2) { + ++pos; + while (true) { + auto nextPos = signature.indexOf(',', pos); + if (nextPos == -1) + nextPos = len - 1; + const QByteArrayView parameter = signature.sliced(pos, nextPos - pos); + if (parameter == "QString") { + debug << "str"; + } else if (parameter == "double") { + debug << "float"; + } else { + const bool hasDelimiter = parameter.contains("::"); + if (hasDelimiter) + debug << '"'; + if (!hasDelimiter && parameter.endsWith('*')) + debug << parameter.first(parameter.size() - 1); + else + debug << parameter; + if (hasDelimiter) + debug << '"'; + } + pos = nextPos + 1; + if (pos >= len) + break; + debug << ','; + } + } + debug << ')'; + return debug; +} + int SignalManager::registerMetaMethodGetIndex(QObject *source, const char *signature, QMetaMethod::MethodType type) { if (!source) { @@ -544,26 +742,32 @@ int SignalManager::registerMetaMethodGetIndex(QObject *source, const char *signa if (methodIndex == -1) { SbkObject *self = Shiboken::BindingManager::instance().retrieveWrapper(source); if (!Shiboken::Object::hasCppWrapper(self)) { - qWarning() << "Invalid Signal signature:" << signature; + qWarning().noquote().nospace() << __FUNCTION__ + << ": Cannot add dynamic method \"" << signature << "\" (" << type + << ") to " << source << ": No Wrapper found."; return -1; - } else { - auto pySelf = reinterpret_cast<PyObject *>(self); - PyObject *dict = self->ob_dict; - MetaObjectBuilder *dmo = metaBuilderFromDict(dict); - - // Create a instance meta object - if (!dmo) { - dmo = new MetaObjectBuilder(Py_TYPE(pySelf), metaObject); - PyObject *pyDmo = PyCapsule_New(dmo, nullptr, destroyMetaObject); - PyObject_SetAttr(pySelf, metaObjectAttr, pyDmo); - Py_DECREF(pyDmo); - } + } + auto *pySelf = reinterpret_cast<PyObject *>(self); + auto *dict = SbkObject_GetDict_NoRef(pySelf); + MetaObjectBuilder *dmo = metaBuilderFromDict(dict); + + // Create a instance meta object + if (!dmo) { + dmo = new MetaObjectBuilder(Py_TYPE(pySelf), metaObject); + PyObject *pyDmo = PyCapsule_New(dmo, nullptr, destroyMetaObject); + PyObject_SetAttr(pySelf, metaObjectAttr, pyDmo); + Py_DECREF(pyDmo); + } - if (type == QMetaMethod::Signal) - return dmo->addSignal(signature); - else - return dmo->addSlot(signature); + if (type == QMetaMethod::Slot) { + qCWarning(lcPySide).noquote().nospace() + << "Warning: Registering dynamic slot \"" + << signature << "\" on \"" << source->metaObject()->className() + << "\". Consider annotating with " << slotSignature(signature); } + + return type == QMetaMethod::Signal + ? dmo->addSignal(signature) : dmo->addSlot(signature); } return methodIndex; } @@ -580,60 +784,32 @@ const QMetaObject *SignalManager::retrieveMetaObject(PyObject *self) // m_dirty flag is set. Q_ASSERT(self); - MetaObjectBuilder *builder = metaBuilderFromDict(reinterpret_cast<SbkObject *>(self)->ob_dict); + auto *ob_dict = SbkObject_GetDict_NoRef(self); + MetaObjectBuilder *builder = metaBuilderFromDict(ob_dict); if (!builder) builder = &(retrieveTypeUserData(self)->mo); return builder->update(); } -namespace { - -static int callMethod(QObject *object, int id, void **args) -{ - const QMetaObject *metaObject = object->metaObject(); - QMetaMethod method = metaObject->method(id); - - if (method.methodType() == QMetaMethod::Signal) { - // emit python signal - QMetaObject::activate(object, id, args); - } else { - Shiboken::GilState gil; - auto self = reinterpret_cast<PyObject *>(Shiboken::BindingManager::instance().retrieveWrapper(object)); - QByteArray methodName = method.methodSignature(); - methodName.truncate(methodName.indexOf('(')); - Shiboken::AutoDecRef pyMethod(PyObject_GetAttrString(self, methodName)); - return SignalManager::callPythonMetaMethod(method, args, pyMethod, false); - } - return -1; -} - - -static PyObject *parseArguments(const QList<QByteArray>& paramTypes, void **args) +static PyObject *parseArguments(const QMetaMethod &method, void **args) { - int argsSize = paramTypes.count(); + const auto ¶mTypes = method.parameterTypes(); + const qsizetype argsSize = paramTypes.size(); PyObject *preparedArgs = PyTuple_New(argsSize); - for (int i = 0, max = argsSize; i < max; ++i) { + for (qsizetype i = 0; i < argsSize; ++i) { void *data = args[i+1]; - const char *dataType = paramTypes[i].constData(); - Shiboken::Conversions::SpecificConverter converter(dataType); - if (converter) { - PyTuple_SET_ITEM(preparedArgs, i, converter.toPython(data)); - } else { - PyErr_Format(PyExc_TypeError, "Can't call meta function because I have no idea how to handle %s", dataType); + auto param = paramTypes.at(i); + Shiboken::Conversions::SpecificConverter converter(param.constData()); + if (!converter) { + PyErr_SetString(PyExc_TypeError, msgCannotConvertParameter(method, i).constData()); Py_DECREF(preparedArgs); - return 0; + return nullptr; } + PyTuple_SET_ITEM(preparedArgs, i, converter.toPython(data)); } return preparedArgs; } -static bool emitShortCircuitSignal(QObject *source, int signalIndex, PyObject *args) -{ - void *signalArgs[2] = {nullptr, args}; - source->qt_metacall(QMetaObject::InvokeMetaMethod, signalIndex, signalArgs); - return true; -} - -} //namespace +#include "signalmanager.moc" diff --git a/sources/pyside6/libpyside/signalmanager.h b/sources/pyside6/libpyside/signalmanager.h index fe077bd1a..397700df1 100644 --- a/sources/pyside6/libpyside/signalmanager.h +++ b/sources/pyside6/libpyside/signalmanager.h @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of Qt for Python. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// 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 SIGNALMANAGER_H #define SIGNALMANAGER_H @@ -47,6 +11,8 @@ #include <QtCore/QMetaMethod> +#include <optional> + QT_FORWARD_DECLARE_CLASS(QDataStream) namespace PySide @@ -69,6 +35,14 @@ public: ~PyObjectWrapper(); operator PyObject*() const; + // FIXME: To be removed in Qt7 + // This was done to make QAbstractItemModel::data() work without explicit conversion of + // QVariant(PyObjectWrapper) to QVariant(int). This works because QAbstractItemModel::data() + // inturn calls legacyEnumValueFromModelData(const QVariant &data). But this function will + // be removed in Qt7. + // The proper fix would be to associate PyObjectWrapper to the corresponding C++ Enum. + int toInt() const; + private: PyObject* m_me; }; @@ -78,11 +52,16 @@ PYSIDE_API QDataStream &operator>>(QDataStream& in, PyObjectWrapper& myObj); class PYSIDE_API SignalManager { - Q_DISABLE_COPY(SignalManager) public: + Q_DISABLE_COPY_MOVE(SignalManager) + + using QmlMetaCallErrorHandler = std::optional<int>(*)(QObject *object); + static SignalManager& instance(); - QObject* globalReceiver(QObject* sender, PyObject* callback); + static void setQmlMetaCallErrorHandler(QmlMetaCallErrorHandler handler); + + QObject* globalReceiver(QObject *sender, PyObject *callback, QObject *receiver = nullptr); void releaseGlobalReceiver(const QObject* sender, QObject* receiver); int globalReceiverSlotIndex(QObject* sender, const char* slotSignature) const; void notifyGlobalReceiver(QObject* receiver); @@ -97,14 +76,14 @@ public: // used to discovery metaobject static const QMetaObject* retrieveMetaObject(PyObject* self); - // Used to discovery if SignalManager was connected with object "destroyed()" signal. - int countConnectionsWith(const QObject *object); - // Disconnect all signals managed by Globalreceiver void clear(); + void purgeEmptyGlobalReceivers(); // Utility function to call a python method usign args received in qt_metacall - static int callPythonMetaMethod(const QMetaMethod& method, void** args, PyObject* obj, bool isShortCuit); + static int callPythonMetaMethod(const QMetaMethod& method, void** args, PyObject* obj); + + static void deleteGlobalReceiver(const QObject *globalReceiver); private: struct SignalManagerPrivate; |