diff options
| author | Friedemann Kleint <Friedemann.Kleint@qt.io> | 2020-11-02 16:11:52 +0100 |
|---|---|---|
| committer | Friedemann Kleint <Friedemann.Kleint@qt.io> | 2020-11-02 16:12:04 +0000 |
| commit | 25180730194bec25f915f32ab846ea583fb1493f (patch) | |
| tree | 9a73e0336ecf21e085d99d6a651c5547b9eb99f8 /sources/pyside6/libpyside | |
| parent | 6e3e7b9ca0548430aaa5e2555d6e02c64625fa3f (diff) | |
Rename PySide2 to PySide6
Adapt CMake files, build scripts, tests and examples.
Task-number: PYSIDE-904
Change-Id: I845f7b006e9ad274fed5444ec4c1f9dbe176ff88
Reviewed-by: Christian Tismer <tismer@stackless.com>
Diffstat (limited to 'sources/pyside6/libpyside')
42 files changed, 8342 insertions, 0 deletions
diff --git a/sources/pyside6/libpyside/CMakeLists.txt b/sources/pyside6/libpyside/CMakeLists.txt new file mode 100644 index 000000000..33e23063b --- /dev/null +++ b/sources/pyside6/libpyside/CMakeLists.txt @@ -0,0 +1,210 @@ +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() + +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(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_SRC + class_property.cpp + dynamicqmetaobject.cpp + feature_select.cpp + signalmanager.cpp + globalreceiverv2.cpp + pysideclassinfo.cpp + pysideqenum.cpp + pysidemetafunction.cpp + pysidesignal.cpp + pysideslot.cpp + pysideproperty.cpp + pysideqflags.cpp + pysideweakref.cpp + pyside.cpp + pysidestaticstrings.cpp +) + +# Add python files to project explorer in Qt Creator, when opening the CMakeLists.txt as a project, +# so you can look up python files with the Locator. +macro(add_other_files) + foreach(_it ${ARGN}) + if(NOT IS_DIRECTORY ${_it}) + get_filename_component(name ${_it} NAME) + if(NOT ${_it} MATCHES "^/\\\\..*$;~$") + set_source_files_properties(${_it} PROPERTIES HEADER_FILE_ONLY TRUE) + endif() + endif() + endforeach() +endmacro() + +# Test files. +file(GLOB_RECURSE pyside_folder_py_files "../*.py") + +# Mostly for setup.py. +file(GLOB setup_folder_py_files "../../../*.py") + +set(other_files ${pyside_folder_py_files} ${setup_folder_py_files}) +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} +) + +target_include_directories(pyside6 PUBLIC + $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}> + $<INSTALL_INTERFACE:include/PySide6> +) + +target_link_libraries(pyside6 + PRIVATE Shiboken6::libshiboken + PRIVATE ${QML_LIBRARIES} + PRIVATE ${Qt${QT_MAJOR_VERSION}Core_LIBRARIES}) + +set_target_properties(pyside6 PROPERTIES + VERSION ${BINDING_API_VERSION} + SOVERSION "${PYSIDE_SO_VERSION}" + 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}) + +if(PYSIDE_QT_CONF_PREFIX) + set_property(SOURCE pyside.cpp + APPEND + PROPERTY COMPILE_DEFINITIONS + PYSIDE_QT_CONF_PREFIX=${PYSIDE_QT_CONF_PREFIX}) +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() + set(LIBRARY_OUTPUT_SUFFIX ${CMAKE_RELEASE_POSTFIX}) +endif() + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D QT_NO_CAST_FROM_ASCII -D QT_NO_CAST_TO_ASCII") + +# create pkg-config file +configure_file("${CMAKE_CURRENT_SOURCE_DIR}/pyside6.pc.in" + "${CMAKE_CURRENT_BINARY_DIR}/pyside6${pyside6_SUFFIX}.pc" @ONLY) + +# for creating cmake-config files +include(CMakePackageConfigHelpers) + +# Build-tree / super project package config file. +set(PYSIDE_PYTHONPATH "${pysidebindings_BINARY_DIR}/PySide6") +set(PYSIDE_TYPESYSTEMS "${pysidebindings_SOURCE_DIR}/PySide6/templates/") +set(PYSIDE_GLUE "${pysidebindings_SOURCE_DIR}/PySide6/glue") + +configure_package_config_file( + "${CMAKE_CURRENT_SOURCE_DIR}/PySide6Config-spec.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/PySide6Config${SHIBOKEN_PYTHON_CONFIG_SUFFIX}.cmake" + INSTALL_DESTINATION "${CMAKE_CURRENT_BINARY_DIR}" + PATH_VARS PYSIDE_PYTHONPATH PYSIDE_TYPESYSTEMS PYSIDE_GLUE + INSTALL_PREFIX "${CMAKE_CURRENT_BINARY_DIR}" +) + +set(PYSIDE_PYTHONPATH "${PYTHON_SITE_PACKAGES}/PySide6") +set(PYSIDE_TYPESYSTEMS "${CMAKE_INSTALL_PREFIX}/share/PySide6${pyside6_SUFFIX}/typesystems") +set(PYSIDE_GLUE "${CMAKE_INSTALL_PREFIX}/share/PySide6${pyside6_SUFFIX}/glue") + +# Install-tree / relocatable package config file. +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}" + PATH_VARS PYSIDE_PYTHONPATH PYSIDE_TYPESYSTEMS PYSIDE_GLUE +) + +configure_file("${CMAKE_CURRENT_SOURCE_DIR}/PySide6Config.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/PySide6Config.cmake" @ONLY) +configure_file("${CMAKE_CURRENT_SOURCE_DIR}/PySide6ConfigVersion.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/PySide6ConfigVersion.cmake" @ONLY) + +install(FILES ${libpyside_HEADERS} + DESTINATION include/${BINDING_NAME}${pyside6_SUFFIX}) + +install(TARGETS pyside6 EXPORT PySide6Targets + LIBRARY DESTINATION "${LIB_INSTALL_DIR}" + ARCHIVE DESTINATION "${LIB_INSTALL_DIR}" + RUNTIME DESTINATION bin) +install(EXPORT PySide6Targets NAMESPACE PySide6:: + DESTINATION "${LIB_INSTALL_DIR}/cmake/PySide6-${BINDING_API_VERSION}") + +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}") + +install(FILES "${CMAKE_CURRENT_BINARY_DIR}/install/PySide6Config${SHIBOKEN_PYTHON_CONFIG_SUFFIX}.cmake" + DESTINATION "${LIB_INSTALL_DIR}/cmake/PySide6-${BINDING_API_VERSION}") + +install(FILES "${CMAKE_CURRENT_BINARY_DIR}/PySide6ConfigVersion.cmake" + DESTINATION "${LIB_INSTALL_DIR}/cmake/PySide6-${BINDING_API_VERSION}") diff --git a/sources/pyside6/libpyside/PySide6Config-spec.cmake.in b/sources/pyside6/libpyside/PySide6Config-spec.cmake.in new file mode 100644 index 000000000..fd3de1e75 --- /dev/null +++ b/sources/pyside6/libpyside/PySide6Config-spec.cmake.in @@ -0,0 +1,16 @@ +# PYSIDE_PYTHONPATH - Path to where the PySide6 Python module files could be found +# PYSIDE_TYPESYSTEMS - Type system files that should be used by other bindings extending PySide6 +# PYSIDE_GLUE - Path to module glue files. + +@PACKAGE_INIT@ + +# Import targets only when using an installed PySide6 config file (so not during a regular +# PySide6 build, or during a super project build). +if (NOT TARGET PySide6::pyside6) + include("${CMAKE_CURRENT_LIST_DIR}/PySide6Targets.cmake") +endif() + +# Set relocatable variables. +set_and_check(PYSIDE_PYTHONPATH "@PACKAGE_PYSIDE_PYTHONPATH@") +set_and_check(PYSIDE_TYPESYSTEMS "@PACKAGE_PYSIDE_TYPESYSTEMS@") +set_and_check(PYSIDE_GLUE "@PACKAGE_PYSIDE_GLUE@") diff --git a/sources/pyside6/libpyside/PySide6Config.cmake.in b/sources/pyside6/libpyside/PySide6Config.cmake.in new file mode 100644 index 000000000..037a5c44f --- /dev/null +++ b/sources/pyside6/libpyside/PySide6Config.cmake.in @@ -0,0 +1,5 @@ +if (NOT PYTHON_CONFIG_SUFFIX) + message(STATUS "PySide6Config: Using default python: @SHIBOKEN_PYTHON_CONFIG_SUFFIX@") + SET(PYTHON_CONFIG_SUFFIX @SHIBOKEN_PYTHON_CONFIG_SUFFIX@) +endif() +include(${CMAKE_CURRENT_LIST_DIR}/PySide6Config${PYTHON_CONFIG_SUFFIX}.cmake) diff --git a/sources/pyside6/libpyside/PySide6ConfigVersion.cmake.in b/sources/pyside6/libpyside/PySide6ConfigVersion.cmake.in new file mode 100644 index 000000000..f5073ce08 --- /dev/null +++ b/sources/pyside6/libpyside/PySide6ConfigVersion.cmake.in @@ -0,0 +1,10 @@ +set(PACKAGE_VERSION @BINDING_API_VERSION@) + +if("${PACKAGE_VERSION}" VERSION_LESS "${PACKAGE_FIND_VERSION}" ) + set(PACKAGE_VERSION_COMPATIBLE FALSE) +else("${PACKAGE_VERSION}" VERSION_LESS "${PACKAGE_FIND_VERSION}" ) + set(PACKAGE_VERSION_COMPATIBLE TRUE) + if( "${PACKAGE_FIND_VERSION}" STREQUAL "${PACKAGE_VERSION}") + set(PACKAGE_VERSION_EXACT TRUE) + endif( "${PACKAGE_FIND_VERSION}" STREQUAL "${PACKAGE_VERSION}") +endif("${PACKAGE_VERSION}" VERSION_LESS "${PACKAGE_FIND_VERSION}" ) diff --git a/sources/pyside6/libpyside/class_property.cpp b/sources/pyside6/libpyside/class_property.cpp new file mode 100644 index 000000000..edbb63769 --- /dev/null +++ b/sources/pyside6/libpyside/class_property.cpp @@ -0,0 +1,158 @@ +/**************************************************************************** +** +** 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" +#include "pysidestaticstrings.h" +#include "feature_select.h" +#include "class_property.h" + +#include <shiboken.h> +#include <sbkstaticstrings.h> + +extern "C" { + +/* + * A `classproperty` is the same as a `property` but the `__get__()` and `__set__()` + * methods are modified to always use the object class instead of a concrete instance. + * + * Note: A "static property" as it is often called does not exist per se. + * Static methods do not receive anything when created. Static methods which + * should participate in a property must be turned into class methods, before. + * See function `createProperty` in `feature_select.cpp`. + */ + +// `class_property.__get__()`: Always pass the class instead of the instance. +static PyObject *PyClassProperty_get(PyObject *self, PyObject * /*ob*/, PyObject *cls) +{ + return PyProperty_Type.tp_descr_get(self, cls, cls); +} + +// `class_property.__set__()`: Just like the above `__get__()`. +static int PyClassProperty_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); +} + +// 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) +{ + auto hold = Py_TYPE(self); + Py_TYPE(self) = &PyProperty_Type; + auto ret = PyProperty_Type.tp_init(self, args, kwargs); + Py_TYPE(self) = 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 *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)); + } + return type; +} + +/* + * Types with class properties need to handle `Type.class_prop = x` in a specific way. + * By default, Python replaces the `class_property` itself, but for wrapped C++ types + * we need to call `class_property.__set__()` in order to propagate the new value to + * the underlying C++ data structure. + */ +static int SbkObjectType_meta_setattro(PyObject *obj, PyObject *name, PyObject *value) +{ + // Use `_PepType_Lookup()` instead of `PyObject_GetAttr()` in order to get the raw + // descriptor (`property`) instead of calling `tp_descr_get` (`property.__get__()`). + auto type = reinterpret_cast<PyTypeObject *>(obj); + PyObject *descr = _PepType_Lookup(type, name); + + // The following assignment combinations are possible: + // 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 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); + } +} + +} // extern "C" + +/* + * These functions are added to the SbkObjectType_TypeF() dynamically. + */ +namespace PySide { namespace ClassProperty { + +void init() +{ + PyTypeObject *type = SbkObjectType_TypeF(); + type->tp_setattro = SbkObjectType_meta_setattro; + Py_TYPE(PyClassPropertyTypeF()) = type; +} + +} // namespace ClassProperty +} // namespace PySide diff --git a/sources/pyside6/libpyside/class_property.h b/sources/pyside6/libpyside/class_property.h new file mode 100644 index 000000000..f94fdde31 --- /dev/null +++ b/sources/pyside6/libpyside/class_property.h @@ -0,0 +1,69 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef CLASS_PROPERTY_H +#define CLASS_PROPERTY_H + +#include "pysidemacros.h" +#include <sbkpython.h> + +extern "C" { + +typedef struct { + PyObject_HEAD + PyObject *prop_get; + PyObject *prop_set; + PyObject *prop_del; + PyObject *prop_doc; + int getter_doc; +} propertyobject; + +PYSIDE_API PyTypeObject *PyClassPropertyTypeF(); + +} // extern "C" + +namespace PySide { +namespace ClassProperty { + +PYSIDE_API void init(); + +} // namespace ClassProperty +} // namespace PySide + +#endif // CLASS_PROPERTY_H diff --git a/sources/pyside6/libpyside/dynamicqmetaobject.cpp b/sources/pyside6/libpyside/dynamicqmetaobject.cpp new file mode 100644 index 000000000..d63fa281b --- /dev/null +++ b/sources/pyside6/libpyside/dynamicqmetaobject.cpp @@ -0,0 +1,588 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#include "dynamicqmetaobject.h" +#include "dynamicqmetaobject_p.h" +#include "pysidesignal.h" +#include "pysidesignal_p.h" +#include "pysideproperty.h" +#include "pysideproperty_p.h" +#include "pysideslot_p.h" +#include "pysideqenum.h" + +#include <shiboken.h> + +#include <QtCore/QByteArray> +#include <QtCore/QObject> +#include <QtCore/QStringList> +#include <QtCore/QTextStream> +#include <QtCore/QVector> +#include <private/qmetaobjectbuilder_p.h> + +#include <cstring> +#include <vector> + +using namespace PySide; + +// MetaObjectBuilder: Provides the QMetaObject's returned by +// QObject::metaObject() for PySide6 objects. There are several +// scenarios to consider: +// 1) A plain Qt class (say QTimer) is instantiated. In that case, +// return the base meta object until a modification is made by +// adding methods, properties or class info (cf qmetaobject_test.py). +// In that case, instantiate a QMetaObjectBuilder inheriting the +// base meta meta object, add the method and return the result +// of QMetaObjectBuilder::toMetaObject() (with dirty handling should +// further modifications be made). +// 2) A Python class inheriting a Qt class is instantiated. For this, +// instantiate a QMetaObjectBuilder and add the methods/properties +// found by inspecting the Python class. + +class MetaObjectBuilderPrivate +{ +public: + using MetaObjects = std::vector<const QMetaObject *>; + + QMetaObjectBuilder *ensureBuilder(); + void parsePythonType(PyTypeObject *type); + int indexOfMethod(QMetaMethod::MethodType mtype, + const QByteArray &signature) const; + int indexOfProperty(const QByteArray &name) const; + int addSlot(const QByteArray &signature); + int addSlot(const QByteArray &signature, const QByteArray &type); + int addSignal(const QByteArray &signature); + void removeMethod(QMetaMethod::MethodType mtype, int index); + int getPropertyNotifyId(PySideProperty *property) const; + int addProperty(const QByteArray &property, PyObject *data); + void addInfo(const QByteArray &key, const QByteArray &value); + void addInfo(const QMap<QByteArray, QByteArray> &info); + void addEnumerator(const char *name, + bool flag, + bool scoped, + const QVector<QPair<QByteArray, int> > &entries); + void removeProperty(int index); + const QMetaObject *update(); + + QMetaObjectBuilder *m_builder = nullptr; + + const QMetaObject *m_baseObject = nullptr; + MetaObjects m_cachedMetaObjects; + bool m_dirty = true; +}; + +QMetaObjectBuilder *MetaObjectBuilderPrivate::ensureBuilder() +{ + if (!m_builder) { + m_builder = new QMetaObjectBuilder(); + m_builder->setClassName(m_baseObject->className()); + m_builder->setSuperClass(m_baseObject); + } + return m_builder; +} + +MetaObjectBuilder::MetaObjectBuilder(const char *className, const QMetaObject *metaObject) : + m_d(new MetaObjectBuilderPrivate) +{ + m_d->m_baseObject = metaObject; + m_d->m_builder = new QMetaObjectBuilder(); + m_d->m_builder->setClassName(className); + m_d->m_builder->setSuperClass(metaObject); + m_d->m_builder->setClassName(className); +} + +MetaObjectBuilder::MetaObjectBuilder(PyTypeObject *type, const QMetaObject *metaObject) + : m_d(new MetaObjectBuilderPrivate) +{ + m_d->m_baseObject = metaObject; + const char *className = type->tp_name; + if (const char *lastDot = strrchr(type->tp_name, '.')) + className = lastDot + 1; + // Different names indicate a Python class inheriting a Qt class. + // Parse the type. + if (strcmp(className, metaObject->className()) != 0) { + m_d->m_builder = new QMetaObjectBuilder(); + m_d->m_builder->setClassName(className); + m_d->m_builder->setSuperClass(metaObject); + m_d->parsePythonType(type); + } +} + +MetaObjectBuilder::~MetaObjectBuilder() +{ + for (auto *metaObject : m_d->m_cachedMetaObjects) + free(const_cast<QMetaObject*>(metaObject)); + delete m_d->m_builder; + delete m_d; +} + +int MetaObjectBuilderPrivate::indexOfMethod(QMetaMethod::MethodType mtype, + const QByteArray &signature) const +{ + int result = -1; + if (m_builder) { + switch (mtype) { + case QMetaMethod::Signal: + result = m_builder->indexOfSignal(signature); + break; + case QMetaMethod::Slot: + result = m_builder->indexOfSlot(signature); + break; + case QMetaMethod::Constructor: + result = m_builder->indexOfConstructor(signature); + break; + case QMetaMethod::Method: + result = m_builder->indexOfMethod(signature); + break; + } + if (result >= 0) + return result + m_baseObject->methodCount(); + } + switch (mtype) { + case QMetaMethod::Signal: + result = m_baseObject->indexOfSignal(signature); + break; + case QMetaMethod::Slot: + result = m_baseObject->indexOfSlot(signature); + break; + case QMetaMethod::Constructor: + result = m_baseObject->indexOfConstructor(signature); + break; + case QMetaMethod::Method: + result = m_baseObject->indexOfMethod(signature); + break; + } + return result; +} + +int MetaObjectBuilder::indexOfMethod(QMetaMethod::MethodType mtype, + const QByteArray &signature) const +{ + return m_d->indexOfMethod(mtype, signature); +} + +int MetaObjectBuilderPrivate::indexOfProperty(const QByteArray &name) const +{ + if (m_builder) { + const int result = m_builder->indexOfProperty(name); + if (result >= 0) + return m_baseObject->propertyCount() + result; + } + return m_baseObject->indexOfProperty(name); +} + +int MetaObjectBuilder::indexOfProperty(const QByteArray &name) const +{ + return m_d->indexOfProperty(name); +} + +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 bool ok = openParen != -1 && closingParen != -1 && openParen < closingParen; + if (!ok) { + const QByteArray message = + "MetaObjectBuilder::addMethod: Invalid method signature provided for \"" + + signature + '"'; + PyErr_WarnEx(PyExc_RuntimeWarning, message.constData(), 0); + } + return ok; +} + +int MetaObjectBuilderPrivate::addSlot(const QByteArray &signature) +{ + if (!checkMethodSignature(signature)) + return -1; + m_dirty = true; + return m_baseObject->methodCount() + + ensureBuilder()->addSlot(signature).index(); +} + +int MetaObjectBuilder::addSlot(const char *signature) +{ + return m_d->addSlot(signature); +} + +int MetaObjectBuilderPrivate::addSlot(const QByteArray &signature, + const QByteArray &type) +{ + if (!checkMethodSignature(signature)) + return -1; + m_dirty = true; + QMetaMethodBuilder methodBuilder = ensureBuilder()->addSlot(signature); + methodBuilder.setReturnType(type); + return m_baseObject->methodCount() + methodBuilder.index(); +} + +int MetaObjectBuilder::addSlot(const char *signature, const char *type) +{ + return m_d->addSlot(signature, type); +} + +int MetaObjectBuilderPrivate::addSignal(const QByteArray &signature) +{ + if (!checkMethodSignature(signature)) + return -1; + m_dirty = true; + return m_baseObject->methodCount() + + ensureBuilder()->addSignal(signature).index(); +} + +int MetaObjectBuilder::addSignal(const char *signature) +{ + return m_d->addSignal(signature); +} + +void MetaObjectBuilderPrivate::removeMethod(QMetaMethod::MethodType mtype, + int index) +{ + index -= m_baseObject->methodCount(); + auto builder = ensureBuilder(); + Q_ASSERT(index >= 0 && index < builder->methodCount()); + switch (mtype) { + case QMetaMethod::Constructor: + builder->removeConstructor(index); + break; + default: + builder->removeMethod(index); + break; + } + m_dirty = true; +} + +void MetaObjectBuilder::removeMethod(QMetaMethod::MethodType mtype, int index) +{ + m_d->removeMethod(mtype, index); +} + +int MetaObjectBuilderPrivate::getPropertyNotifyId(PySideProperty *property) const +{ + int notifyId = -1; + if (property->d->notify) { + if (const char *signalNotify = PySide::Property::getNotifyName(property)) + notifyId = indexOfMethod(QMetaMethod::Signal, signalNotify); + } + return notifyId; +} + +int MetaObjectBuilderPrivate::addProperty(const QByteArray &propertyName, + PyObject *data) +{ + int index = indexOfProperty(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); + // Adding property attributes + newProperty.setReadable(PySide::Property::isReadable(property)); + newProperty.setWritable(PySide::Property::isWritable(property)); + newProperty.setResettable(PySide::Property::hasReset(property)); + newProperty.setDesignable(PySide::Property::isDesignable(property)); + newProperty.setScriptable(PySide::Property::isScriptable(property)); + newProperty.setStored(PySide::Property::isStored(property)); + newProperty.setUser(PySide::Property::isUser(property)); + newProperty.setConstant(PySide::Property::isConstant(property)); + newProperty.setFinal(PySide::Property::isFinal(property)); + + index = newProperty.index() + m_baseObject->propertyCount(); + m_dirty = true; + return index; +} + +int MetaObjectBuilder::addProperty(const char *property, PyObject *data) +{ + return m_d->addProperty(property, data); +} + +void MetaObjectBuilderPrivate::addInfo(const QByteArray &key, + const QByteArray &value) +{ + ensureBuilder()->addClassInfo(key, value); + m_dirty = true; +} + +void MetaObjectBuilder::addInfo(const char *key, const char *value) +{ + m_d->addInfo(key, value); +} + +void MetaObjectBuilderPrivate::addInfo(const QMap<QByteArray, QByteArray> &info) +{ + auto builder = ensureBuilder(); + for (auto i = info.constBegin(), end = info.constEnd(); i != end; ++i) + builder->addClassInfo(i.key(), i.value()); + m_dirty = true; +} + +void MetaObjectBuilder::addInfo(const QMap<QByteArray, QByteArray> &info) +{ + m_d->addInfo(info); +} + +void MetaObjectBuilder::addEnumerator(const char *name, bool flag, bool scoped, + const QVector<QPair<QByteArray, int> > &entries) +{ + m_d->addEnumerator(name, flag, scoped, entries); +} + +void MetaObjectBuilderPrivate::addEnumerator(const char *name, bool flag, bool scoped, + const QVector<QPair<QByteArray, int> > &entries) +{ + auto builder = ensureBuilder(); + int have_already = builder->indexOfEnumerator(name); + if (have_already >= 0) + builder->removeEnumerator(have_already); + auto enumbuilder = builder->addEnumerator(name); + enumbuilder.setIsFlag(flag); + enumbuilder.setIsScoped(scoped); + + for (auto item : entries) + enumbuilder.addKey(item.first, item.second); + m_dirty = true; +} + +void MetaObjectBuilderPrivate::removeProperty(int index) +{ + index -= m_baseObject->propertyCount(); + auto builder = ensureBuilder(); + Q_ASSERT(index >= 0 && index < builder->propertyCount()); + builder->removeProperty(index); + m_dirty = true; +} + +void MetaObjectBuilder::removeProperty(int index) +{ + m_d->removeProperty(index); +} + +// PYSIDE-315: Instead of sorting the items and maybe breaking indices, we +// ensure that the signals and slots are sorted by the improved +// parsePythonType() (signals must go before slots). The order can only +// become distorted if the class is modified after creation. In that +// case, we give a warning. + +static QString msgMethodSortOrder(const QMetaObject *mo, int offendingIndex) +{ + QString result; + QTextStream str(&result); + str << "\n\n*** Sort Warning ***\nSignals and slots in QMetaObject '" + << mo->className() + << "' are not ordered correctly, this may lead to issues.\n"; + const int methodOffset = mo->methodOffset(); + for (int m = methodOffset, methodCount = mo->methodCount(); m < methodCount; ++m) { + const auto method = mo->method(m); + str << (m - methodOffset + 1) << (m > offendingIndex ? '!' : ' ') + << (method.methodType() == QMetaMethod::Signal ? " Signal " : " Slot ") + << method.methodSignature() << '\n'; + } + return result; +} + +static void checkMethodOrder(const QMetaObject *metaObject) +{ + const int lastMethod = metaObject->methodCount() - 1; + for (int m = metaObject->methodOffset(); m < lastMethod; ++m) { + if (metaObject->method(m).methodType() == QMetaMethod::Slot + && metaObject->method(m + 1).methodType() == QMetaMethod::Signal) { + const auto message = msgMethodSortOrder(metaObject, m); + PyErr_WarnEx(PyExc_RuntimeWarning, qPrintable(message), 0); + // Prevent a warning from being turned into an error. We cannot easily unwind. + PyErr_Clear(); + break; + } + } +} + +const QMetaObject *MetaObjectBuilderPrivate::update() +{ + if (!m_builder) + return m_baseObject; + if (m_cachedMetaObjects.empty() || m_dirty) { + // PYSIDE-803: The dirty branch needs to be protected by the GIL. + // This was moved from SignalManager::retrieveMetaObject to here, + // which is only the update in "return builder->update()". + Shiboken::GilState gil; + m_cachedMetaObjects.push_back(m_builder->toMetaObject()); + checkMethodOrder(m_cachedMetaObjects.back()); + m_dirty = false; + } + return m_cachedMetaObjects.back(); +} + +const QMetaObject *MetaObjectBuilder::update() +{ + return m_d->update(); +} + +using namespace Shiboken; + +void MetaObjectBuilderPrivate::parsePythonType(PyTypeObject *type) +{ + // Get all non-QObject-derived base types in method resolution order, filtering out the types + // that can't have signals, slots or properties. + // This enforces registering of all signals and slots at type parsing time, and not later at + // signal connection time, thus making sure no method indices change which would break + // 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 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) { + basesToCheck.push_back(baseType); + } + } + + // PYSIDE-315: Handle all signals first, in all involved types. + // Leave the properties to be registered after signals because they may depend on + // notify signals. + for (PyTypeObject *baseType : basesToCheck) { + PyObject *attrs = baseType->tp_dict; + PyObject *key = nullptr; + PyObject *value = nullptr; + Py_ssize_t pos = 0; + + 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 + ')'; + 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); + } + } + } + } + } + } + + AutoDecRef slotAttrName(String::fromCString(PYSIDE_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; + 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)); + if (index == -1) + addProperty(String::toCString(key), value); + } else if (Py_TYPE(value)->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); + } + } + } + } + } + } + // PYSIDE-957: Collect the delayed QEnums + auto collectedEnums = PySide::QEnum::resolveDelayedQEnums(type); + for (PyObject *obEnumType : collectedEnums) { + bool isFlag = PySide::QEnum::isFlag(obEnumType); + AutoDecRef obName(PyObject_GetAttr(obEnumType, PyMagicName::name())); + // Everything has been checked already in resolveDelayedQEnums. + // Therefore, we don't need to error-check here again. + auto name = String::toCString(obName); + AutoDecRef members(PyObject_GetAttr(obEnumType, PyMagicName::members())); + AutoDecRef items(PyMapping_Items(members)); + Py_ssize_t nr_items = PySequence_Length(items); + + QVector<QPair<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); + } + addEnumerator(name, isFlag, true, entries); + } +} diff --git a/sources/pyside6/libpyside/dynamicqmetaobject.h b/sources/pyside6/libpyside/dynamicqmetaobject.h new file mode 100644 index 000000000..7279d5c26 --- /dev/null +++ b/sources/pyside6/libpyside/dynamicqmetaobject.h @@ -0,0 +1,84 @@ +/**************************************************************************** +** +** 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 DYNAMICQMETAOBJECT_H +#define DYNAMICQMETAOBJECT_H + +#include <sbkpython.h> + +#include <QtCore/QMetaObject> +#include <QtCore/QMetaMethod> + +class MetaObjectBuilderPrivate; + +namespace PySide +{ + +class MetaObjectBuilder +{ + Q_DISABLE_COPY(MetaObjectBuilder) +public: + MetaObjectBuilder(const char *className, const QMetaObject *metaObject); + + MetaObjectBuilder(PyTypeObject *type, const QMetaObject *metaObject); + ~MetaObjectBuilder(); + + int indexOfMethod(QMetaMethod::MethodType mtype, const QByteArray &signature) const; + int indexOfProperty(const QByteArray &name) const; + int addSlot(const char *signature); + int addSlot(const char *signature, const char *type); + int addSignal(const char *signature); + void removeMethod(QMetaMethod::MethodType mtype, int index); + int addProperty(const char *property, PyObject *data); + void addInfo(const char *key, const char *value); + void addInfo(const QMap<QByteArray, QByteArray> &info); + void addEnumerator(const char *name, + bool flag, + bool scoped, + const QVector<QPair<QByteArray, int> > &entries); + void removeProperty(int index); + + const QMetaObject *update(); + +private: + MetaObjectBuilderPrivate *m_d; +}; + +} +#endif diff --git a/sources/pyside6/libpyside/dynamicqmetaobject_p.h b/sources/pyside6/libpyside/dynamicqmetaobject_p.h new file mode 100644 index 000000000..9199630b7 --- /dev/null +++ b/sources/pyside6/libpyside/dynamicqmetaobject_p.h @@ -0,0 +1,107 @@ +/**************************************************************************** +** +** 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 = 0); + 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 new file mode 100644 index 000000000..d6d77a530 --- /dev/null +++ b/sources/pyside6/libpyside/feature_select.cpp @@ -0,0 +1,766 @@ +/**************************************************************************** +** +** 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 "feature_select.h" +#include "pyside.h" +#include "pysidestaticstrings.h" +#include "class_property.h" + +#include <shiboken.h> +#include <sbkstaticstrings.h> + +////////////////////////////////////////////////////////////////////////////// +// +// PYSIDE-1019: Support switchable extensions +// +// This functionality is no longer implemented in the signature module, since +// the PyCFunction getsets do not have to be modified any longer. +// Instead, we simply exchange the complete class dicts. This is done in the +// basewrapper.cpp file and in every generated `tp_(get|set)attro`. +// +// This is the general framework of the switchable extensions. +// A maximum of eight features is planned so far. This seems to be enough. +// More features are possible, but then we must somehow register the +// extra `select_id`s above 255. +// + +/***************************************************************************** + + How Does This Feature Selection Work? + ------------------------------------- + +The basic idea is to replace the `tp_dict` of a QObject derived type. +This way, we can replace the methods of the class in no time. + +The crucial point to understand is how the `tp_dict` is actually accessed: +When you type "QObject.__dict__", the descriptor of `SbkObjectType_Type` +is called. This descriptor is per default unassigned, so the base class +PyType_Type provides the tp_getset method `type_dict`: + + static PyObject * + type_dict(PyTypeObject *type, void *context) + { + if (type->tp_dict == NULL) { + Py_RETURN_NONE; + } + return PyDictProxy_New(type->tp_dict); + } + +In order to change that, we need to insert our own version into SbkObjectType: + + static PyObject *Sbk_TypeGet___dict__(PyTypeObject *type, void *context) + { + auto dict = type->tp_dict; + if (dict == NULL) + Py_RETURN_NONE; + if (SelectFeatureSet != nullptr) + dict = SelectFeatureSet(type); + return PyDictProxy_New(dict); + } + +This way, the Python function `type_ready()` does not fill in the default, +but uses our modified version. It a similar way, we overwrite type_getattro +with our own version, again in SbkObjectType, replacing the default of +PyType_Type. + +Now we can exchange the dict with a customized version. +We have our own derived type `ChameleonDict` with additional attributes. +These allow us to create a ring of dicts which can be rotated to the actual +needed dict version: + +Every dict has a field `select_id` which is selected by the `from __feature__` +import. The dicts are cyclic connected by the `dict_ring` field. + +When a class dict is required, now always `SelectFeatureSet` is called, which +looks into the `__name__` attribute of the active module and decides which +version of `tp_dict` is needed. Then the right dict is searched in the ring +and created if not already there. + +Furthermore, we need to overwrite every `tp_(get|set)attro` with a version +that switches dicts right before looking up methods. +The dict changing must walk the whole `tp_mro` in order to change all names. + +This is everything that the following code does. + +*****************************************************************************/ + + +namespace PySide { namespace Feature { + +using namespace Shiboken; + +typedef bool(*FeatureProc)(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() +{ + // It is not easy to create a compatible dict object with the + // limited API. Easier is to use Python to create a derived + // type and to modify that a bit from the C code. + PyObject *ChameleonDict = PepRun_GetResult(R"CPP(if True: + + class ChameleonDict(dict): + __slots__ = ("dict_ring", "select_id") + + result = ChameleonDict + + )CPP"); + return reinterpret_cast<PyTypeObject *>(ChameleonDict); +} + +static PyTypeObject *new_dict_type = nullptr; + +static void ensureNewDictType() +{ + if (new_dict_type == nullptr) { + new_dict_type = createDerivedDictType(); + if (new_dict_type == nullptr) + Py_FatalError("PySide6: Problem creating ChameleonDict"); + } +} + +static inline PyObject *nextInCircle(PyObject *dict) +{ + // returns a borrowed ref + AutoDecRef next_dict(PyObject_GetAttr(dict, PyName::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; +} + +static inline void setCurrentSelectId(PyTypeObject *type, PyObject *select_id) +{ + SbkObjectType_SetReserved(type, PyInt_AsSsize_t(select_id)); // int/long cheating +} + +static inline void setCurrentSelectId(PyTypeObject *type, int id) +{ + 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]; +} + +static bool replaceClassDict(PyTypeObject *type) +{ + /* + * Replace the type dict by the derived ChameleonDict. + * 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); + 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); + // 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; + return true; +} + +static bool addNewDict(PyTypeObject *type, PyObject *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); + if (new_dict == nullptr) + return false; + setSelectId(new_dict, select_id); + // insert the dict into the ring + auto next_dict = nextInCircle(dict); + setNextDict(dict, new_dict); + setNextDict(new_dict, next_dict); + type->tp_dict = new_dict; + return true; +} + +static bool moveToFeatureSet(PyTypeObject *type, PyObject *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; + do { + dict = nextInCircle(dict); + AutoDecRef 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); + return true; + } + } while (dict != initial_dict); + type->tp_dict = initial_dict; + return false; +} + +static bool createNewFeatureSet(PyTypeObject *type, PyObject *select_id) +{ + /* + * Create a new feature set. + * A `false` return value is a fatal error. + * + * A FeatureProc sees an empty `type->tp_dict` and the previous dict + * 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); + Q_UNUSED(ok); + assert(ok); + + AutoDecRef prev_dict(type->tp_dict); + Py_INCREF(prev_dict); // keep the first ref unchanged + if (!addNewDict(type, select_id)) + return false; + auto id = PyInt_AsSsize_t(select_id); // int/long cheating + 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); + // 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)); + if (prev_dict.isNull()) + return false; + } + } + } + return true; +} + +static bool SelectFeatureSetSubtype(PyTypeObject *type, PyObject *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)) { + // 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; + } + } + if (!moveToFeatureSet(type, select_id)) { + if (!createNewFeatureSet(type, select_id)) { + Py_FatalError("failed to create a new feature set!"); + return false; + } + } + return true; +} + +static inline PyObject *SelectFeatureSet(PyTypeObject *type) +{ + /* + * This is the main function of the module. + * The purpose of this function is to switch the dict of a class right + * before a (get|set)attro call is performed. + * + * 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)) { + // 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; + } + } + return type->tp_dict; +} + +// For cppgenerator: +void Select(PyObject *obj) +{ + if (featurePointer == nullptr) + return; + auto type = Py_TYPE(obj); + type->tp_dict = SelectFeatureSet(type); +} + +PyObject *Select(PyTypeObject *type) +{ + if (featurePointer != nullptr) + type->tp_dict = SelectFeatureSet(type); + return type->tp_dict; +} + +static bool feature_01_addLowerNames(PyTypeObject *type, PyObject *prev_dict, int id); +static bool feature_02_true_property(PyTypeObject *type, PyObject *prev_dict, int id); +static bool feature_04_addDummyNames(PyTypeObject *type, PyObject *prev_dict, int id); +static bool feature_08_addDummyNames(PyTypeObject *type, PyObject *prev_dict, int id); +static bool feature_10_addDummyNames(PyTypeObject *type, PyObject *prev_dict, int id); +static bool feature_20_addDummyNames(PyTypeObject *type, PyObject *prev_dict, int id); +static bool feature_40_addDummyNames(PyTypeObject *type, PyObject *prev_dict, int id); +static bool feature_80_addDummyNames(PyTypeObject *type, PyObject *prev_dict, int id); + +static FeatureProc featureProcArray[] = { + feature_01_addLowerNames, + feature_02_true_property, + feature_04_addDummyNames, + feature_08_addDummyNames, + feature_10_addDummyNames, + feature_20_addDummyNames, + feature_40_addDummyNames, + feature_80_addDummyNames, + nullptr +}; + +void finalize() +{ + for (int idx = -1; idx < 256; ++idx) + Py_DECREF(fast_id_array[idx]); +} + +static bool patch_property_impl(); + +void init() +{ + // This function can be called multiple times. + static bool is_initialized = false; + 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); + patch_property_impl(); + PySide::ClassProperty::init(); + is_initialized = true; + } + // Reset the cache. This is called at any "from __feature__ import". + cached_globals = nullptr; +} + +////////////////////////////////////////////////////////////////////////////// +// +// PYSIDE-1019: Support switchable extensions +// +// Feature 0x01: Allow snake_case instead of camelCase +// +// This functionality is no longer implemented in the signature module, since +// the PyCFunction getsets do not have to be modified any longer. +// Instead, we simply exchange the complete class dicts. This is done in the +// basewrapper.cpp file. +// + +static PyObject *methodWithNewName(PyTypeObject *type, + PyMethodDef *meth, + const char *new_name) +{ + /* + * Create a method with a lower case name. + */ + auto obtype = reinterpret_cast<PyObject *>(type); + int len = strlen(new_name); + auto name = new char[len + 1]; + strcpy(name, new_name); + auto new_meth = new PyMethodDef; + new_meth->ml_name = name; + new_meth->ml_meth = meth->ml_meth; + new_meth->ml_flags = meth->ml_flags; + new_meth->ml_doc = meth->ml_doc; + PyObject *descr = nullptr; + if (new_meth->ml_flags & METH_STATIC) { + AutoDecRef cfunc(PyCFunction_NewEx(new_meth, obtype, nullptr)); + if (cfunc.isNull()) + return nullptr; + descr = PyStaticMethod_New(cfunc); + } + else { + descr = PyDescr_NewMethod(type, new_meth); + } + return descr; +} + +static bool feature_01_addLowerNames(PyTypeObject *type, PyObject *prev_dict, int id) +{ + /* + * 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; + + // We first copy the things over which will not be changed: + while (PyDict_Next(prev_dict, &pos, &key, &value)) { + if ( Py_TYPE(value) != PepMethodDescr_TypePtr + && Py_TYPE(value) != PepStaticMethod_TypePtr) { + if (PyDict_SetItem(lower_dict, key, value)) + return false; + 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)); + AutoDecRef new_method(methodWithNewName(type, meth, name)); + if (new_method.isNull()) + return false; + if (PyDict_SetItemString(lower_dict, name, new_method) < 0) + return false; + } + return true; +} + +////////////////////////////////////////////////////////////////////////////// +// +// PYSIDE-1019: Support switchable extensions +// +// Feature 0x02: Use true properties instead of getters and setters +// + +// This is the Python 2 version for inspection of m_ml, only. +// The actual Python 3 version is larget. + +typedef struct { + 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; + 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; + new_func->ml_doc = func->m_ml->ml_doc; + auto cfunc = PyCFunction_NewEx(new_func, nullptr, nullptr); + cfunc = PyDescr_NewClassMethod(type, new_func); + return cfunc; +} + +static PyObject *createProperty(PyTypeObject *type, PyObject *getter, PyObject *setter) +{ + bool chassprop = false; + assert(getter != nullptr); + if (setter == nullptr) + setter = Py_None; + auto ptype = &PyProperty_Type; + if (Py_TYPE(getter) == PepStaticMethod_TypePtr) { + ptype = PyClassPropertyTypeF(); + chassprop = true; + getter = modifyStaticToClassMethod(type, getter); + if (setter != Py_None) + setter = modifyStaticToClassMethod(type, setter); + } + auto obtype = reinterpret_cast<PyObject *>(ptype); + PyObject *prop = PyObject_CallFunctionObjArgs(obtype, getter, setter, nullptr); + return prop; +} + +static QStringList parseFields(const char *propstr) +{ + /* + * Break the string into subfields at ':' and add defaults. + */ + QString s = QString(QLatin1String(propstr)); + auto list = s.split(QLatin1Char(':')); + assert(list.size() == 2 || list.size() == 3); + auto name = list[0]; + auto read = list[1]; + if (read.size() == 0) + list[1] = name; + if (list.size() == 2) + return list; + auto write = list[2]; + if (write.size() == 0) { + list[2] = QLatin1String("set") + name; + list[2][3] = list[2][3].toUpper(); + } + return list; +} + +static PyObject *make_snake_case(QString s, bool lower) +{ + if (s.isNull()) + return nullptr; + return String::getSnakeCaseName(s.toLatin1().data(), lower); +} + +static bool feature_02_true_property(PyTypeObject *type, PyObject *prev_dict, int id) +{ + /* + * Use the property info to create true Python property objects. + */ + + // 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; + + // We then replace methods by properties. + bool lower = (id & 0x01) != 0; + auto props = SbkObjectType_GetPropertyStrings(type); + if (props == nullptr || *props == nullptr) + return true; + for (; *props != nullptr; ++props) { + auto propstr = *props; + auto fields = parseFields(propstr); + bool haveWrite = fields.size() == 3; + PyObject *name = make_snake_case(fields[0], lower); + PyObject *read = make_snake_case(fields[1], lower); + PyObject *write = haveWrite ? make_snake_case(fields[2], lower) : nullptr; + PyObject *getter = PyDict_GetItem(prev_dict, read); + if (getter == nullptr || !(Py_TYPE(getter) == PepMethodDescr_TypePtr || + Py_TYPE(getter) == PepStaticMethod_TypePtr)) + continue; + PyObject *setter = haveWrite ? PyDict_GetItem(prev_dict, write) : nullptr; + + AutoDecRef PyProperty(createProperty(type, getter, setter)); + if (PyProperty.isNull()) + return false; + if (PyDict_SetItem(prop_dict, name, PyProperty) < 0) + return false; + if (fields[0] != fields[1] && PyDict_GetItem(prop_dict, read)) + if (PyDict_DelItem(prop_dict, read) < 0) + return false; + // Theoretically, we need to check for multiple signatures to be exact. + // But we don't do so intentionally because it would be confusing. + if (haveWrite && PyDict_GetItem(prop_dict, write)) + if (PyDict_DelItem(prop_dict, write) < 0) + return false; + } + return true; +} + +////////////////////////////////////////////////////////////////////////////// +// +// These are a number of patches to make Python's property object better +// suitable for us. +// We turn `__doc__` into a lazy attribute saving signature initialization. +// +// There is now also a class property implementation which inherits +// from this one. +// + +static PyObject *property_doc_get(PyObject *self, void *) +{ + auto po = reinterpret_cast<propertyobject *>(self); + + if (po->prop_doc != nullptr && po->prop_doc != Py_None) { + Py_INCREF(po->prop_doc); + return po->prop_doc; + } + 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()); + if (txt != nullptr) { + Py_INCREF(txt); + po->prop_doc = txt; + Py_INCREF(txt); + return txt; + } + PyErr_Clear(); + } + Py_RETURN_NONE; +} + +static int property_doc_set(PyObject *self, PyObject *value, void *) +{ + auto po = reinterpret_cast<propertyobject *>(self); + + Py_INCREF(value); + po->prop_doc = value; + return 0; +} + +static PyGetSetDef property_getset[] = { + // This gets added to the existing getsets + {const_cast<char *>("__doc__"), property_doc_get, property_doc_set, nullptr, nullptr}, + {nullptr, nullptr, nullptr, nullptr, nullptr} +}; + +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; + AutoDecRef descr(PyDescr_NewGetSet(type, gsp)); + if (descr.isNull()) + return false; + if (PyDict_SetItemString(dict, gsp->name, descr) < 0) + return false; + return true; +} + +////////////////////////////////////////////////////////////////////////////// +// +// PYSIDE-1019: Support switchable extensions +// +// Feature 0x04..0x40: A fake switchable option for testing +// + +#define SIMILAR_FEATURE(xx) \ +static bool feature_##xx##_addDummyNames(PyTypeObject *type, PyObject *prev_dict, int id) \ +{ \ + PyObject *dict = type->tp_dict; \ + if (PyDict_Update(dict, prev_dict) < 0) \ + return false; \ + if (PyDict_SetItemString(dict, "fake_feature_" #xx, Py_None) < 0) \ + return false; \ + return true; \ +} + +SIMILAR_FEATURE(04) +SIMILAR_FEATURE(08) +SIMILAR_FEATURE(10) +SIMILAR_FEATURE(20) +SIMILAR_FEATURE(40) +SIMILAR_FEATURE(80) + +} // namespace PySide +} // namespace Feature diff --git a/sources/pyside6/libpyside/feature_select.h b/sources/pyside6/libpyside/feature_select.h new file mode 100644 index 000000000..845828a4c --- /dev/null +++ b/sources/pyside6/libpyside/feature_select.h @@ -0,0 +1,56 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef FEATURE_SELECT_H +#define FEATURE_SELECT_H + +#include "pysidemacros.h" +#include <sbkpython.h> + +namespace PySide { +namespace Feature { + +PYSIDE_API void init(); +PYSIDE_API void Select(PyObject *obj); +PYSIDE_API PyObject *Select(PyTypeObject *type); + +} // namespace Feature +} // namespace PySide + +#endif // FEATURE_SELECT_H diff --git a/sources/pyside6/libpyside/globalreceiverv2.cpp b/sources/pyside6/libpyside/globalreceiverv2.cpp new file mode 100644 index 000000000..72197fe51 --- /dev/null +++ b/sources/pyside6/libpyside/globalreceiverv2.cpp @@ -0,0 +1,348 @@ +/**************************************************************************** +** +** 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 "globalreceiverv2.h" +#include "dynamicqmetaobject_p.h" +#include "pysideweakref.h" +#include "signalmanager.h" + +#include <autodecref.h> +#include <gilstate.h> + +#include <QtCore/QMetaMethod> +#include <QtCore/QSet> + +#define RECEIVER_DESTROYED_SLOT_NAME "__receiverDestroyed__(QObject*)" + +namespace +{ + static int DESTROY_SIGNAL_ID = 0; + static int DESTROY_SLOT_ID = 0; +} + +namespace PySide +{ +class DynamicSlotDataV2 +{ + Q_DISABLE_COPY(DynamicSlotDataV2) + public: + DynamicSlotDataV2(PyObject *callback, GlobalReceiverV2 *parent); + ~DynamicSlotDataV2(); + + int addSlot(const char *signature); + int id(const char *signature) const; + PyObject *callback(); + QByteArray hash() const; + void notify(); + + static void onCallbackDestroyed(void *data); + static QByteArray hash(PyObject *callback); + + + private: + bool m_isMethod; + PyObject *m_callback; + PyObject *m_pythonSelf; + PyObject *m_pyClass; + PyObject *m_weakRef; + QMap<QByteArray, int> m_signatures; + GlobalReceiverV2 *m_parent; + QByteArray m_hash; +}; + +} + +using namespace PySide; + +DynamicSlotDataV2::DynamicSlotDataV2(PyObject *callback, GlobalReceiverV2 *parent) + : m_pythonSelf(0), m_pyClass(0), m_weakRef(0), m_parent(parent) +{ + Shiboken::GilState gil; + + m_isMethod = PyMethod_Check(callback); + 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 + m_callback = PyMethod_GET_FUNCTION(callback); + m_pythonSelf = PyMethod_GET_SELF(callback); + + //monitor class from method lifetime + m_weakRef = WeakRef::create(m_pythonSelf, DynamicSlotDataV2::onCallbackDestroyed, this); + + m_hash = QByteArray::number((qlonglong)PyObject_Hash(m_callback)) + + QByteArray::number((qlonglong)PyObject_Hash(m_pythonSelf)); + + } else { + m_callback = callback; + Py_INCREF(m_callback); + + m_hash = QByteArray::number((qlonglong)PyObject_Hash(m_callback)); + } +} + +QByteArray DynamicSlotDataV2::hash() const +{ + return m_hash; +} + +QByteArray DynamicSlotDataV2::hash(PyObject *callback) +{ + Shiboken::GilState gil; + if (PyMethod_Check(callback)) { + return QByteArray::number((qlonglong)PyObject_Hash(PyMethod_GET_FUNCTION(callback))) + + QByteArray::number((qlonglong)PyObject_Hash(PyMethod_GET_SELF(callback))); + } + return QByteArray::number(qlonglong(PyObject_Hash(callback))); +} + +PyObject *DynamicSlotDataV2::callback() +{ + PyObject *callback = m_callback; + + //create a callback based on method data + if (m_isMethod) + callback = PyMethod_New(m_callback, m_pythonSelf); + else + Py_INCREF(callback); + + return callback; +} + +int DynamicSlotDataV2::id(const char *signature) const +{ + const auto it = m_signatures.constFind(signature); + return it != m_signatures.cend() ? it.value() : -1; +} + +int DynamicSlotDataV2::addSlot(const char *signature) +{ + int index = id(signature); + if (index == -1) + index = m_signatures[signature] = m_parent->metaObjectBuilder().addSlot(signature); + return index; +} + +void DynamicSlotDataV2::onCallbackDestroyed(void *data) +{ + auto self = reinterpret_cast<DynamicSlotDataV2 *>(data); + self->m_weakRef = 0; + Py_BEGIN_ALLOW_THREADS + delete self->m_parent; + Py_END_ALLOW_THREADS +} + +DynamicSlotDataV2::~DynamicSlotDataV2() +{ + Shiboken::GilState gil; + + Py_XDECREF(m_weakRef); + m_weakRef = 0; + + if (!m_isMethod) + Py_DECREF(m_callback); +} + +GlobalReceiverV2::GlobalReceiverV2(PyObject *callback, SharedMap map) : + QObject(nullptr), + m_metaObject("__GlobalReceiver__", &QObject::staticMetaObject), + m_sharedMap(std::move(map)) +{ + 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->hash()); + // 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. + // The signal has to be suppressed because it would lead to the following situation: + // Callback is deleted, hence the last reference is decremented, + // 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; + delete data; +} + +int GlobalReceiverV2::addSlot(const char *signature) +{ + return m_data->addSlot(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); + } +} + +void GlobalReceiverV2::decRef(const QObject *link) +{ + if (m_refs.empty()) + return; + + + 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 + +} + +int GlobalReceiverV2::refCount(const QObject *link) const +{ + if (link) + return m_refs.count(link); + + return m_refs.size(); +} + +void GlobalReceiverV2::notify() +{ + 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 +} + +QByteArray GlobalReceiverV2::hash() const +{ + return m_data->hash(); +} + +QByteArray GlobalReceiverV2::hash(PyObject *callback) +{ + return DynamicSlotDataV2::hash(callback); +} + +const QMetaObject *GlobalReceiverV2::metaObject() const +{ + return const_cast<GlobalReceiverV2 *>(this)->m_metaObject.update(); +} + +int GlobalReceiverV2::qt_metacall(QMetaObject::Call call, int id, void **args) +{ + Shiboken::GilState gil; + Q_ASSERT(call == QMetaObject::InvokeMetaMethod); + Q_ASSERT(id >= QObject::staticMetaObject.methodCount()); + + QMetaMethod slot = metaObject()->method(id); + 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); + } + 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); + } + + // SignalManager::callPythonMetaMethod might have failed, in that case we have to print the + // error so it considered "handled". + if (PyErr_Occurred()) { + 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); + } + + return -1; +} diff --git a/sources/pyside6/libpyside/globalreceiverv2.h b/sources/pyside6/libpyside/globalreceiverv2.h new file mode 100644 index 000000000..433f587a9 --- /dev/null +++ b/sources/pyside6/libpyside/globalreceiverv2.h @@ -0,0 +1,150 @@ +/**************************************************************************** +** +** 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 GLOBALRECEIVER_V2_H +#define GLOBALRECEIVER_V2_H + +#include <sbkpython.h> + +#include "dynamicqmetaobject.h" + +#include <QtCore/QByteArray> +#include <QtCore/QObject> +#include <QtCore/QMap> +#include <QtCore/QSharedPointer> + +namespace PySide +{ + +class DynamicSlotDataV2; +class GlobalReceiverV2; + +typedef QMap<QByteArray, GlobalReceiverV2 *> GlobalReceiverV2Map; +typedef QSharedPointer<GlobalReceiverV2Map> SharedMap; + +/** + * A class used to make the link between the C++ Signal/Slot and Python callback + * This class is used internally by SignalManager + **/ + +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, SharedMap map); + + /** + * Destructor + **/ + ~GlobalReceiverV2() override; + + /** + * 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 + **/ + int addSlot(const char *signature); + + /** + * 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 + **/ + QByteArray hash() 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 + **/ + static QByteArray hash(PyObject *callback); + + const MetaObjectBuilder &metaObjectBuilder() const { return m_metaObject; } + MetaObjectBuilder &metaObjectBuilder() { return m_metaObject; } + +private: + MetaObjectBuilder m_metaObject; + DynamicSlotDataV2 *m_data; + QList<const QObject *> m_refs; + SharedMap m_sharedMap; +}; + +} + +#endif diff --git a/sources/pyside6/libpyside/pyside.cpp b/sources/pyside6/libpyside/pyside.cpp new file mode 100644 index 000000000..3eca161fd --- /dev/null +++ b/sources/pyside6/libpyside/pyside.cpp @@ -0,0 +1,616 @@ +/**************************************************************************** +** +** 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 "pyside.h" +#include "pyside_p.h" +#include "signalmanager.h" +#include "pysideclassinfo_p.h" +#include "pysideproperty_p.h" +#include "pysideproperty.h" +#include "pysidesignal.h" +#include "pysidesignal_p.h" +#include "pysidestaticstrings.h" +#include "pysideslot_p.h" +#include "pysidemetafunction_p.h" +#include "pysidemetafunction.h" +#include "dynamicqmetaobject.h" + +#include <autodecref.h> +#include <basewrapper.h> +#include <bindingmanager.h> +#include <gilstate.h> +#include <sbkconverter.h> +#include <sbkstring.h> +#include <sbkstaticstrings.h> +#include <qapp_macro.h> + +#include <QtCore/QByteArray> +#include <QtCore/QCoreApplication> +#include <QtCore/QDir> +#include <QtCore/QFileInfo> +#include <QtCore/QSharedPointer> +#include <QtCore/QStack> + +#include <algorithm> +#include <cstring> +#include <cctype> +#include <typeinfo> + +static QStack<PySide::CleanupFunction> cleanupFunctionList; +static void *qobjectNextAddr; + +QT_BEGIN_NAMESPACE +extern bool qRegisterResourceData(int, const unsigned char *, const unsigned char *, + const unsigned char *); +QT_END_NAMESPACE + +namespace PySide +{ + +void init(PyObject *module) +{ + qobjectNextAddr = 0; + ClassInfo::init(module); + Signal::init(module); + Slot::init(module); + Property::init(module); + MetaFunction::init(module); + // Init signal manager, so it will register some meta types used by QVariant. + SignalManager::instance(); + initQApp(); +} + +static bool _setProperty(PyObject *qObj, PyObject *name, PyObject *value, bool *accept) +{ + QByteArray propName(Shiboken::String::toCString(name)); + propName[0] = std::toupper(propName[0]); + propName.prepend("set"); + + Shiboken::AutoDecRef propSetter(PyObject_GetAttrString(qObj, propName.constData())); + if (!propSetter.isNull()) { + *accept = true; + Shiboken::AutoDecRef args(PyTuple_Pack(1, value)); + Shiboken::AutoDecRef retval(PyObject_CallObject(propSetter, args)); + if (retval.isNull()) + return false; + } else { + PyErr_Clear(); + Shiboken::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) + return false; + } + } + return true; +} + +bool fillQtProperties(PyObject *qObj, const QMetaObject *metaObj, PyObject *kwds, const char **blackList, unsigned int blackListSize) +{ + + PyObject *key, *value; + Py_ssize_t pos = 0; + + while (PyDict_Next(kwds, &pos, &key, &value)) { + if (!blackListSize || !std::binary_search(blackList, blackList + blackListSize, std::string(Shiboken::String::toCString(key)))) { + QByteArray propName(Shiboken::String::toCString(key)); + 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)) + return false; + } + } + if (!accept) { + // PYSIDE-1019: Allow any existing attribute in the constructor. + if (!_setProperty(qObj, key, value, &accept)) + return false; + } + if (!accept) { + PyErr_Format(PyExc_AttributeError, "'%s' is not a Qt property or a signal", + propName.constData()); + return false; + } + } + } + return true; +} + +void registerCleanupFunction(CleanupFunction func) +{ + cleanupFunctionList.push(func); +} + +void runCleanupFunctions() +{ + while (!cleanupFunctionList.isEmpty()) { + CleanupFunction f = cleanupFunctionList.pop(); + f(); + } +} + +static void destructionVisitor(SbkObject *pyObj, void *data) +{ + auto realData = reinterpret_cast<void **>(data); + auto pyQApp = reinterpret_cast<SbkObject *>(realData[0]); + auto pyQObjectType = reinterpret_cast<PyTypeObject *>(realData[1]); + + if (pyObj != pyQApp && PyObject_TypeCheck(pyObj, pyQObjectType)) { + if (Shiboken::Object::hasOwnership(pyObj) && Shiboken::Object::isValid(pyObj, false)) { + Shiboken::Object::setValidCpp(pyObj, false); + + Py_BEGIN_ALLOW_THREADS + Shiboken::callCppDestructor<QObject>(Shiboken::Object::cppPointer(pyObj, pyQObjectType)); + Py_END_ALLOW_THREADS + } + } + +}; + +void destroyQCoreApplication() +{ + QCoreApplication *app = QCoreApplication::instance(); + if (!app) + return; + SignalManager::instance().clear(); + + Shiboken::BindingManager &bm = Shiboken::BindingManager::instance(); + SbkObject *pyQApp = bm.retrieveWrapper(app); + PyTypeObject *pyQObjectType = Shiboken::Conversions::getPythonTypeObject("QObject*"); + assert(pyQObjectType); + + void *data[2] = {pyQApp, pyQObjectType}; + bm.visitAllPyObjects(&destructionVisitor, &data); + + // in the end destroy app + // Allow threads because the destructor calls + // QThreadPool::globalInstance().waitForDone() which may deadlock on the GIL + // if there is a worker working with python objects. + Py_BEGIN_ALLOW_THREADS + delete app; + Py_END_ALLOW_THREADS + // PYSIDE-571: make sure to create a singleton deleted qApp. + Py_DECREF(MakeQAppWrapper(nullptr)); +} + +std::size_t getSizeOfQObject(SbkObjectType *type) +{ + return retrieveTypeUserData(type)->cppObjSize; +} + +void initDynamicMetaObject(SbkObjectType *type, const QMetaObject *base, std::size_t cppObjSize) +{ + //create DynamicMetaObject based on python type + auto userData = new TypeUserData(reinterpret_cast<PyTypeObject *>(type), base, cppObjSize); + userData->mo.update(); + Shiboken::ObjectType::setTypeUserData(type, userData, Shiboken::callCppDestructor<TypeUserData>); + + //initialize staticQMetaObject property + void *metaObjectPtr = const_cast<QMetaObject *>(userData->mo.update()); + static SbkConverter *converter = Shiboken::Conversions::getConverter("QMetaObject"); + if (!converter) + 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)); +} + +TypeUserData *retrieveTypeUserData(PyTypeObject *pyTypeObj) +{ + return retrieveTypeUserData(reinterpret_cast<SbkObjectType *>(pyTypeObj)); +} + +TypeUserData *retrieveTypeUserData(PyObject *pyObj) +{ + auto pyTypeObj = PyType_Check(pyObj) + ? reinterpret_cast<PyTypeObject *>(pyObj) : Py_TYPE(pyObj); + return retrieveTypeUserData(pyTypeObj); +} + +const QMetaObject *retrieveMetaObject(PyTypeObject *pyTypeObj) +{ + TypeUserData *userData = retrieveTypeUserData(pyTypeObj); + return userData ? userData->mo.update() : nullptr; +} + +const QMetaObject *retrieveMetaObject(PyObject *pyObj) +{ + auto pyTypeObj = PyType_Check(pyObj) + ? reinterpret_cast<PyTypeObject *>(pyObj) : Py_TYPE(pyObj); + return retrieveMetaObject(pyTypeObj); +} + +void initQObjectSubType(SbkObjectType *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); + + TypeUserData *userData = nullptr; + + for (int i = 0; i < numBases; ++i) { + auto base = reinterpret_cast<PyTypeObject *>(PyTuple_GET_ITEM(bases, i)); + if (PyType_IsSubtype(base, qObjType)) { + userData = retrieveTypeUserData(base); + break; + } + } + if (!userData) { + qWarning("Sub class of QObject not inheriting QObject!? Crash will happen when using %s.", className.constData()); + return; + } + initDynamicMetaObject(type, userData->mo.update(), userData->cppObjSize); +} + +void initQApp() +{ + /* + * qApp will not be initialized when embedding is active. + * That means that qApp exists already when PySide is initialized. + * We could solve that by creating a qApp variable, but in embedded + * mode, we also have the effect that the first assignment to qApp + * is persistent! Therefore, we can never be sure to have created + * qApp late enough to get the right type for the instance. + * + * I would appreciate very much if someone could explain or even fix + * this issue. It exists only when a pre-existing application exists. + */ + if (!qApp) + Py_DECREF(MakeQAppWrapper(nullptr)); +} + +PyObject *getMetaDataFromQObject(QObject *cppSelf, PyObject *self, PyObject *name) +{ + PyObject *attr = PyObject_GenericGetAttr(self, name); + if (!Shiboken::Object::isValid(reinterpret_cast<SbkObject *>(self), false)) + return attr; + + if (attr && Property::checkType(attr)) { + PyObject *value = Property::getValue(reinterpret_cast<PySideProperty *>(attr), self); + Py_DECREF(attr); + if (!value) + return 0; + 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 '__') + if (!attr) { + const char *cname = Shiboken::String::toCString(name); + uint cnameLen = qstrlen(cname); + if (std::strncmp("__", cname, 2)) { + const QMetaObject *metaObject = cppSelf->metaObject(); + //signal + QList<QMetaMethod> signalList; + for(int i=0, i_max = metaObject->methodCount(); i < i_max; i++) { + QMetaMethod method = metaObject->method(i); + const QByteArray methSig_ = method.methodSignature(); + const char *methSig = methSig_.constData(); + bool methMacth = !std::strncmp(cname, methSig, cnameLen) && methSig[cnameLen] == '('; + if (methMacth) { + if (method.methodType() == QMetaMethod::Signal) { + signalList.append(method); + } else { + PySideMetaFunction *func = MetaFunction::newObject(cppSelf, i); + if (func) { + PyObject *result = reinterpret_cast<PyObject *>(func); + PyObject_SetAttr(self, name, result); + return result; + } + } + } + } + if (!signalList.empty()) { + PyObject *pySignal = reinterpret_cast<PyObject *>(Signal::newObjectFromMethod(self, signalList)); + PyObject_SetAttr(self, name, pySignal); + return pySignal; + } + } + } + return attr; +} + +bool inherits(PyTypeObject *objType, const char *class_name) +{ + if (strcmp(objType->tp_name, class_name) == 0) + return true; + + PyTypeObject *base = objType->tp_base; + if (base == 0) + return false; + + return inherits(base, class_name); +} + +void *nextQObjectMemoryAddr() +{ + return qobjectNextAddr; +} + +void setNextQObjectMemoryAddr(void *addr) +{ + qobjectNextAddr = addr; +} + +} // namespace PySide + +// A QSharedPointer 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>); + +namespace PySide +{ + +static void invalidatePtr(any_t *object) +{ + Shiboken::GilState state; + + SbkObject *wrapper = Shiboken::BindingManager::instance().retrieveWrapper(object); + if (wrapper != NULL) + Shiboken::BindingManager::instance().releaseWrapper(wrapper); +} + +static const char invalidatePropertyName[] = "_PySideInvalidatePtr"; + +// 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) +{ + const char *typeName = typeid(*cppSelf).name(); + if (!Shiboken::Conversions::getConverter(typeName)) { + for (auto metaObject = cppSelf->metaObject(); metaObject; metaObject = metaObject->superClass()) { + const char *name = metaObject->className(); + if (Shiboken::Conversions::getConverter(name)) { + typeName = name; + break; + } + } + } + return typeName; +} + +PyObject *getWrapperForQObject(QObject *cppSelf, SbkObjectType *sbk_type) +{ + PyObject *pyOut = reinterpret_cast<PyObject *>(Shiboken::BindingManager::instance().retrieveWrapper(cppSelf)); + if (pyOut) { + Py_INCREF(pyOut); + return pyOut; + } + + // Setting the property will trigger an QEvent notification, which may call into + // code that creates the wrapper so only set the property if it isn't already + // 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)); + pyOut = reinterpret_cast<PyObject *>(Shiboken::BindingManager::instance().retrieveWrapper(cppSelf)); + if (pyOut) { + Py_INCREF(pyOut); + return pyOut; + } + } + + pyOut = Shiboken::Object::newObject(sbk_type, cppSelf, false, false, typeName(cppSelf)); + + return pyOut; +} + +#ifdef PYSIDE_QML_SUPPORT +static QuickRegisterItemFunction quickRegisterItem; + +QuickRegisterItemFunction getQuickRegisterItemFunction() +{ + return quickRegisterItem; +} + +void setQuickRegisterItemFunction(QuickRegisterItemFunction function) +{ + quickRegisterItem = function; +} +#endif // PYSIDE_QML_SUPPORT + +// Inspired by Shiboken::String::toCString; +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 (PyBytes_Check(str)) { + const char *asciiBuffer = PyBytes_AS_STRING(str); + if (asciiBuffer) + return QString::fromLatin1(asciiBuffer); + } + return QString(); +} + +static const unsigned char qt_resource_name[] = { + // qt + 0x0,0x2, + 0x0,0x0,0x7,0x84, + 0x0,0x71, + 0x0,0x74, + // etc + 0x0,0x3, + 0x0,0x0,0x6c,0xa3, + 0x0,0x65, + 0x0,0x74,0x0,0x63, + // qt.conf + 0x0,0x7, + 0x8,0x74,0xa6,0xa6, + 0x0,0x71, + 0x0,0x74,0x0,0x2e,0x0,0x63,0x0,0x6f,0x0,0x6e,0x0,0x66 +}; + +static const unsigned char qt_resource_struct[] = { + // : + 0x0,0x0,0x0,0x0,0x0,0x2,0x0,0x0,0x0,0x1,0x0,0x0,0x0,0x1, + // :/qt + 0x0,0x0,0x0,0x0,0x0,0x2,0x0,0x0,0x0,0x1,0x0,0x0,0x0,0x2, + // :/qt/etc + 0x0,0x0,0x0,0xa,0x0,0x2,0x0,0x0,0x0,0x1,0x0,0x0,0x0,0x3, + // :/qt/etc/qt.conf + 0x0,0x0,0x0,0x16,0x0,0x0,0x0,0x0,0x0,0x1,0x0,0x0,0x0,0x0 +}; + +bool registerInternalQtConf() +{ + // Guard to ensure single registration. +#ifdef PYSIDE_QT_CONF_PREFIX + static bool registrationAttempted = false; +#else + static bool registrationAttempted = true; +#endif + static bool isRegistered = false; + if (registrationAttempted) + return isRegistered; + registrationAttempted = true; + + // Support PyInstaller case when a qt.conf file might be provided next to the generated + // 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()); + QString appDirPath = QFileInfo(executablePath).absolutePath(); + QString maybeQtConfPath = QDir(appDirPath).filePath(QStringLiteral("qt.conf")); + bool executableQtConfAvailable = QFileInfo::exists(maybeQtConfPath); + maybeQtConfPath = QDir::toNativeSeparators(maybeQtConfPath); + + // 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) { + registrationAttempted = true; + return false; + } + + PyObject *pysideModule = PyImport_ImportModule("PySide6"); + if (!pysideModule) + return false; + + // Querying __file__ should be done only for modules that have finished their initialization. + // Thus querying for the top-level PySide6 package works for us whenever any Qt-wrapped module + // is loaded. + PyObject *pysideInitFilePath = PyObject_GetAttr(pysideModule, Shiboken::PyMagicName::file()); + Py_DECREF(pysideModule); + if (!pysideInitFilePath) + return false; + + QString initPath = pyStringToQString(pysideInitFilePath); + Py_DECREF(pysideInitFilePath); + if (initPath.isEmpty()) + return false; + + // pysideDir - absolute path to the directory containing the init file, which also contains + // the rest of the PySide6 modules. + // prefixPath - absolute path to the directory containing the installed Qt (prefix). + QDir pysideDir = QFileInfo(QDir::fromNativeSeparators(initPath)).absoluteDir(); + QString setupPrefix; +#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 + + // 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 +#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 +#endif + ; + rccData.append('\n'); + + // The RCC data structure expects a 4-byte size value representing the actual data. + int size = rccData.size(); + + for (int i = 0; i < 4; ++i) { + rccData.prepend((size & 0xff)); + size >>= 8; + } + + const int version = 0x01; + isRegistered = qRegisterResourceData(version, qt_resource_struct, qt_resource_name, + reinterpret_cast<const unsigned char *>( + rccData.constData())); + + return isRegistered; +} + + + +} //namespace PySide + diff --git a/sources/pyside6/libpyside/pyside.h b/sources/pyside6/libpyside/pyside.h new file mode 100644 index 000000000..e241ac74d --- /dev/null +++ b/sources/pyside6/libpyside/pyside.h @@ -0,0 +1,173 @@ +/**************************************************************************** +** +** 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_H +#define PYSIDE_H + +#include <sbkpython.h> + +#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 blackList keys to be ignored in kwds dictionary, this string list MUST be sorted. + * \param blackListSize numbe rof elements in blackList. + * \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, const char **blackList, unsigned int blackListSize); + +/** +* 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 + + +#endif // PYSIDE_H diff --git a/sources/pyside6/libpyside/pyside6.pc.in b/sources/pyside6/libpyside/pyside6.pc.in new file mode 100644 index 000000000..394372539 --- /dev/null +++ b/sources/pyside6/libpyside/pyside6.pc.in @@ -0,0 +1,15 @@ +prefix=@CMAKE_INSTALL_PREFIX@ +exec_prefix=@CMAKE_INSTALL_PREFIX@ +libdir=@LIB_INSTALL_DIR@ +includedir=@CMAKE_INSTALL_PREFIX@/include/PySide6@pyside6_SUFFIX@ +typesystemdir=@CMAKE_INSTALL_PREFIX@/share/PySide6@pyside6_SUFFIX@/typesystems +gluedir=@CMAKE_INSTALL_PREFIX@/share/PySide6@pyside6_SUFFIX@/glue +pythonpath=@PYTHON_SITE_PACKAGES@ + +Name: PySide6@pyside6_SUFFIX@ +Description: Support library for Python bindings of Qt5-based libraries. +Version: @BINDING_API_VERSION_FULL@ +Libs: -L${libdir} -lpyside6@pyside6_SUFFIX@@SHIBOKEN_PYTHON_CONFIG_SUFFIX@@LIBRARY_OUTPUT_SUFFIX@ +Cflags: -I${includedir} +Requires: shiboken6 + diff --git a/sources/pyside6/libpyside/pyside_p.h b/sources/pyside6/libpyside/pyside_p.h new file mode 100644 index 000000000..1084a40a1 --- /dev/null +++ b/sources/pyside6/libpyside/pyside_p.h @@ -0,0 +1,71 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef PYSIDE_P_H +#define PYSIDE_P_H + +#include <pysidemacros.h> + +#include <dynamicqmetaobject.h> + +struct SbkObjectType; + +namespace PySide +{ + +// Struct associated with QObject's via Shiboken::Object::getTypeUserData() +struct TypeUserData +{ + explicit TypeUserData(PyTypeObject* type, const QMetaObject* metaobject, std::size_t size) : + mo(type, metaobject), cppObjSize(size) {} + + MetaObjectBuilder mo; + std::size_t cppObjSize; +}; + +TypeUserData *retrieveTypeUserData(SbkObjectType *sbkTypeObj); +TypeUserData *retrieveTypeUserData(PyTypeObject *pyTypeObj); +TypeUserData *retrieveTypeUserData(PyObject *pyObj); +// For QML +PYSIDE_API const QMetaObject *retrieveMetaObject(PyTypeObject *pyTypeObj); +PYSIDE_API const QMetaObject *retrieveMetaObject(PyObject *pyObj); + +} //namespace PySide + +#endif // PYSIDE_P_H diff --git a/sources/pyside6/libpyside/pysideclassinfo.cpp b/sources/pyside6/libpyside/pysideclassinfo.cpp new file mode 100644 index 000000000..67334f2f4 --- /dev/null +++ b/sources/pyside6/libpyside/pysideclassinfo.cpp @@ -0,0 +1,207 @@ +/**************************************************************************** +** +** 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 <sbkpython.h> + +#include "pysideclassinfo.h" +#include "pyside_p.h" +#include "pysideclassinfo_p.h" +#include "dynamicqmetaobject.h" + +#include <shiboken.h> +#include <signature.h> + +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, +}; + + +PyTypeObject *PySideClassInfoTypeF(void) +{ + static PyTypeObject *type = + reinterpret_cast<PyTypeObject *>(SbkType_FromSpec(&PySideClassInfoType_spec)); + 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; + } + + PySideClassInfo *data = reinterpret_cast<PySideClassInfo *>(self); + PySideClassInfoPrivate *pData = data->d; + + if (pData->m_alreadyWrapped) { + PyErr_SetString(PyExc_TypeError, "This instance of ClassInfo() was already used to wrap an object"); + return 0; + } + + PyObject *klass = PyTuple_GetItem(args, 0); + bool validClass = false; + + // 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; + } + + 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 (!validClass) { + PyErr_SetString(PyExc_TypeError, "This decorator can only be used on classes that are subclasses of QObject"); + return 0; + } + + Py_INCREF(klass); + return klass; +} + +static PyObject *classInfoTpNew(PyTypeObject *subtype, 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); +} + +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)); + return -1; + } + + PySideClassInfo *data = reinterpret_cast<PySideClassInfo *>(self); + PySideClassInfoPrivate *pData = data->d; + + 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 (Shiboken::String::check(key) && Shiboken::String::check(value)) { + pData->m_data[Shiboken::String::toCString(key)] = Shiboken::String::toCString(value); + } else { + 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); +} + + +} // extern "C" + + +namespace PySide { namespace ClassInfo { + +static const char *ClassInfo_SignatureStrings[] = { + "PySide6.QtCore.ClassInfo(**info:typing.Dict[str,str])", + nullptr}; // Sentinel + +void init(PyObject *module) +{ + if (InitSignatureStrings(PySideClassInfoTypeF(), ClassInfo_SignatureStrings) < 0) + return; + + Py_INCREF(PySideClassInfoTypeF()); + PyModule_AddObject(module, "ClassInfo", reinterpret_cast<PyObject *>(PySideClassInfoTypeF())); +} + +bool checkType(PyObject *pyObj) +{ + if (pyObj) + return PyType_IsSubtype(Py_TYPE(pyObj), PySideClassInfoTypeF()); + return false; +} + +QMap<QByteArray, QByteArray> getMap(PySideClassInfo *obj) +{ + return obj->d->m_data; +} + +} //namespace Property +} //namespace PySide diff --git a/sources/pyside6/libpyside/pysideclassinfo.h b/sources/pyside6/libpyside/pysideclassinfo.h new file mode 100644 index 000000000..ff60b91c3 --- /dev/null +++ b/sources/pyside6/libpyside/pysideclassinfo.h @@ -0,0 +1,70 @@ +/**************************************************************************** +** +** 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_CLASSINFO_H +#define PYSIDE_CLASSINFO_H + +#include <pysidemacros.h> + +#include <sbkpython.h> + +#include <QtCore/QMap> +#include <QtCore/QByteArray> + +extern "C" +{ + extern PYSIDE_API PyTypeObject *PySideClassInfoTypeF(void); + + struct PySideClassInfoPrivate; + struct PYSIDE_API PySideClassInfo + { + PyObject_HEAD + PySideClassInfoPrivate* d; + }; +}; + +namespace PySide { namespace ClassInfo { + +PYSIDE_API bool checkType(PyObject* pyObj); +PYSIDE_API QMap<QByteArray, QByteArray> getMap(PySideClassInfo* obj); + +} //namespace ClassInfo +} //namespace PySide + +#endif diff --git a/sources/pyside6/libpyside/pysideclassinfo_p.h b/sources/pyside6/libpyside/pysideclassinfo_p.h new file mode 100644 index 000000000..021aa58e9 --- /dev/null +++ b/sources/pyside6/libpyside/pysideclassinfo_p.h @@ -0,0 +1,70 @@ +/**************************************************************************** +** +** 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_CLASSINFO_P_H +#define PYSIDE_CLASSINFO_P_H + +#include <sbkpython.h> +#include <QMetaObject> +#include "pysideclassinfo.h" + +struct PySideClassInfo; + +extern "C" +{ + +struct PySideClassInfoPrivate { + QMap<QByteArray, QByteArray> m_data; + bool m_alreadyWrapped; +}; + +} // extern "C" + +namespace PySide { namespace ClassInfo { + +/** + * Init PySide QProperty support system + */ +void init(PyObject* module); + + +} // namespace ClassInfo +} // namespace PySide + +#endif diff --git a/sources/pyside6/libpyside/pysidemacros.h b/sources/pyside6/libpyside/pysidemacros.h new file mode 100644 index 000000000..fcdfe3c6e --- /dev/null +++ b/sources/pyside6/libpyside/pysidemacros.h @@ -0,0 +1,55 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef PYSIDEMACROS_H +#define PYSIDEMACROS_H + +#include <shibokenmacros.h> + +#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 +#else +# define PYSIDE_API PYSIDE_IMPORT +#endif + +#endif // PYSIDEMACROS_H diff --git a/sources/pyside6/libpyside/pysidemetafunction.cpp b/sources/pyside6/libpyside/pysidemetafunction.cpp new file mode 100644 index 000000000..6d93e7629 --- /dev/null +++ b/sources/pyside6/libpyside/pysidemetafunction.cpp @@ -0,0 +1,231 @@ +/**************************************************************************** +** +** 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 "pysidemetafunction.h" +#include "pysidemetafunction_p.h" + +#include <shiboken.h> +#include <signature.h> + +#include <QtCore/QMetaMethod> + +extern "C" +{ + +struct PySideMetaFunctionPrivate +{ + QObject *qobject; + int methodIndex; +}; + +//methods +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, +}; + + +PyTypeObject *PySideMetaFunctionTypeF(void) +{ + static PyTypeObject *type = reinterpret_cast<PyTypeObject *>( + SbkType_FromSpec(&PySideMetaFunctionType_spec)); + return type; +} + +void functionFree(void *self) +{ + PySideMetaFunction *function = reinterpret_cast<PySideMetaFunction *>(self); + delete function->d; +} + +PyObject *functionCall(PyObject *self, PyObject *args, PyObject * /* kw */) +{ + PySideMetaFunction *function = reinterpret_cast<PySideMetaFunction *>(self); + + PyObject *retVal; + if (!PySide::MetaFunction::call(function->d->qobject, function->d->methodIndex, args, &retVal)) + return 0; + return retVal; +} + +} // extern "C" + +namespace PySide { namespace MetaFunction { + +static const char *MetaFunction_SignatureStrings[] = { + "PySide6.QtCore.MetaFunction.__call__(*args:typing.Any)->typing.Any", + nullptr}; // Sentinel + +void init(PyObject *module) +{ + if (InitSignatureStrings(PySideMetaFunctionTypeF(), MetaFunction_SignatureStrings) < 0) + return; + + Py_INCREF(PySideMetaFunctionTypeF()); + PyModule_AddObject(module, "MetaFunction", reinterpret_cast<PyObject *>(PySideMetaFunctionTypeF())); +} + +PySideMetaFunction *newObject(QObject *source, int methodIndex) +{ + if (methodIndex >= source->metaObject()->methodCount()) + return 0; + + QMetaMethod method = source->metaObject()->method(methodIndex); + if ((method.methodType() == QMetaMethod::Slot) || + (method.methodType() == QMetaMethod::Method)) { + PySideMetaFunction *function = PyObject_New(PySideMetaFunction, PySideMetaFunctionTypeF()); + function->d = new PySideMetaFunctionPrivate(); + function->d->qobject = source; + function->d->methodIndex = methodIndex; + return function; + } + return 0; +} + +bool call(QObject *self, int methodIndex, PyObject *args, PyObject **retVal) +{ + + QMetaMethod method = self->metaObject()->method(methodIndex); + 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; + + if (numArgs - 1 > argTypes.count()) { + PyErr_Format(PyExc_TypeError, "%s only accepts %d argument(s), %d given!", + method.methodSignature().constData(), + argTypes.count(), numArgs - 1); + return false; + } + + if (numArgs - 1 < argTypes.count()) { + PyErr_Format(PyExc_TypeError, "%s needs %d argument(s), %d given!", + method.methodSignature().constData(), + argTypes.count(), numArgs - 1); + return false; + } + + QVariant *methValues = new QVariant[numArgs]; + void **methArgs = new void *[numArgs]; + + // Prepare room for return type + const char *returnType = method.typeName(); + if (returnType && std::strcmp("void", returnType)) + argTypes.prepend(returnType); + else + argTypes.prepend(QByteArray()); + + int i; + for (i = 0; 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; + continue; + } + + Shiboken::Conversions::SpecificConverter converter(typeName); + if (converter) { + QMetaType metaType = QMetaType::fromName(typeName); + if (!Shiboken::Conversions::pythonTypeIsObjectType(converter)) { + if (!metaType.isValid()) { + PyErr_Format(PyExc_TypeError, "Value types used on meta functions (including signals) need to be " + "registered on meta type: %s", typeName.data()); + break; + } + methValues[i] = QVariant(metaType); + } + methArgs[i] = methValues[i].data(); + if (i == 0) // Don't do this for return type + continue; + if (metaType.id() == QMetaType::QString) { + QString tmp; + converter.toCpp(PySequence_Fast_GET_ITEM(sequence.object(), i - 1), &tmp); + methValues[i] = tmp; + } else { + converter.toCpp(PySequence_Fast_GET_ITEM(sequence.object(), i - 1), methArgs[i]); + } + } else { + PyErr_Format(PyExc_TypeError, "Unknown type used to call meta function (that may be a signal): %s", argTypes[i].constData()); + break; + } + } + + bool ok = i == numArgs; + if (ok) { + Py_BEGIN_ALLOW_THREADS + QMetaObject::metacall(self, QMetaObject::InvokeMetaMethod, method.methodIndex(), methArgs); + Py_END_ALLOW_THREADS + + if (retVal) { + if (methArgs[0]) { + static SbkConverter *qVariantTypeConverter = Shiboken::Conversions::getConverter("QVariant"); + Q_ASSERT(qVariantTypeConverter); + *retVal = Shiboken::Conversions::copyToPython(qVariantTypeConverter, &methValues[0]); + } else { + *retVal = Py_None; + Py_INCREF(*retVal); + } + } + } + + delete[] methArgs; + delete[] methValues; + + return ok; +} + + +} //namespace MetaFunction +} //namespace PySide + diff --git a/sources/pyside6/libpyside/pysidemetafunction.h b/sources/pyside6/libpyside/pysidemetafunction.h new file mode 100644 index 000000000..f7cc5307b --- /dev/null +++ b/sources/pyside6/libpyside/pysidemetafunction.h @@ -0,0 +1,75 @@ +/**************************************************************************** +** +** 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_METAFUNCTION_H +#define PYSIDE_METAFUNCTION_H + +#include <pysidemacros.h> + +#include <sbkpython.h> + +#include <QtCore/QObject> + +extern "C" +{ + extern PYSIDE_API PyTypeObject *PySideMetaFunctionTypeF(void); + + struct PySideMetaFunctionPrivate; + struct PYSIDE_API PySideMetaFunction + { + PyObject_HEAD + PySideMetaFunctionPrivate *d; + }; +}; //extern "C" + +namespace PySide { namespace MetaFunction { + +/** + * This function creates a MetaFunction object + * + * @param obj the QObject witch this fuction is part of + * @param methodIndex The index of this function on MetaObject + * @return Return a new reference of PySideMetaFunction + **/ +PYSIDE_API PySideMetaFunction *newObject(QObject *obj, int methodIndex); + +} //namespace MetaFunction +} //namespace PySide + +#endif diff --git a/sources/pyside6/libpyside/pysidemetafunction_p.h b/sources/pyside6/libpyside/pysidemetafunction_p.h new file mode 100644 index 000000000..c67233857 --- /dev/null +++ b/sources/pyside6/libpyside/pysidemetafunction_p.h @@ -0,0 +1,62 @@ +/**************************************************************************** +** +** 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_METAFUNCTION_P_H +#define PYSIDE_METAFUNCTION_P_H + +#include <sbkpython.h> + +#include <QtCore/QtGlobal> + +QT_BEGIN_NAMESPACE +class QObject; +QT_END_NAMESPACE + +namespace PySide { namespace MetaFunction { + + void init(PyObject *module); + /** + * Does a Qt metacall on a QObject + */ + bool call(QObject *self, int methodIndex, PyObject *args, PyObject **retVal = nullptr); + +} //namespace MetaFunction +} //namespace PySide + +#endif diff --git a/sources/pyside6/libpyside/pysideproperty.cpp b/sources/pyside6/libpyside/pysideproperty.cpp new file mode 100644 index 000000000..b031c5b12 --- /dev/null +++ b/sources/pyside6/libpyside/pysideproperty.cpp @@ -0,0 +1,634 @@ +/**************************************************************************** +** +** 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 <sbkpython.h> +#include "pysideproperty.h" +#include "pysideproperty_p.h" +#include "dynamicqmetaobject_p.h" +#include "pysidesignal.h" +#include "pysidesignal_p.h" + +#include <shiboken.h> +#include <signature.h> + +using namespace Shiboken; + +extern "C" +{ + +static PyObject *qpropertyTpNew(PyTypeObject *subtype, PyObject *args, PyObject *kwds); +static int qpropertyTpInit(PyObject *, PyObject *, PyObject *); +static void qpropertyDeAlloc(PyObject *self); + +//methods +static PyObject *qPropertyGetter(PyObject *, PyObject *); +static PyObject *qPropertySetter(PyObject *, PyObject *); +static PyObject *qPropertyResetter(PyObject *, PyObject *); +static PyObject *qPropertyDeleter(PyObject *, PyObject *); +static PyObject *qPropertyCall(PyObject *, PyObject *, PyObject *); +static int qpropertyTraverse(PyObject *self, visitproc visit, void *arg); +static int qpropertyClear(PyObject *self); + +// Attributes +static PyObject *qPropertyDocGet(PyObject *, void *); +static int qPropertyDocSet(PyObject *, PyObject *, void *); +static PyObject *qProperty_fget(PyObject *, void *); +static PyObject *qProperty_fset(PyObject *, void *); +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}, + // Synonyms from Qt + {"read", (PyCFunction)qPropertyGetter, METH_O, 0}, + {"write", (PyCFunction)qPropertySetter, METH_O, 0}, + {0, 0, 0, 0} +}; + +static PyGetSetDef PySidePropertyType_getset[] = { + // Note: we could not use `PyMemberDef` like Python's properties, + // because of the indirection of PySidePropertyPrivate. + {const_cast<char *>("fget"), qProperty_fget, nullptr, nullptr, nullptr}, + {const_cast<char *>("fset"), qProperty_fset, nullptr, nullptr, nullptr}, + {const_cast<char *>("freset"), qProperty_freset, nullptr, nullptr, nullptr}, + {const_cast<char *>("fdel"), qProperty_fdel, nullptr, nullptr, nullptr}, + {const_cast<char *>("__doc__"), qPropertyDocGet, qPropertyDocSet, nullptr, nullptr}, + {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, +}; + + +PyTypeObject *PySidePropertyTypeF(void) +{ + static PyTypeObject *type = reinterpret_cast<PyTypeObject *>( + SbkType_FromSpec(&PySidePropertyType_spec)); + return type; +} + +static void qpropertyMetaCall(PySideProperty *pp, PyObject *self, QMetaObject::Call call, void **args) +{ + Shiboken::Conversions::SpecificConverter converter(pp->d->typeName); + Q_ASSERT(converter); + + QByteArray type(pp->d->typeName); + + 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); + } + break; + } + + case QMetaObject::WriteProperty: + { + Shiboken::GilState gil; + Shiboken::AutoDecRef value(converter.toPython(args[0])); + PySide::Property::setValue(pp, self, value); + break; + } + + case QMetaObject::ResetProperty: + { + Shiboken::GilState gil; + PySide::Property::reset(pp, self); + 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; + } +} + + +static PyObject *qpropertyTpNew(PyTypeObject *subtype, PyObject * /* args */, PyObject * /* kwds */) +{ + PySideProperty *me = reinterpret_cast<PySideProperty *>(subtype->tp_alloc(subtype, 0)); + me->d = new PySidePropertyPrivate; + return reinterpret_cast<PyObject *>(me); +} + +static int qpropertyTpInit(PyObject *self, PyObject *args, PyObject *kwds) +{ + PyObject *type = nullptr; + 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}; + char *doc{}; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, + "O|OOOOsObbbbbb:QtCore.Property", + const_cast<char **>(kwlist), + /*OO*/ &type, &(pData->fget), + /*OOO*/ &(pData->fset), &(pData->freset), &(pData->fdel), + /*s*/ &doc, + /*O*/ &(pData->notify), + /*bbb*/ &(pData->designable), &(pData->scriptable), &(pData->stored), + /*bbb*/ &(pData->user), &(pData->constant), &(pData->final))) { + return -1; + } + + // PYSIDE-1019: Fetching the default `__doc__` from fget would fail for inherited functions + // because we don't initialize the mro with signatures (and we will not!). + // But it is efficient and in-time to do that on demand in qPropertyDocGet. + pData->getter_doc = false; + if (doc) + pData->doc = doc; + else + pData->doc.clear(); + + pData->typeName = PySide::Signal::getTypeName(type); + + if (pData->typeName.isEmpty()) + PyErr_SetString(PyExc_TypeError, "Invalid property type or type name."); + else if (pData->constant && (pData->fset || pData->notify)) + PyErr_SetString(PyExc_TypeError, "A constant property cannot have a WRITE method or a NOTIFY signal."); + + if (!PyErr_Occurred()) { + Py_XINCREF(pData->fget); + Py_XINCREF(pData->fset); + Py_XINCREF(pData->freset); + Py_XINCREF(pData->fdel); + Py_XINCREF(pData->notify); + return 0; + } + pData->fget = nullptr; + pData->fset = nullptr; + pData->freset = nullptr; + pData->fdel = nullptr; + pData->notify = nullptr; + return -1; +} + +static void qpropertyDeAlloc(PyObject *self) +{ + qpropertyClear(self); + if (PepRuntime_38_flag) { + // PYSIDE-939: Handling references correctly. + // This was not needed before Python 3.8 (Python issue 35810) + Py_DECREF(Py_TYPE(self)); + } + Py_TYPE(self)->tp_free(self); +} + +static PyObject * +_property_copy(PyObject *old, PyObject *get, PyObject *set, PyObject *reset, PyObject *del) +{ + PySideProperty *pold = reinterpret_cast<PySideProperty *>(old); + PySidePropertyPrivate *pData = pold->d; + + AutoDecRef type(PyObject_Type(old)); + QByteArray doc{}; + if (type.isNull()) + return nullptr; + + if (get == nullptr || get == Py_None) { + Py_XDECREF(get); + get = pData->fget ? pData->fget : Py_None; + } + if (set == nullptr || set == Py_None) { + Py_XDECREF(set); + set = pData->fset ? pData->fset : Py_None; + } + if (reset == nullptr || reset == Py_None) { + Py_XDECREF(reset); + reset = pData->freset ? pData->freset : Py_None; + } + if (del == nullptr || del == Py_None) { + Py_XDECREF(del); + del = pData->fdel ? pData->fdel : Py_None; + } + if (pData->getter_doc && get != Py_None) { + /* make _init use __doc__ from getter */ + doc = ""; + } + else { + doc = !pData->doc.isEmpty() ? pData->doc : ""; + } + 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->designable, pData->scriptable, pData->stored, + pData->user, pData->constant, pData->final); + + return obNew; +} + +static PyObject *qPropertyGetter(PyObject *self, PyObject *getter) +{ + return _property_copy(self, getter, nullptr, nullptr, nullptr); +} + +static PyObject *qPropertySetter(PyObject *self, PyObject *setter) +{ + return _property_copy(self, nullptr, setter, nullptr, nullptr); +} + +static PyObject *qPropertyResetter(PyObject *self, PyObject *resetter) +{ + return _property_copy(self, nullptr, nullptr, resetter, nullptr); +} + +static PyObject *qPropertyDeleter(PyObject *self, PyObject *deleter) +{ + return _property_copy(self, nullptr, nullptr, nullptr, deleter); +} + +static PyObject *qPropertyCall(PyObject *self, PyObject *args, PyObject * /* kw */) +{ + PyObject *getter = PyTuple_GetItem(args, 0); + return _property_copy(self, getter, nullptr, nullptr, nullptr); +} + +// PYSIDE-1019: Provide the same getters as Pythons `PyProperty`. + +static PyObject *qProperty_fget(PyObject *self, void *) +{ + auto func = reinterpret_cast<PySideProperty *>(self)->d->fget; + auto ret = func != nullptr ? func : Py_None; + Py_INCREF(ret); + return ret; +} + +static PyObject *qProperty_fset(PyObject *self, void *) +{ + auto func = reinterpret_cast<PySideProperty *>(self)->d->fset; + auto ret = func != nullptr ? func : Py_None; + Py_INCREF(ret); + return ret; +} + +static PyObject *qProperty_freset(PyObject *self, void *) +{ + auto func = reinterpret_cast<PySideProperty *>(self)->d->freset; + auto ret = func != nullptr ? func : Py_None; + Py_INCREF(ret); + return ret; +} + +static PyObject *qProperty_fdel(PyObject *self, void *) +{ + auto func = reinterpret_cast<PySideProperty *>(self)->d->fdel; + auto ret = func != nullptr ? func : Py_None; + Py_INCREF(ret); + return ret; +} + +static PyObject *qPropertyDocGet(PyObject *self, void *) +{ + auto data = reinterpret_cast<PySideProperty *>(self); + PySidePropertyPrivate *pData = data->d; + + QByteArray doc(pData->doc); + if (!doc.isEmpty()) + return PyUnicode_FromString(doc); + 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()) { + pData->doc = String::toCString(get_doc); + pData->getter_doc = true; + if (Py_TYPE(self) == PySidePropertyTypeF()) + return qPropertyDocGet(self, nullptr); + /* + * If this is a property subclass, put __doc__ in dict of the + * subclass instance instead, otherwise it gets shadowed by + * __doc__ in the class's dict. + */ + auto get_doc_obj = get_doc.object(); + int err = PyObject_SetAttr(self, PyMagicName::doc(), get_doc); + return err < 0 ? nullptr : (Py_INCREF(get_doc_obj), get_doc_obj); + } + PyErr_Clear(); + } + Py_RETURN_NONE; +} + +static int qPropertyDocSet(PyObject *self, PyObject *value, void *) +{ + auto data = reinterpret_cast<PySideProperty *>(self); + PySidePropertyPrivate *pData = data->d; + + if (String::check(value)) { + pData->doc = String::toCString(value); + return 0; + } + PyErr_SetString(PyExc_TypeError, "String argument expected."); + return -1; +} + +static int qpropertyTraverse(PyObject *self, visitproc visit, void *arg) +{ + PySidePropertyPrivate *data = reinterpret_cast<PySideProperty *>(self)->d; + if (!data) + return 0; + + Py_VISIT(data->fget); + Py_VISIT(data->fset); + Py_VISIT(data->freset); + Py_VISIT(data->fdel); + Py_VISIT(data->notify); + return 0; +} + +static int qpropertyClear(PyObject *self) +{ + PySidePropertyPrivate *data = reinterpret_cast<PySideProperty *>(self)->d; + if (!data) + return 0; + + Py_CLEAR(data->fget); + Py_CLEAR(data->fset); + Py_CLEAR(data->freset); + Py_CLEAR(data->fdel); + Py_CLEAR(data->notify); + + + delete data; + reinterpret_cast<PySideProperty *>(self)->d = nullptr; + return 0; +} + +} // extern "C" + +namespace { + +static PyObject *getFromType(PyTypeObject *type, PyObject *name) +{ + PyObject *attr = nullptr; + attr = PyDict_GetItem(type->tp_dict, name); + if (!attr) { + PyObject *bases = type->tp_bases; + int size = PyTuple_GET_SIZE(bases); + for(int i=0; i < size; i++) { + PyObject *base = PyTuple_GET_ITEM(bases, i); + attr = getFromType(reinterpret_cast<PyTypeObject *>(base), name); + if (attr) + return attr; + } + } + return attr; +} + +} //namespace + + +namespace PySide { namespace 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)", + nullptr}; // Sentinel + +void init(PyObject *module) +{ + if (InitSignatureStrings(PySidePropertyTypeF(), Property_SignatureStrings) < 0) + return; + + Py_INCREF(PySidePropertyTypeF()); + PyModule_AddObject(module, "Property", reinterpret_cast<PyObject *>(PySidePropertyTypeF())); +} + +bool checkType(PyObject *pyObj) +{ + if (pyObj) { + return PyType_IsSubtype(Py_TYPE(pyObj), PySidePropertyTypeF()); + } + return false; +} + +int setValue(PySideProperty *self, PyObject *source, PyObject *value) +{ + 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; +} + +PyObject *getValue(PySideProperty *self, PyObject *source) +{ + 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; +} + +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; +} + +const char *getTypeName(const PySideProperty *self) +{ + return self->d->typeName; +} + +PySideProperty *getObject(PyObject *source, PyObject *name) +{ + PyObject *attr = nullptr; + + attr = getFromType(Py_TYPE(source), name); + if (attr && checkType(attr)) { + Py_INCREF(attr); + return reinterpret_cast<PySideProperty *>(attr); + } + + if (!attr) + PyErr_Clear(); //Clear possible error caused by PyObject_GenericGetAttr + + return 0; +} + +bool isReadable(const PySideProperty * /* self */) +{ + return true; +} + +bool isWritable(const PySideProperty *self) +{ + return (self->d->fset != 0); +} + +bool hasReset(const PySideProperty *self) +{ + return (self->d->freset != 0); +} + +bool isDesignable(const PySideProperty *self) +{ + return self->d->designable; +} + +bool isScriptable(const PySideProperty *self) +{ + return self->d->scriptable; +} + +bool isStored(const PySideProperty *self) +{ + return self->d->stored; +} + +bool isUser(const PySideProperty *self) +{ + return self->d->user; +} + +bool isConstant(const PySideProperty *self) +{ + return self->d->constant; +} + +bool isFinal(const PySideProperty *self) +{ + return self->d->final; +} + +const char *getNotifyName(PySideProperty *self) +{ + if (self->d->notifySignature.isEmpty()) { + PyObject *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) +{ + return self->d->userData; +} + +} //namespace Property +} //namespace PySide diff --git a/sources/pyside6/libpyside/pysideproperty.h b/sources/pyside6/libpyside/pysideproperty.h new file mode 100644 index 000000000..4a467b186 --- /dev/null +++ b/sources/pyside6/libpyside/pysideproperty.h @@ -0,0 +1,116 @@ +/**************************************************************************** +** +** 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_PROPERTY_H +#define PYSIDE_PROPERTY_H + +#include <pysidemacros.h> + +#include <sbkpython.h> + +#include <QtCore/QMetaObject> + +extern "C" +{ + extern PYSIDE_API PyTypeObject *PySidePropertyTypeF(void); + + struct PySidePropertyPrivate; + struct PYSIDE_API PySideProperty + { + PyObject_HEAD + PySidePropertyPrivate* d; + }; +}; + +namespace PySide { namespace Property { + +typedef void (*MetaCallHandler)(PySideProperty*,PyObject*,QMetaObject::Call, void**); + +PYSIDE_API bool checkType(PyObject *pyObj); + +/** + * This function call set property function and pass value as arg + * This function does not check the property object type + * + * @param self The property object + * @param source The QObject witch has the property + * @param value The value to set in property + * @return Return 0 if ok or -1 if this function fail + **/ +PYSIDE_API int setValue(PySideProperty *self, PyObject *source, PyObject *value); + +/** + * This function call get property function + * This function does not check the property object type + * + * @param self The property object + * @param source The QObject witch has the property + * @return Return the result of property get function or 0 if this fail + **/ +PYSIDE_API PyObject *getValue(PySideProperty *self, PyObject *source); + +/** + * This function return the notify name used on this property + * + * @param self The property object + * @return Return a const char with the notify name used + **/ +PYSIDE_API const char *getNotifyName(PySideProperty *self); + + +/** + * This function search in the source object for desired property + * + * @param source The QObject object + * @param name The property name + * @return Return a new reference to property object + **/ +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 + +#endif diff --git a/sources/pyside6/libpyside/pysideproperty_p.h b/sources/pyside6/libpyside/pysideproperty_p.h new file mode 100644 index 000000000..e7b6e4d77 --- /dev/null +++ b/sources/pyside6/libpyside/pysideproperty_p.h @@ -0,0 +1,182 @@ +/**************************************************************************** +** +** 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_QPROPERTY_P_H +#define PYSIDE_QPROPERTY_P_H + +#include <sbkpython.h> +#include <QtCore/QByteArray> +#include <QMetaObject> +#include "pysideproperty.h" + +struct PySideProperty; + +struct PySidePropertyPrivate +{ + QByteArray typeName; + PySide::Property::MetaCallHandler metaCallHandler = nullptr; + PyObject *fget = nullptr; + PyObject *fset = nullptr; + PyObject *freset = nullptr; + PyObject *fdel = nullptr; + PyObject *notify = nullptr; + bool getter_doc = false; + QByteArray notifySignature; + QByteArray doc; + bool designable = true; + bool scriptable = true; + bool stored = true; + bool user = false; + bool constant = false; + bool final = false; + void *userData = nullptr; +}; + +namespace PySide { namespace Property { + +/** + * Init PySide QProperty support system + */ +void init(PyObject* module); + +/** + * This function call reset property function + * This function does not check the property object type + * + * @param self The property object + * @param source The QObject witch has the property + * @return Return 0 if ok or -1 if this function fail + **/ +int reset(PySideProperty* self, PyObject* source); + + +/** + * This function return the property type + * This function does not check the property object type + * + * @param self The property object + * @return Return the property type name + **/ +const char* getTypeName(const PySideProperty* self); + +/** + * This function check if property has read function + * This function does not check the property object type + * + * @param self The property object + * @return Return a boolean value + **/ +bool isReadable(const PySideProperty* self); + +/** + * This function check if property has write function + * This function does not check the property object type + * + * @param self The property object + * @return Return a boolean value + **/ +bool isWritable(const PySideProperty* self); + +/** + * This function check if property has reset function + * This function does not check the property object type + * + * @param self The property object + * @return Return a boolean value + **/ +bool hasReset(const PySideProperty* self); + +/** + * This function check if property has the flag DESIGNABLE setted + * This function does not check the property object type + * + * @param self The property object + * @return Return a boolean value + **/ +bool isDesignable(const PySideProperty* self); + +/** + * This function check if property has the flag SCRIPTABLE setted + * This function does not check the property object type + * + * @param self The property object + * @return Return a boolean value + **/ +bool isScriptable(const PySideProperty* self); + +/** + * This function check if property has the flag STORED setted + * This function does not check the property object type + * + * @param self The property object + * @return Return a boolean value + **/ +bool isStored(const PySideProperty* self); + +/** + * This function check if property has the flag USER setted + * This function does not check the property object type + * + * @param self The property object + * @return Return a boolean value + **/ +bool isUser(const PySideProperty* self); + +/** + * This function check if property has the flag CONSTANT setted + * This function does not check the property object type + * + * @param self The property object + * @return Return a boolean value + **/ +bool isConstant(const PySideProperty* self); + +/** + * This function check if property has the flag FINAL setted + * This function does not check the property object type + * + * @param self The property object + * @return Return a boolean value + **/ +bool isFinal(const PySideProperty* self); + +} // namespace Property +} // namespace PySide + +#endif diff --git a/sources/pyside6/libpyside/pysideqenum.cpp b/sources/pyside6/libpyside/pysideqenum.cpp new file mode 100644 index 000000000..07a548cb6 --- /dev/null +++ b/sources/pyside6/libpyside/pysideqenum.cpp @@ -0,0 +1,258 @@ +/**************************************************************************** +** +** 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 <shiboken.h> + +#include "pysideqenum.h" +#include "dynamicqmetaobject.h" +#include "pyside_p.h" + + +/////////////////////////////////////////////////////////////// +// +// PYSIDE-957: Create QEnum dynamically from Python Enum +// +// +extern "C" { + +using namespace Shiboken; + +static PyObject *analyzePyEnum(PyObject *pyenum, PyObject *container = nullptr) +{ + /* + * This is the straight-forward implementation of QEnum/QFlag. It does no + * longer create an equivalent Qt enum but takes the Python enum as-is. + * + * It parses an Enum/Flag derived Python enum completely so that + * registering can be done without error checks. This would be impossible + * in MetaObjectBuilderPrivate::parsePythonType. + */ + AutoDecRef members(PyObject_GetAttr(pyenum, Shiboken::PyMagicName::members())); + if (members.isNull()) + return nullptr; + AutoDecRef items(PyMapping_Items(members)); + if (items.isNull()) + return nullptr; + int iflag = PySide::QEnum::isFlag(pyenum); + if (iflag < 0) + return nullptr; + Py_ssize_t nr_items = PySequence_Length(items); + if (nr_items < 0) + return nullptr; + + for (Py_ssize_t idx = 0; idx < nr_items; ++idx) { + AutoDecRef item(PySequence_GetItem(items, idx)); + if (item.isNull()) + return nullptr; + + // The item should be a 2-element sequence of the key name and an + // object containing the value. + AutoDecRef key(PySequence_GetItem(item, 0)); + AutoDecRef member(PySequence_GetItem(item, 1)); + if (key.isNull() || member.isNull()) + return nullptr; + if (!Shiboken::String::check(key)) { + // '%.200s' is the safety stringbuffer size of most CPython functions. + PyErr_Format(PyExc_TypeError, + "QEnum expected a string mapping as __members__, got '%.200s'", + Py_TYPE(key)->tp_name); + return nullptr; + } + + // Get the value. + AutoDecRef value(PyObject_GetAttr(member, Shiboken::PyName::value())); + if (value.isNull()) + return nullptr; + if (!PyInt_Check(value)) { // int/long cheating + PyErr_Format(PyExc_TypeError, + "QEnum expected an int value as '%.200s', got '%.200s'", + Shiboken::String::toCString(key), Py_TYPE(value)->tp_name); + return nullptr; + } + } + Py_RETURN_NONE; +} + +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 + return -1; + return PyInt_AsSsize_t(ob_lineno); // int/long cheating +} + +static bool is_module_code() +{ + PyObject *frame = reinterpret_cast<PyObject *>(PyEval_GetFrame()); // borrowed ref + AutoDecRef ob_code(PyObject_GetAttr(frame, Shiboken::PyName::f_code())); + if (ob_code.isNull()) + return false; + AutoDecRef ob_name(PyObject_GetAttr(ob_code, Shiboken::PyName::co_name())); + if (ob_name.isNull()) + return false; + const char *codename = Shiboken::String::toCString(ob_name); + return strcmp(codename, "<module>") == 0; +} + +} // extern "C" + +namespace PySide { namespace QEnum { + +static std::map<int, PyObject *> enumCollector; + +int isFlag(PyObject *obType) +{ + /* + * Find out if this is an Enum or a Flag derived class. + * It checks also if things come from the enum module and if it is + * an Enum or Flag class at all. + * + * 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) { + PyErr_Format(PyExc_TypeError, "type %.200s does not inherit from 'Enum' or 'Flag'", + type->tp_name); + return -1; + } + return bool(have_flag); +} + +PyObject *QEnumMacro(PyObject *pyenum, bool flag) +{ + /* + * This is the official interface of 'QEnum'. It first calls 'analyzePyEnum'. + * When called as toplevel enum, it simply returns after some checks. + * Otherwise, 'pyenum' is stored for later use by the meta class registation. + */ + int computedFlag = isFlag(pyenum); + if (computedFlag < 0) + return nullptr; + if (bool(computedFlag) != flag) { + AutoDecRef name(PyObject_GetAttr(pyenum, PyMagicName::qualname())); + auto cname = String::toCString(name); + const char *e = "Enum"; + const char *f = "Flag"; + PyErr_Format(PyExc_TypeError, "expected '%s' but got '%s' (%.200s)", + flag ? f : e, flag ? e : f, cname); + return nullptr; + } + auto ok = analyzePyEnum(pyenum); + if (ok == nullptr) + return nullptr; + if (is_module_code()) { + // This is a toplevel enum which we resolve immediately. + Py_INCREF(pyenum); + return pyenum; + } + + Py_ssize_t lineno = get_lineno(); + if (lineno < 0) + return nullptr; + // Handle the rest via line number and the meta class. + Py_INCREF(pyenum); + Py_XDECREF(enumCollector[lineno]); + enumCollector[lineno] = pyenum; + Py_RETURN_NONE; +} + +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 + * MetaObjectBuilderPrivate::parsePythonType and resolves the collected + * Python Enum arguments. The result is then registered. + */ + if (enumCollector.empty()) + return {}; + PyObject *obContainerType = reinterpret_cast<PyObject *>(containerType); + Py_ssize_t lineno = get_lineno(); + + std::vector<PyObject *> result; + + auto it = enumCollector.begin(); + while (it != enumCollector.end()) { + int nr = it->first; + PyObject *pyenum = it->second; + if (nr >= lineno) { + AutoDecRef name(PyObject_GetAttr(pyenum, PyMagicName::name())); + if (name.isNull() || PyObject_SetAttr(obContainerType, name, pyenum) < 0) + return {}; + result.push_back(pyenum); + it = enumCollector.erase(it); + } else { + ++it; + } + } + return result; +} + +} // namespace Enum +} // namespace Shiboken + +// +/////////////////////////////////////////////////////////////// diff --git a/sources/pyside6/libpyside/pysideqenum.h b/sources/pyside6/libpyside/pysideqenum.h new file mode 100644 index 000000000..fc4e55982 --- /dev/null +++ b/sources/pyside6/libpyside/pysideqenum.h @@ -0,0 +1,57 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef PYSIDE_QENUM_H +#define PYSIDE_QENUM_H + +#include <pysidemacros.h> +#include <vector> + +namespace PySide { namespace QEnum { + +// PYSIDE-957: Support the QEnum macro +PYSIDE_API PyObject *QEnumMacro(PyObject *, bool); +PYSIDE_API int isFlag(PyObject *); +PYSIDE_API std::vector<PyObject *> resolveDelayedQEnums(PyTypeObject *); +PYSIDE_API void init(); + +} // namespace QEnum +} // namespace PySide + +#endif diff --git a/sources/pyside6/libpyside/pysideqflags.cpp b/sources/pyside6/libpyside/pysideqflags.cpp new file mode 100644 index 000000000..b07a73332 --- /dev/null +++ b/sources/pyside6/libpyside/pysideqflags.cpp @@ -0,0 +1,205 @@ +/**************************************************************************** +** +** 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 new file mode 100644 index 000000000..71f30808d --- /dev/null +++ b/sources/pyside6/libpyside/pysideqflags.h @@ -0,0 +1,79 @@ +/**************************************************************************** +** +** 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/pysidesignal.cpp b/sources/pyside6/libpyside/pysidesignal.cpp new file mode 100644 index 000000000..d14f32525 --- /dev/null +++ b/sources/pyside6/libpyside/pysidesignal.cpp @@ -0,0 +1,1040 @@ +/**************************************************************************** +** +** 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 <sbkpython.h> +#include "pysidesignal.h" +#include "pysidesignal_p.h" +#include "pysidestaticstrings.h" +#include "signalmanager.h" + +#include <shiboken.h> + +#include <QtCore/QObject> +#include <QtCore/QMetaMethod> +#include <QtCore/QMetaObject> +#include <signature.h> + +#include <algorithm> +#include <utility> + +#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; + }; + + 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 PyObject *buildQtCompatible(const QByteArray &); +} +} + +extern "C" +{ + +// Signal methods +static int signalTpInit(PyObject *, PyObject *, PyObject *); +static void signalFree(void *); +static void signalInstanceFree(void *); +static PyObject *signalGetItem(PyObject *self, PyObject *key); +static PyObject *signalToString(PyObject *self); +static PyObject *signalDescrGet(PyObject *self, PyObject *obj, PyObject *type); + +// Signal Instance methods +static PyObject *signalInstanceConnect(PyObject *, PyObject *, PyObject *); +static PyObject *signalInstanceDisconnect(PyObject *, PyObject *); +static PyObject *signalInstanceEmit(PyObject *, PyObject *); +static PyObject *signalInstanceGetItem(PyObject *, PyObject *); + +static PyObject *signalInstanceCall(PyObject *self, PyObject *args, PyObject *kw); +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 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 *PySideMetaSignalTypeF(void) +{ + static PyTypeObject *type = nullptr; + if (!type) { + PyObject *bases = Py_BuildValue("(O)", &PyType_Type); + type = (PyTypeObject *)SbkType_FromSpecWithBases(&PySideMetaSignalType_spec, bases); + Py_XDECREF(bases); + } + 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, +}; + + +PyTypeObject *PySideSignalTypeF(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); + } + return type; +} + +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 */ +}; + +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, +}; + + +PyTypeObject *PySideSignalInstanceTypeF(void) +{ + static PyTypeObject *type = + reinterpret_cast<PyTypeObject *>(SbkType_FromSpec(&PySideSignalInstanceType_spec)); + return type; +} + +static int signalTpInit(PyObject *self, PyObject *args, PyObject *kwds) +{ + static PyObject *emptyTuple = nullptr; + 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)) + return -1; + + bool tupledArgs = false; + PySideSignal *data = reinterpret_cast<PySideSignal *>(self); + if (!data->data) + data->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)); + } + } + + 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)); + } + } + + if (!tupledArgs) { + const auto sig = PySide::Signal::parseSignature(args); + PySide::Signal::appendSignature( + data, + PySide::Signal::SignalSignature(sig)); + } + + return 0; +} + +static void signalFree(void *self) +{ + 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); +} + +static PyObject *signalGetItem(PyObject *self, PyObject *key) +{ + auto data = reinterpret_cast<PySideSignal *>(self); + QByteArray sigKey; + if (key) { + sigKey = PySide::Signal::parseSignature(key); + } else { + sigKey = data->data == nullptr || data->data->signatures.isEmpty() + ? PySide::Signal::voidType() : data->data->signatures.constFirst().signature; + } + auto sig = PySide::Signal::buildSignature(data->data->signalName, sigKey); + return Shiboken::String::fromCString(sig.constData()); +} + + +static PyObject *signalToString(PyObject *self) +{ + return signalGetItem(self, 0); +} + +static void signalInstanceFree(void *self) +{ + auto pySelf = reinterpret_cast<PyObject *>(self); + auto data = reinterpret_cast<PySideSignalInstance *>(self); + + PySideSignalInstancePrivate *dataPvt = data->d; + + Py_XDECREF(dataPvt->homonymousMethod); + + if (dataPvt->next) { + Py_DECREF(dataPvt->next); + dataPvt->next = 0; + } + delete dataPvt; + data->d = 0; + Py_TYPE(pySelf)->tp_base->tp_free(self); +} + +static PyObject *signalInstanceConnect(PyObject *self, PyObject *args, PyObject *kwds) +{ + PyObject *slot = nullptr; + PyObject *type = nullptr; + static const char *kwlist[] = {"slot", "type", nullptr}; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, + "O|O:SignalInstance", const_cast<char **>(kwlist), &slot, &type)) + return 0; + + PySideSignalInstance *source = reinterpret_cast<PySideSignalInstance *>(self); + Shiboken::AutoDecRef pyArgs(PyList_New(0)); + + bool match = false; + if (Py_TYPE(slot) == PySideSignalInstanceTypeF()) { + PySideSignalInstance *sourceWalk = source; + PySideSignalInstance *targetWalk; + + //find best match + while (sourceWalk && !match) { + targetWalk = reinterpret_cast<PySideSignalInstance *>(slot); + while (targetWalk && !match) { + 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); + + PyList_Append(pyArgs, targetWalk->d->source); + Shiboken::AutoDecRef targetSignature(PySide::Signal::buildQtCompatible(targetWalk->d->signature)); + PyList_Append(pyArgs, targetSignature); + + match = true; + } + targetWalk = reinterpret_cast<PySideSignalInstance *>(targetWalk->d->next); + } + sourceWalk = reinterpret_cast<PySideSignalInstance *>(sourceWalk->d->next); + } + } 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; + + QByteArray functionName; + 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; + + // 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; + } + } + } + } + + // Adding references to pyArgs + PyList_Append(pyArgs, source->d->source); + + if (matchedSlot) { + // 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)); + PyList_Append(pyArgs, signature); + } else { + // Try the first by default if the slot was not found + Shiboken::AutoDecRef signature(PySide::Signal::buildQtCompatible(source->d->signature)); + PyList_Append(pyArgs, signature); + } + PyList_Append(pyArgs, slot); + match = true; + } + + if (type) + PyList_Append(pyArgs, type); + + 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; + } + PyObject *result = PyObject_CallObject(pyMethod, tupleArgs); + if (result == Py_True || result == Py_False) + 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; +} + +static int argCountInSignature(const char *signature) +{ + return QByteArray(signature).count(",") + 1; +} + +static PyObject *signalInstanceEmit(PyObject *self, PyObject *args) +{ + PySideSignalInstance *source = reinterpret_cast<PySideSignalInstance *>(self); + + Shiboken::AutoDecRef pyArgs(PyList_New(0)); + int numArgsGiven = PySequence_Fast_GET_SIZE(args); + int numArgsInSignature = argCountInSignature(source->d->signature); + + // If number of arguments given to emit is smaller than the first source signature expects, + // it is possible it's a case of emitting a signal with default parameters. + // Search through all the overloaded signals with the same name, and try to find a signature + // with the same number of arguments as given to emit, and is also marked as a cloned method + // (which in metaobject parlance means a signal with default parameters). + // @TODO: This should be improved to take into account argument types as well. The current + // assumption is there are no signals which are both overloaded on argument types and happen to + // have signatures with default parameters. + if (numArgsGiven < numArgsInSignature) { + PySideSignalInstance *possibleDefaultInstance = source; + while ((possibleDefaultInstance = possibleDefaultInstance->d->next)) { + if (possibleDefaultInstance->d->attributes & QMetaMethod::Cloned + && argCountInSignature(possibleDefaultInstance->d->signature) == numArgsGiven) { + source = possibleDefaultInstance; + break; + } + } + } + Shiboken::AutoDecRef sourceSignature(PySide::Signal::buildQtCompatible(source->d->signature)); + + PyList_Append(pyArgs, sourceSignature); + for (Py_ssize_t i = 0, max = PyTuple_Size(args); i < max; i++) + PyList_Append(pyArgs, PyTuple_GetItem(args, i)); + + Shiboken::AutoDecRef pyMethod(PyObject_GetAttr(source->d->source, + PySide::PyName::qtEmit())); + + Shiboken::AutoDecRef tupleArgs(PyList_AsTuple(pyArgs)); + return PyObject_CallObject(pyMethod, 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); + const auto sig = PySide::Signal::buildSignature(sigName, sigKey); + while (data) { + 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; +} + +static PyObject *signalInstanceDisconnect(PyObject *self, PyObject *args) +{ + auto source = reinterpret_cast<PySideSignalInstance *>(self); + Shiboken::AutoDecRef pyArgs(PyList_New(0)); + + PyObject *slot; + 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()) { + PySideSignalInstance *target = reinterpret_cast<PySideSignalInstance *>(slot); + if (QMetaObject::checkConnectArgs(source->d->signature, target->d->signature)) { + PyList_Append(pyArgs, source->d->source); + Shiboken::AutoDecRef source_signature(PySide::Signal::buildQtCompatible(source->d->signature)); + PyList_Append(pyArgs, source_signature); + + PyList_Append(pyArgs, target->d->source); + Shiboken::AutoDecRef target_signature(PySide::Signal::buildQtCompatible(target->d->signature)); + PyList_Append(pyArgs, target_signature); + match = true; + } + } else { + //try the first signature + PyList_Append(pyArgs, source->d->source); + Shiboken::AutoDecRef signature(PySide::Signal::buildQtCompatible(source->d->signature)); + PyList_Append(pyArgs, signature); + + // disconnect all, so we need to use the c++ signature disconnect(qobj, signal, 0, 0) + if (slot == Py_None) + PyList_Append(pyArgs, slot); + PyList_Append(pyArgs, slot); + match = true; + } + + if (match) { + Shiboken::AutoDecRef tupleArgs(PyList_AsTuple(pyArgs)); + Shiboken::AutoDecRef pyMethod(PyObject_GetAttr(source->d->source, + PySide::PyName::qtDisconnect())); + PyObject *result = PyObject_CallObject(pyMethod, tupleArgs); + if (!result || result == Py_True) + return result; + else + Py_DECREF(result); + } + + PyErr_Format(PyExc_RuntimeError, "Failed to disconnect signal %s.", + source->d->signature.constData()); + return 0; +} + +// PYSIDE-68: Supply the missing __get__ function +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) { + Py_INCREF(self); + return self; + } + Shiboken::AutoDecRef name(Py_BuildValue("s", signal->data->signalName.data())); + return reinterpret_cast<PyObject *>(PySide::Signal::initialize(signal, name, obj)); +} + +static PyObject *signalCall(PyObject *self, PyObject *args, PyObject *kw) +{ + auto signal = reinterpret_cast<PySideSignal *>(self); + + // Native C++ signals can't be called like functions, thus we throw an exception. + // 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; + + // 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 + 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; + return callFunc(homonymousMethod, args, kw); +} + +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; + } + + 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 +} + +static PyObject *metaSignalCheck(PyObject * /* klass */, PyObject *arg) +{ + if (PyType_IsSubtype(Py_TYPE(arg), PySideSignalInstanceTypeF())) + Py_RETURN_TRUE; + else + Py_RETURN_FALSE; +} + +} // extern "C" + +namespace PySide { +namespace Signal { + +static const char *MetaSignal_SignatureStrings[] = { + "PySide6.QtCore.MetaSignal.__instancecheck__(object:object)->bool", + nullptr}; // Sentinel + +static const char *Signal_SignatureStrings[] = { + "PySide6.QtCore.Signal(*types:type,name:str=nullptr,arguments:str=nullptr)", + nullptr}; // Sentinel + +static const char *SignalInstance_SignatureStrings[] = { + "PySide6.QtCore.SignalInstance.connect(slot:object,type:type=nullptr)", + "PySide6.QtCore.SignalInstance.disconnect(slot:object=nullptr)", + "PySide6.QtCore.SignalInstance.emit(*args:typing.Any)", + nullptr}; // Sentinel + +void init(PyObject *module) +{ + if (InitSignatureStrings(PySideMetaSignalTypeF(), MetaSignal_SignatureStrings) < 0) + return; + Py_INCREF(PySideMetaSignalTypeF()); + PyModule_AddObject(module, "MetaSignal", reinterpret_cast<PyObject *>(PySideMetaSignalTypeF())); + + if (InitSignatureStrings(PySideSignalTypeF(), Signal_SignatureStrings) < 0) + return; + Py_INCREF(PySideSignalTypeF()); + PyModule_AddObject(module, "Signal", reinterpret_cast<PyObject *>(PySideSignalTypeF())); + + if (InitSignatureStrings(PySideSignalInstanceTypeF(), SignalInstance_SignatureStrings) < 0) + return; + Py_INCREF(PySideSignalInstanceTypeF()); + PyModule_AddObject(module, "SignalInstance", reinterpret_cast<PyObject *>(PySideSignalInstanceTypeF())); +} + +bool checkType(PyObject *pyObj) +{ + if (pyObj) + return PyType_IsSubtype(Py_TYPE(pyObj), PySideSignalTypeF()); + return false; +} + +void updateSourceObject(PyObject *source) +{ + PyTypeObject *objType = reinterpret_cast<PyTypeObject *>(PyObject_Type(source)); + + Py_ssize_t pos = 0; + PyObject *value; + PyObject *key; + + 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); + } + } + + Py_XDECREF(objType); +} + +QByteArray getTypeName(PyObject *type) +{ + 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)) + return QByteArrayLiteral("QString"); + if (objType == &PyInt_Type) + return QByteArrayLiteral("int"); + if (objType == &PyLong_Type) + return QByteArrayLiteral("long"); + if (objType == &PyFloat_Type) + return QByteArrayLiteral("double"); + if (objType == &PyBool_Type) + return QByteArrayLiteral("bool"); + if (objType == &PyList_Type) + return QByteArrayLiteral("QVariantList"); + if (Py_TYPE(objType) == SbkEnumType_TypeF()) + return Shiboken::Enum::getCppName(objType); + return QByteArrayLiteral("PyObject"); + } + if (type == 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 (result == "qreal") + result = sizeof(qreal) == sizeof(double) ? "double" : "float"; + return result; + } + return QByteArray(); +} + +static QByteArray buildSignature(const QByteArray &name, const QByteArray &signature) +{ + return QMetaObject::normalizedSignature(name + '(' + signature + ')'); +} + +static QByteArray parseSignature(PyObject *args) +{ + if (args && (Shiboken::String::check(args) || !PySequence_Check(args))) + return getTypeName(args); + + 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; + } + } + return signature; +} + +static void appendSignature(PySideSignal *self, const SignalSignature &signature) +{ + self->data->signatures.append({signature.m_parameterTypes, signature.m_attributes}); +} + +static void instanceInitialize(PySideSignalInstance *self, PyObject *name, PySideSignal *data, PyObject *source, int index) +{ + self->d = new PySideSignalInstancePrivate; + PySideSignalInstancePrivate *selfPvt = self->d; + selfPvt->next = nullptr; + if (data->data->signalName.isEmpty()) + data->data->signalName = Shiboken::String::toCString(name); + selfPvt->signalName = data->data->signalName; + + selfPvt->source = source; + const auto &signature = data->data->signatures.at(index); + selfPvt->signature = buildSignature(self->d->signalName, signature.signature); + selfPvt->attributes = signature.attributes; + selfPvt->homonymousMethod = 0; + if (data->homonymousMethod) { + selfPvt->homonymousMethod = data->homonymousMethod; + Py_INCREF(selfPvt->homonymousMethod); + } + index++; + + if (index < data->data->signatures.size()) { + selfPvt->next = PyObject_New(PySideSignalInstance, PySideSignalInstanceTypeF()); + instanceInitialize(selfPvt->next, name, data, source, index); + } +} + +PySideSignalInstance *initialize(PySideSignal *self, PyObject *name, PyObject *object) +{ + PySideSignalInstance *instance = PyObject_New(PySideSignalInstance, + PySideSignalInstanceTypeF()); + instanceInitialize(instance, name, self, object, 0); + auto sbkObj = reinterpret_cast<SbkObject *>(object); + if (!Shiboken::Object::wasCreatedByPython(sbkObj)) + Py_INCREF(object); // PYSIDE-79: this flag was crucial for a wrapper call. + return instance; +} + +bool connect(PyObject *source, const char *signal, PyObject *callback) +{ + Shiboken::AutoDecRef pyMethod(PyObject_GetAttr(source, + PySide::PyName::qtConnect())); + if (pyMethod.isNull()) + return false; + + Shiboken::AutoDecRef pySignature(Shiboken::String::fromCString(signal)); + Shiboken::AutoDecRef pyArgs(PyTuple_Pack(3, source, pySignature.object(), callback)); + PyObject *result = PyObject_CallObject(pyMethod, pyArgs); + if (result == Py_False) { + PyErr_Format(PyExc_RuntimeError, "Failed to connect signal %s, to python callable object.", signal); + Py_DECREF(result); + result = 0; + } + return result; +} + +PySideSignalInstance *newObjectFromMethod(PyObject *source, const QList<QMetaMethod>& methodList) +{ + PySideSignalInstance *root = nullptr; + PySideSignalInstance *previous = nullptr; + for (const QMetaMethod &m : methodList) { + PySideSignalInstance *item = PyObject_New(PySideSignalInstance, PySideSignalInstanceTypeF()); + if (!root) + root = item; + + if (previous) + previous->d->next = item; + + item->d = new PySideSignalInstancePrivate; + 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 + selfPvt->signalName = cppName; + selfPvt->signature = m.methodSignature(); + selfPvt->attributes = m.attributes(); + selfPvt->homonymousMethod = 0; + selfPvt->next = 0; + } + 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) +{ + auto typeDict = reinterpret_cast<PyTypeObject *>(wrapperType)->tp_dict; + PyObject *homonymousMethod; + if ((homonymousMethod = PyDict_GetItemString(typeDict, signalName))) { + Py_INCREF(homonymousMethod); + signal->homonymousMethod = homonymousMethod; + } + PyDict_SetItemString(typeDict, signalName, reinterpret_cast<PyObject *>(signal)); +} + +// This function is used by qStableSort to promote empty signatures +static bool compareSignals(const SignalSignature &sig1, const SignalSignature &) +{ + return sig1.m_parameterTypes.isEmpty(); +} + +static PyObject *buildQtCompatible(const QByteArray &signature) +{ + const auto ba = QT_SIGNAL_SENTINEL + signature; + return Shiboken::String::fromStringAndSize(ba, ba.size()); +} + +void registerSignals(SbkObjectType *pyObj, const QMetaObject *metaObject) +{ + typedef QHash<QByteArray, QList<SignalSignature> > SignalSigMap; + SignalSigMap 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(), ","); + if (method.attributes() & QMetaMethod::Cloned) + signature.m_attributes = QMetaMethod::Cloned; + signalsFound[methodName] << signature; + } + } + + SignalSigMap::Iterator it = signalsFound.begin(); + SignalSigMap::Iterator end = signalsFound.end(); + for (; it != end; ++it) { + PySideSignal *self = PyObject_New(PySideSignal, PySideSignalTypeF()); + self->data = new PySideSignalData; + self->data->signalName = it.key(); + self->homonymousMethod = 0; + + // Empty signatures comes first! So they will be the default signal signature + std::stable_sort(it.value().begin(), it.value().end(), &compareSignals); + SignalSigMap::mapped_type::const_iterator j = it.value().begin(); + SignalSigMap::mapped_type::const_iterator endJ = it.value().end(); + for (; j != endJ; ++j) { + const SignalSignature &sig = *j; + appendSignature(self, sig); + } + + _addSignalToWrapper(pyObj, it.key(), self); + Py_DECREF(reinterpret_cast<PyObject *>(self)); + } +} + +PyObject *getObject(PySideSignalInstance *signal) +{ + return signal->d->source; +} + +const char *getSignature(PySideSignalInstance *signal) +{ + return signal->d->signature; +} + +QStringList getArgsFromSignature(const char *signature, bool *isShortCircuit) +{ + QString qsignature = QString::fromLatin1(signature).trimmed(); + QStringList result; + + if (isShortCircuit) + *isShortCircuit = !qsignature.contains(QLatin1Char('(')); + if (qsignature.contains(QLatin1String("()")) || qsignature.contains(QLatin1String("(void)"))) + return result; + if (qsignature.endsWith(QLatin1Char(')'))) { + const int paren = qsignature.indexOf(QLatin1Char('(')); + if (paren >= 0) { + qsignature.chop(1); + qsignature.remove(0, paren + 1); + result = qsignature.split(QLatin1Char(',')); + for (QString &type : result) + type = type.trimmed(); + } + } + return result; +} + +QString 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); + } else if (PyCFunction_Check(callback)) { + const PyCFunctionObject *funcObj = reinterpret_cast<const PyCFunctionObject *>(callback); + functionName = PepCFunction_GET_NAMESTR(funcObj); + useSelf = PyCFunction_GET_SELF(funcObj); + const int flags = PyCFunction_GET_FLAGS(funcObj); + + 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; + } + } + } + + if (numArgs == -1) { + if (flags & METH_VARARGS) + numArgs = -1; + else if (flags & METH_NOARGS) + numArgs = 0; + } + } else if (PyCallable_Check(callback)) { + functionName = "__callback" + QByteArray::number((qlonglong)callback); + } + + 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 (!isShortCircuit) { + signature.append(QLatin1Char('(')); + if (numArgs == -1) + numArgs = std::numeric_limits<int>::max(); + while (args.count() && (args.count() > (numArgs - useSelf))) { + args.removeLast(); + } + signature.append(args.join(QLatin1Char(','))); + signature.append(QLatin1Char(')')); + } + return signature; +} + +bool isQtSignal(const char *signal) +{ + return (signal && signal[0] == QT_SIGNAL_SENTINEL); +} + +bool checkQtSignal(const char *signal) +{ + if (!isQtSignal(signal)) { + PyErr_SetString(PyExc_TypeError, "Use the function PySide6.QtCore.SIGNAL on signals"); + return false; + } + return true; +} + +QString codeCallbackName(PyObject *callback, const QString &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 + QString::number(quint64(callback), 16); +} + +QByteArray voidType() +{ + return QByteArrayLiteral("void"); +} + +} //namespace Signal +} //namespace PySide + diff --git a/sources/pyside6/libpyside/pysidesignal.h b/sources/pyside6/libpyside/pysidesignal.h new file mode 100644 index 000000000..973644b0a --- /dev/null +++ b/sources/pyside6/libpyside/pysidesignal.h @@ -0,0 +1,163 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef PYSIDE_SIGNAL_H +#define PYSIDE_SIGNAL_H + +#include <pysidemacros.h> + +#include <sbkpython.h> +#include <basewrapper.h> + +#include <QtCore/QList> +#include <QtCore/QMetaMethod> + +QT_BEGIN_NAMESPACE +struct QMetaObject; +class QObject; +QT_END_NAMESPACE + +extern "C" +{ + extern PYSIDE_API PyTypeObject *PySideSignalTypeF(void); + extern PYSIDE_API PyTypeObject *PySideSignalInstanceTypeF(void); + + // Internal object + struct PYSIDE_API PySideSignal; + + struct PySideSignalInstancePrivate; + struct PYSIDE_API PySideSignalInstance + { + PyObject_HEAD + PySideSignalInstancePrivate *d; + }; +}; // extern "C" + +namespace PySide { +namespace Signal { + +PYSIDE_API bool checkType(PyObject *type); + +/** + * Register all C++ signals of a QObject on Python type. + */ +PYSIDE_API void registerSignals(SbkObjectType *pyObj, const QMetaObject *metaObject); + +/** + * This function creates a Signal object which stays attached to QObject class based on a list of QMetaMethods + * + * @param source of the Signal to be registered on meta object + * @param methods a list of QMetaMethod wich contains the supported signature + * @return Return a new reference to PyObject* of type PySideSignal + **/ +PYSIDE_API PySideSignalInstance *newObjectFromMethod(PyObject *source, const QList<QMetaMethod> &methods); + +/** + * This function initializes the Signal object by creating a PySideSignalInstance + * + * @param self a Signal object used as base to PySideSignalInstance + * @param name the name to be used on PySideSignalInstance + * @param object the PyObject where the signal will be attached + * @return Return a new reference to PySideSignalInstance + **/ +PYSIDE_API PySideSignalInstance *initialize(PySideSignal *signal, PyObject *name, PyObject *object); + +/** + * This function is used to retrieve the object in which the signal is attached + * + * @param self The Signal object + * @return Return the internal reference to the parent object of the signal + **/ +PYSIDE_API PyObject *getObject(PySideSignalInstance *signal); + +/** + * This function is used to retrieve the signal signature + * + * @param self The Signal object + * @return Return the signal signature + **/ +PYSIDE_API const char *getSignature(PySideSignalInstance *signal); + +/** + * This function is used to retrieve the signal signature + * + * @param self The Signal object + * @return Return the signal signature + **/ +PYSIDE_API void updateSourceObject(PyObject *source); + +/** + * This function verifies if the signature is a QtSignal base on SIGNAL flag + * @param signature The signal signature + * @return Return true if this is a Qt Signal, otherwise return false + **/ +PYSIDE_API bool isQtSignal(const char *signature); + +/** + * This function is similar to isQtSignal, however if it fails, it'll raise a Python error instead. + * + * @param signature The signal signature + * @return Return true if this is a Qt Signal, otherwise return false + **/ +PYSIDE_API bool checkQtSignal(const char *signature); + +/** + * This function is used to retrieve the signature base on Signal and receiver callback + * @param signature The signal signature + * @param receiver The QObject which will receive the signal + * @param callback Callback function which will connect to the signal + * @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); + +/** + * 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 = 0); + +} // namespace Signal +} // namespace PySide + +#endif diff --git a/sources/pyside6/libpyside/pysidesignal_p.h b/sources/pyside6/libpyside/pysidesignal_p.h new file mode 100644 index 000000000..337feaa8a --- /dev/null +++ b/sources/pyside6/libpyside/pysidesignal_p.h @@ -0,0 +1,94 @@ +/**************************************************************************** +** +** 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_QSIGNAL_P_H +#define PYSIDE_QSIGNAL_P_H + +#include <sbkpython.h> + +#include <QtCore/QByteArray> +#include <QtCore/QVector> + +struct PySideSignalData +{ + struct Signature + { + QByteArray signature; + int attributes; + }; + + QByteArray signalName; + QVector<Signature> signatures; + QByteArrayList *signalArguments; +}; + +extern "C" +{ + extern PyTypeObject *PySideSignalTypeF(void); + + struct PySideSignal { + PyObject_HEAD + PySideSignalData *data; + PyObject *homonymousMethod; + }; + + struct PySideSignalInstance; +}; //extern "C" + +struct PySideSignalInstancePrivate +{ + QByteArray signalName; + QByteArray signature; + int attributes = 0; + PyObject *source = nullptr; + PyObject *homonymousMethod = nullptr; + PySideSignalInstance *next = nullptr; +}; + +namespace PySide { namespace 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 voidType(); + +}} //namespace PySide + +#endif diff --git a/sources/pyside6/libpyside/pysideslot.cpp b/sources/pyside6/libpyside/pysideslot.cpp new file mode 100644 index 000000000..99c3cdf55 --- /dev/null +++ b/sources/pyside6/libpyside/pysideslot.cpp @@ -0,0 +1,199 @@ +/**************************************************************************** +** +** 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" +#include "pysidesignal_p.h" +#include "pysideslot_p.h" + +#include <shiboken.h> + +#include <QtCore/QMetaObject> +#include <QtCore/QString> +#include <signature.h> + +using namespace Shiboken; + +struct SlotData +{ + QByteArray name; + QByteArray args; + QByteArray resultType; +}; + +typedef struct +{ + PyObject_HEAD + SlotData *slotData; +} PySideSlot; + +extern "C" +{ + +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 *PySideSlotTypeF(void) +{ + static PyTypeObject *type = reinterpret_cast<PyTypeObject *>( + SbkType_FromSpec(&PySideSlotType_spec)); + return type; +} + +int slotTpInit(PyObject *self, PyObject *args, PyObject *kw) +{ + static PyObject *emptyTuple = nullptr; + static const char *kwlist[] = {"name", "result", nullptr}; + char *argName = nullptr; + PyObject *argResult = nullptr; + + if (emptyTuple == 0) + emptyTuple = PyTuple_New(0); + + if (!PyArg_ParseTupleAndKeywords(emptyTuple, kw, "|sO:QtCore.Slot", + const_cast<char **>(kwlist), &argName, &argResult)) { + return -1; + } + + PySideSlot *data = reinterpret_cast<PySideSlot *>(self); + if (!data->slotData) + data->slotData = new SlotData; + for(Py_ssize_t i = 0, i_max = PyTuple_Size(args); i < i_max; i++) { + PyObject *argType = PyTuple_GET_ITEM(args, i); + const auto typeName = PySide::Signal::getTypeName(argType); + if (typeName.isEmpty()) { + PyErr_Format(PyExc_TypeError, "Unknown signal argument type: %s", Py_TYPE(argType)->tp_name); + return -1; + } + if (!data->slotData->args.isEmpty()) + data->slotData->args += ','; + data->slotData->args += typeName; + } + + if (argName) + data->slotData->name = argName; + + data->slotData->resultType = argResult + ? PySide::Signal::getTypeName(argResult) : PySide::Signal::voidType(); + + return 0; +} + +PyObject *slotCall(PyObject *self, PyObject *args, PyObject * /* kw */) +{ + static PyObject *pySlotName = nullptr; + PyObject *callback; + callback = PyTuple_GetItem(args, 0); + Py_INCREF(callback); + + if (Py_TYPE(callback)->tp_call != nullptr) { + PySideSlot *data = reinterpret_cast<PySideSlot *>(self); + + if (!data->slotData) + data->slotData = new SlotData; + + 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); + } + const QByteArray returnType = QMetaObject::normalizedType(data->slotData->resultType); + const QByteArray signature = + returnType + ' ' + data->slotData->name + '(' + data->slotData->args + ')'; + + if (!pySlotName) + pySlotName = String::fromCString(PYSIDE_SLOT_LIST_ATTR); + + PyObject *pySignature = String::fromCString(signature); + PyObject *signatureList = 0; + if (PyObject_HasAttr(callback, pySlotName)) { + signatureList = PyObject_GetAttr(callback, pySlotName); + } else { + signatureList = PyList_New(0); + PyObject_SetAttr(callback, pySlotName, signatureList); + Py_DECREF(signatureList); + } + + PyList_Append(signatureList, pySignature); + Py_DECREF(pySignature); + + //clear data + delete data->slotData; + data->slotData = nullptr; + return callback; + } + return callback; +} + +} // extern "C" + +namespace PySide { +namespace Slot { + +static const char *Slot_SignatureStrings[] = { + "PySide6.QtCore.Slot(*types:type,name:str=nullptr,result:str=nullptr)->typing.Callable[...,typing.Optional[str]]", + nullptr}; // Sentinel + +void init(PyObject *module) +{ + if (InitSignatureStrings(PySideSlotTypeF(), Slot_SignatureStrings) < 0) + return; + + Py_INCREF(PySideSlotTypeF()); + PyModule_AddObject(module, "Slot", reinterpret_cast<PyObject *>(PySideSlotTypeF())); +} + +} // namespace Slot +} // namespace PySide diff --git a/sources/pyside6/libpyside/pysideslot_p.h b/sources/pyside6/libpyside/pysideslot_p.h new file mode 100644 index 000000000..3d98e15c4 --- /dev/null +++ b/sources/pyside6/libpyside/pysideslot_p.h @@ -0,0 +1,49 @@ +/**************************************************************************** +** +** 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_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); +}} + +#endif diff --git a/sources/pyside6/libpyside/pysidestaticstrings.cpp b/sources/pyside6/libpyside/pysidestaticstrings.cpp new file mode 100644 index 000000000..760d77632 --- /dev/null +++ b/sources/pyside6/libpyside/pysidestaticstrings.cpp @@ -0,0 +1,63 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#include "pysidestaticstrings.h" +#include <sbkstring.h> + +#define STATIC_STRING_IMPL(funcName, value) \ +PyObject *funcName() \ +{ \ + static PyObject *const s = Shiboken::String::createStaticString(value); \ + return s; \ +} + +namespace PySide +{ +namespace PyName +{ +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(name, "name") +STATIC_STRING_IMPL(property, "property") +STATIC_STRING_IMPL(select_id, "select_id") +} // namespace PyName +} // namespace PySide diff --git a/sources/pyside6/libpyside/pysidestaticstrings.h b/sources/pyside6/libpyside/pysidestaticstrings.h new file mode 100644 index 000000000..1222d8f47 --- /dev/null +++ b/sources/pyside6/libpyside/pysidestaticstrings.h @@ -0,0 +1,60 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef PYSIDESTRINGS_H +#define PYSIDESTRINGS_H + +#include <sbkpython.h> + +namespace PySide +{ +namespace PyName +{ +PyObject *qtStaticMetaObject(); +PyObject *qtConnect(); +PyObject *qtDisconnect(); +PyObject *qtEmit(); +PyObject *dict_ring(); +PyObject *name(); +PyObject *property(); +PyObject *select_id(); +} // namespace PyName +} // namespace PySide + +#endif // PYSIDESTRINGS_H diff --git a/sources/pyside6/libpyside/pysideweakref.cpp b/sources/pyside6/libpyside/pysideweakref.cpp new file mode 100644 index 000000000..cd90634bd --- /dev/null +++ b/sources/pyside6/libpyside/pysideweakref.cpp @@ -0,0 +1,114 @@ +/**************************************************************************** +** +** 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 "pysideweakref.h" + +#include <sbkpython.h> +#include <shiboken.h> + +typedef struct { + 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 *PySideCallableObjectTypeF() +{ + static PyTypeObject *type = + reinterpret_cast<PyTypeObject *>(SbkType_FromSpec(&PySideCallableObjectType_spec)); + return type; +} + +static PyObject *CallableObject_call(PyObject *callable_object, PyObject *args, PyObject * /* kw */) +{ + PySideCallableObject *obj = reinterpret_cast<PySideCallableObject *>(callable_object); + obj->weakref_func(obj->user_data); + + Py_XDECREF(PyTuple_GET_ITEM(args, 0)); //kill weak ref object + Py_RETURN_NONE; +} + +namespace PySide { namespace WeakRef { + +PyObject *create(PyObject *obj, PySideWeakRefFunction func, void *userData) +{ + if (obj == Py_None) + return 0; + + if (Py_TYPE(PySideCallableObjectTypeF()) == 0) + { + Py_TYPE(PySideCallableObjectTypeF()) = &PyType_Type; + PyType_Ready(PySideCallableObjectTypeF()); + } + + PyTypeObject *type = PySideCallableObjectTypeF(); + PySideCallableObject *callable = PyObject_New(PySideCallableObject, type); + if (!callable || PyErr_Occurred()) + return 0; + + PyObject *weak = PyWeakref_NewRef(obj, reinterpret_cast<PyObject *>(callable)); + if (!weak || PyErr_Occurred()) + return 0; + + callable->weakref_func = func; + callable->user_data = userData; + Py_DECREF(callable); // PYSIDE-79: after decref the callable is undefined (theoretically) + + return reinterpret_cast<PyObject *>(weak); +} + +} } //namespace + diff --git a/sources/pyside6/libpyside/pysideweakref.h b/sources/pyside6/libpyside/pysideweakref.h new file mode 100644 index 000000000..628c1eda4 --- /dev/null +++ b/sources/pyside6/libpyside/pysideweakref.h @@ -0,0 +1,56 @@ +/**************************************************************************** +** +** 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 __PYSIDEWEAKREF__ +#define __PYSIDEWEAKREF__ + +#include <pysidemacros.h> +#include <sbkpython.h> + +typedef void (*PySideWeakRefFunction)(void* userData); + +namespace PySide { namespace WeakRef { + +PYSIDE_API PyObject* create(PyObject* ob, PySideWeakRefFunction func, void* userData); + +} //PySide +} //WeakRef + + +#endif diff --git a/sources/pyside6/libpyside/signalmanager.cpp b/sources/pyside6/libpyside/signalmanager.cpp new file mode 100644 index 000000000..0992cfcfd --- /dev/null +++ b/sources/pyside6/libpyside/signalmanager.cpp @@ -0,0 +1,640 @@ +// -*- 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$ +** +****************************************************************************/ + +#include "signalmanager.h" +#include "pysidesignal.h" +#include "pysideproperty.h" +#include "pysideproperty_p.h" +#include "pyside.h" +#include "pyside_p.h" +#include "dynamicqmetaobject.h" +#include "pysidemetafunction_p.h" + +#include <autodecref.h> +#include <basewrapper.h> +#include <bindingmanager.h> +#include <gilstate.h> +#include <sbkconverter.h> +#include <sbkstring.h> +#include <sbkstaticstrings.h> + +#include <QtCore/QDebug> +#include <QtCore/QHash> + +#include <algorithm> +#include <limits> + +// 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 + +#if QSLOT_CODE != 1 || QSIGNAL_CODE != 2 +#error QSLOT_CODE and/or QSIGNAL_CODE changed! change the hardcoded stuff to the correct value! +#endif +#define PYSIDE_SLOT '1' +#define PYSIDE_SIGNAL '2' +#include "globalreceiverv2.h" + +namespace { + static PyObject *metaObjectAttr = 0; + + 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, 0); + auto meta = reinterpret_cast<PySide::MetaObjectBuilder *>(ptr); + SbkObject *wrapper = Shiboken::BindingManager::instance().retrieveWrapper(meta); + if (wrapper) + Shiboken::BindingManager::instance().releaseWrapper(wrapper); + delete meta; + } +} + +namespace PySide { + + +PyObjectWrapper::PyObjectWrapper() + :m_me(Py_None) +{ + // PYSIDE-813: When PYSIDE-164 was solved by adding some thread allowance, + // this code was no longer protected. It was hard to find this connection. + // See the website https://bugreports.qt.io/browse/PYSIDE-813 for details. + Shiboken::GilState gil; + Py_XINCREF(m_me); +} + +PyObjectWrapper::PyObjectWrapper(PyObject *me) + : m_me(me) +{ + Shiboken::GilState gil; + Py_XINCREF(m_me); +} + +PyObjectWrapper::PyObjectWrapper(const PyObjectWrapper &other) + : m_me(other.m_me) +{ + Shiboken::GilState gil; + Py_XINCREF(m_me); +} + +PyObjectWrapper::~PyObjectWrapper() +{ + // Check that Python is still initialized as sometimes this is called by a static destructor + // after Python interpeter is shutdown. + if (!Py_IsInitialized()) + return; + + Shiboken::GilState gil; + Py_XDECREF(m_me); +} + +void PyObjectWrapper::reset(PyObject *o) +{ + Shiboken::GilState gil; + Py_XINCREF(o); + Py_XDECREF(m_me); + m_me = o; +} + +PyObjectWrapper &PyObjectWrapper::operator=(const PySide::PyObjectWrapper &other) +{ + reset(other.m_me); + return *this; +} + +PyObjectWrapper::operator PyObject *() const +{ + return m_me; +} + +QDataStream &operator<<(QDataStream &out, const PyObjectWrapper &myObj) +{ + if (Py_IsInitialized() == 0) { + qWarning() << "Stream operator for PyObject called without python interpreter."; + return out; + } + + static PyObject *reduce_func = 0; + + Shiboken::GilState gil; + if (!reduce_func) { + Shiboken::AutoDecRef pickleModule(PyImport_ImportModule("pickle")); + reduce_func = PyObject_GetAttr(pickleModule, Shiboken::PyName::dumps()); + } + Shiboken::AutoDecRef repr(PyObject_CallFunctionObjArgs(reduce_func, (PyObject *)myObj, NULL)); + if (repr.object()) { + const char *buff = nullptr; + Py_ssize_t size = 0; + if (PyBytes_Check(repr.object())) { + buff = PyBytes_AS_STRING(repr.object()); + size = PyBytes_GET_SIZE(repr.object()); + } else if (Shiboken::String::check(repr.object())) { + buff = Shiboken::String::toCString(repr.object()); + size = Shiboken::String::len(repr.object()); + } + QByteArray data(buff, size); + out << data; + } + return out; +} + +QDataStream &operator>>(QDataStream &in, PyObjectWrapper &myObj) +{ + if (Py_IsInitialized() == 0) { + qWarning() << "Stream operator for PyObject called without python interpreter."; + return in; + } + + static PyObject *eval_func = 0; + + Shiboken::GilState gil; + if (!eval_func) { + Shiboken::AutoDecRef pickleModule(PyImport_ImportModule("pickle")); + eval_func = PyObject_GetAttr(pickleModule, Shiboken::PyName::loads()); + } + + QByteArray repr; + in >> repr; + Shiboken::AutoDecRef pyCode(PyBytes_FromStringAndSize(repr.data(), repr.size())); + Shiboken::AutoDecRef value(PyObject_CallFunctionObjArgs(eval_func, pyCode.object(), 0)); + if (!value.object()) + value.reset(Py_None); + myObj.reset(value); + return in; +} + +}; + +using namespace PySide; + +struct SignalManager::SignalManagerPrivate +{ + SharedMap m_globalReceivers; + + SignalManagerPrivate() + { + m_globalReceivers = SharedMap( new QMap<QByteArray, GlobalReceiverV2 *>() ); + } + + ~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()); + } + } +}; + +static void clearSignalManager() +{ + PySide::SignalManager::instance().clear(); +} + +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) +{ + return PyObject_PythonToCpp_PyObject_PTR; +} +static PyObject *PyObject_PTR_CppToPython_PyObject(const void *cppIn) +{ + auto pyOut = reinterpret_cast<PyObject *>(const_cast<void *>(cppIn)); + if (pyOut) + Py_INCREF(pyOut); + return pyOut; +} + +SignalManager::SignalManager() : m_d(new SignalManagerPrivate) +{ + // Register Qt primitive typedefs used on signals. + using namespace Shiboken; + + // Register PyObject type to use in queued signal and slot connections + qRegisterMetaType<PyObjectWrapper>("PyObject"); + + SbkConverter *converter = Shiboken::Conversions::createConverter(&PyBaseObject_Type, nullptr); + Shiboken::Conversions::setCppPointerToPythonFunction(converter, PyObject_PTR_CppToPython_PyObject); + Shiboken::Conversions::setPythonToCppPointerFunctions(converter, PyObject_PythonToCpp_PyObject_PTR, is_PyObject_PythonToCpp_PyObject_PTR_Convertible); + Shiboken::Conversions::registerConverterName(converter, "PyObject"); + Shiboken::Conversions::registerConverterName(converter, "object"); + Shiboken::Conversions::registerConverterName(converter, "PyObjectWrapper"); + Shiboken::Conversions::registerConverterName(converter, "PySide::PyObjectWrapper"); + + PySide::registerCleanupFunction(clearSignalManager); + + if (!metaObjectAttr) + metaObjectAttr = Shiboken::String::fromCString("__METAOBJECT__"); +} + +void SignalManager::clear() +{ + delete m_d; + m_d = new SignalManagerPrivate(); +} + +SignalManager::~SignalManager() +{ + delete m_d; +} + +SignalManager &SignalManager::instance() +{ + static SignalManager me; + return me; +} + +QObject *SignalManager::globalReceiver(QObject *sender, PyObject *callback) +{ + SharedMap globalReceivers = m_d->m_globalReceivers; + QByteArray hash = GlobalReceiverV2::hash(callback); + GlobalReceiverV2 *gr = nullptr; + auto it = globalReceivers->find(hash); + if (it == globalReceivers->end()) { + gr = new GlobalReceiverV2(callback, globalReceivers); + globalReceivers->insert(hash, gr); + if (sender) { + gr->incRef(sender); // create a link reference + gr->decRef(); // remove extra reference + } + } else { + gr = it.value(); + if (sender) + gr->incRef(sender); + } + + return reinterpret_cast<QObject *>(gr); +} + +int SignalManager::countConnectionsWith(const QObject *object) +{ + 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; +} + +void SignalManager::notifyGlobalReceiver(QObject *receiver) +{ + reinterpret_cast<GlobalReceiverV2 *>(receiver)->notify(); +} + +void SignalManager::releaseGlobalReceiver(const QObject *source, QObject *receiver) +{ + auto gr = reinterpret_cast<GlobalReceiverV2 *>(receiver); + gr->decRef(source); +} + +int SignalManager::globalReceiverSlotIndex(QObject *receiver, const char *signature) const +{ + return reinterpret_cast<GlobalReceiverV2 *>(receiver)->addSlot(signature); +} + +bool SignalManager::emitSignal(QObject *source, const char *signal, PyObject *args) +{ + if (!Signal::checkQtSignal(signal)) + return false; + 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 false; +} + +int SignalManager::qt_metacall(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; + } + + 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; + } + } + + 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; + + default: + qWarning("Unsupported meta invocation type."); + } + + // 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 (call == QMetaObject::InvokeMetaMethod) { + id = id - methodCount; + } else { + id = id - propertyCount; + } + + if (pp || pp_name) { + Shiboken::GilState gil; + Py_XDECREF(pp); + Py_XDECREF(pp_name); + } + + // 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); + } + } + + return id; +} + +int SignalManager::callPythonMetaMethod(const QMetaMethod &method, void **args, PyObject *pyMethod, bool isShortCuit) +{ + Q_ASSERT(pyMethod); + + Shiboken::GilState gil; + PyObject *pyArguments = nullptr; + + if (isShortCuit){ + pyArguments = reinterpret_cast<PyObject *>(args[1]); + } else { + pyArguments = parseArguments(method.parameterTypes(), args); + } + + if (pyArguments) { + Shiboken::Conversions::SpecificConverter *retConverter = nullptr; + 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); + return -1; + } + } + + Shiboken::AutoDecRef retval(PyObject_CallObject(pyMethod, pyArguments)); + + if (!isShortCuit && pyArguments){ + Py_DECREF(pyArguments); + } + + if (!retval.isNull() && retval != Py_None && !PyErr_Occurred() && retConverter) { + retConverter->toCpp(retval, args[0]); + } + delete retConverter; + } + + return -1; +} + +bool SignalManager::registerMetaMethod(QObject *source, const char *signature, QMetaMethod::MethodType type) +{ + int ret = registerMetaMethodGetIndex(source, signature, type); + return (ret != -1); +} + +static MetaObjectBuilder *metaBuilderFromDict(PyObject *dict) +{ + // PYSIDE-803: The dict in this function is the ob_dict of an SbkObject. + // The "metaObjectAttr" entry is only handled in this file. There is no + // way in this function to involve the interpreter. Therefore, we need + // no GIL. + // Note that "SignalManager::registerMetaMethodGetIndex" has write actions + // that might involve the interpreter, but in that context the GIL is held. + if (!dict || !PyDict_Contains(dict, metaObjectAttr)) + return nullptr; + + // PYSIDE-813: The above assumption is not true in debug mode: + // PyDict_GetItem would touch PyThreadState_GET and the global error state. + // PyDict_GetItemWithError instead can work without GIL. + PyObject *pyBuilder = PyDict_GetItemWithError(dict, metaObjectAttr); + return reinterpret_cast<MetaObjectBuilder *>(PyCapsule_GetPointer(pyBuilder, nullptr)); +} + +int SignalManager::registerMetaMethodGetIndex(QObject *source, const char *signature, QMetaMethod::MethodType type) +{ + if (!source) { + qWarning("SignalManager::registerMetaMethodGetIndex(\"%s\") called with source=nullptr.", + signature); + return -1; + } + const QMetaObject *metaObject = source->metaObject(); + int methodIndex = metaObject->indexOfMethod(signature); + // Create the dynamic signal is needed + if (methodIndex == -1) { + SbkObject *self = Shiboken::BindingManager::instance().retrieveWrapper(source); + if (!Shiboken::Object::hasCppWrapper(self)) { + qWarning() << "Invalid Signal signature:" << signature; + 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, 0, destroyMetaObject); + PyObject_SetAttr(pySelf, metaObjectAttr, pyDmo); + Py_DECREF(pyDmo); + } + + if (type == QMetaMethod::Signal) + return dmo->addSignal(signature); + else + return dmo->addSlot(signature); + } + } + return methodIndex; +} + +const QMetaObject *SignalManager::retrieveMetaObject(PyObject *self) +{ + // PYSIDE-803: Avoid the GIL in SignalManager::retrieveMetaObject + // This function had the GIL. We do not use the GIL unless we have to. + // metaBuilderFromDict accesses a Python dict, but in that context there + // is no way to reach the interpreter, see "metaBuilderFromDict". + // + // The update function is MetaObjectBuilderPrivate::update in + // dynamicmetaobject.c . That function now uses the GIL when the + // m_dirty flag is set. + Q_ASSERT(self); + + MetaObjectBuilder *builder = metaBuilderFromDict(reinterpret_cast<SbkObject *>(self)->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) +{ + int argsSize = paramTypes.count(); + PyObject *preparedArgs = PyTuple_New(argsSize); + + for (int i = 0, max = argsSize; i < max; ++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); + Py_DECREF(preparedArgs); + return 0; + } + } + 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 diff --git a/sources/pyside6/libpyside/signalmanager.h b/sources/pyside6/libpyside/signalmanager.h new file mode 100644 index 000000000..fe077bd1a --- /dev/null +++ b/sources/pyside6/libpyside/signalmanager.h @@ -0,0 +1,121 @@ +/**************************************************************************** +** +** 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 SIGNALMANAGER_H +#define SIGNALMANAGER_H + +#include "pysidemacros.h" + +#include <sbkpython.h> +#include <shibokenmacros.h> + +#include <QtCore/QMetaMethod> + +QT_FORWARD_DECLARE_CLASS(QDataStream) + +namespace PySide +{ + +/// Thin wrapper for PyObject which increases the reference count at the constructor but *NOT* at destructor. +class PYSIDE_API PyObjectWrapper +{ +public: + PyObjectWrapper(PyObjectWrapper&&) = delete; + PyObjectWrapper& operator=(PyObjectWrapper &&) = delete; + + PyObjectWrapper(); + explicit PyObjectWrapper(PyObject* me); + PyObjectWrapper(const PyObjectWrapper &other); + PyObjectWrapper& operator=(const PyObjectWrapper &other); + + void reset(PyObject *o); + + ~PyObjectWrapper(); + operator PyObject*() const; + +private: + PyObject* m_me; +}; + +PYSIDE_API QDataStream &operator<<(QDataStream& out, const PyObjectWrapper& myObj); +PYSIDE_API QDataStream &operator>>(QDataStream& in, PyObjectWrapper& myObj); + +class PYSIDE_API SignalManager +{ + Q_DISABLE_COPY(SignalManager) +public: + static SignalManager& instance(); + + QObject* globalReceiver(QObject* sender, PyObject* callback); + void releaseGlobalReceiver(const QObject* sender, QObject* receiver); + int globalReceiverSlotIndex(QObject* sender, const char* slotSignature) const; + void notifyGlobalReceiver(QObject* receiver); + + bool emitSignal(QObject* source, const char* signal, PyObject* args); + static int qt_metacall(QObject* object, QMetaObject::Call call, int id, void** args); + + // Used to register a new signal/slot on QMetaobject of source. + static bool registerMetaMethod(QObject* source, const char* signature, QMetaMethod::MethodType type); + static int registerMetaMethodGetIndex(QObject* source, const char* signature, QMetaMethod::MethodType type); + + // 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(); + + // 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); + +private: + struct SignalManagerPrivate; + SignalManagerPrivate* m_d; + + SignalManager(); + ~SignalManager(); +}; + +} + +Q_DECLARE_METATYPE(PySide::PyObjectWrapper) + +#endif |
