diff options
author | Friedemann Kleint <Friedemann.Kleint@qt.io> | 2020-06-20 10:22:51 +0200 |
---|---|---|
committer | Friedemann Kleint <Friedemann.Kleint@qt.io> | 2020-06-20 10:23:16 +0200 |
commit | 54e7da4895cd5d9228cef3e4e3ebc61c5109ed12 (patch) | |
tree | 46208372751b0f9f99de50babe5a5e635d8d6985 | |
parent | 44cb6278265ddb2d970d59469a5676d18e547871 (diff) | |
parent | 479a6bfbf7fe82a387d313a8bed95f026d9d7922 (diff) |
Merge remote-tracking branch 'origin/5.15' into dev
Change-Id: I59be4217917d79b90169fe494438781b5bdcebf1
54 files changed, 2281 insertions, 323 deletions
diff --git a/build_scripts/platforms/windows_desktop.py b/build_scripts/platforms/windows_desktop.py index 750a064b4..49c384bc7 100644 --- a/build_scripts/platforms/windows_desktop.py +++ b/build_scripts/platforms/windows_desktop.py @@ -251,7 +251,8 @@ def copy_msvc_redist_files(vars, redist_target_path): "vcamp140.dll", "vccorlib140.dll", "vcomp140.dll", - "vcruntime140.dll" + "vcruntime140.dll", + "vcruntime140_1.dll" ] # Make a directory where the files should be extracted. @@ -262,9 +263,9 @@ def copy_msvc_redist_files(vars, redist_target_path): in_coin = os.environ.get('COIN_LAUNCH_PARAMETERS', None) if in_coin is not None: redist_url = "http://download.qt.io/development_releases/prebuilt/vcredist/" - zip_file = "pyside_qt_deps_64.7z" + zip_file = "pyside_qt_deps_64_2019.7z" if "{target_arch}".format(**vars) == "32": - zip_file = "pyside_qt_deps_32.7z" + zip_file = "pyside_qt_deps_32_2019.7z" download_and_extract_7z(redist_url + zip_file, redist_target_path) else: print("Qt dependency DLLs (MSVC redist) will not be downloaded and extracted.") diff --git a/examples/widgetbinding/CMakeLists.txt b/examples/widgetbinding/CMakeLists.txt new file mode 100644 index 000000000..a557f90ec --- /dev/null +++ b/examples/widgetbinding/CMakeLists.txt @@ -0,0 +1,275 @@ +cmake_minimum_required(VERSION 3.1) +cmake_policy(VERSION 3.1) + +# Enable policy to not use RPATH settings for install_name on macOS. +if(POLICY CMP0068) + cmake_policy(SET CMP0068 NEW) +endif() + +# Enable policy to run automoc on generated files. +if(POLICY CMP0071) + cmake_policy(SET CMP0071 NEW) +endif() + +# Consider changing the project name to something relevant for you. +project(wiggly LANGUAGES CXX) +set(CMAKE_INCLUDE_CURRENT_DIR ON) +set(CMAKE_AUTOMOC ON) +find_package(Qt5 5.12 REQUIRED COMPONENTS Core Gui Widgets) + +# ================================ General configuration ====================================== + +# Set CPP standard to C++11 minimum. +set(CMAKE_CXX_STANDARD 11) + +# The wiggly library for which we will create bindings. You can change the name to something +# relevant for your project. +set(wiggly_library "libwiggly") + +# The name of the generated bindings module (as imported in Python). You can change the name +# to something relevant for your project. +set(bindings_library "wiggly") + +# The header file with all the types and functions for which bindings will be generated. +# Usually it simply includes other headers of the library you are creating bindings for. +set(wrapped_header ${CMAKE_SOURCE_DIR}/bindings.h) + +# The typesystem xml file which defines the relationships between the C++ types / functions +# and the corresponding Python equivalents. +set(typesystem_file ${CMAKE_SOURCE_DIR}/bindings.xml) + +# Specify which C++ files will be generated by shiboken. This includes the module wrapper +# and a '.cpp' file per C++ type. These are needed for generating the module shared +# library. +set(generated_sources + ${CMAKE_CURRENT_BINARY_DIR}/${bindings_library}/wiggly_module_wrapper.cpp + ${CMAKE_CURRENT_BINARY_DIR}/${bindings_library}/wigglywidget_wrapper.cpp) + + +# ================================== Shiboken detection ====================================== +# Use provided python interpreter if given. +if(NOT python_interpreter) + find_program(python_interpreter "python") +endif() +message(STATUS "Using python interpreter: ${python_interpreter}") + +# Macro to get various pyside / python include / link flags and paths. +# Uses the not entirely supported utils/pyside2_config.py file. +macro(pyside2_config option output_var) + if(${ARGC} GREATER 2) + set(is_list ${ARGV2}) + else() + set(is_list "") + endif() + + execute_process( + COMMAND ${python_interpreter} "${CMAKE_SOURCE_DIR}/../utils/pyside2_config.py" + ${option} + OUTPUT_VARIABLE ${output_var} + OUTPUT_STRIP_TRAILING_WHITESPACE) + + if ("${${output_var}}" STREQUAL "") + message(FATAL_ERROR "Error: Calling pyside2_config.py ${option} returned no output.") + endif() + if(is_list) + string (REPLACE " " ";" ${output_var} "${${output_var}}") + endif() +endmacro() + +# Query for the shiboken generator path, Python path, include paths and linker flags. +pyside2_config(--shiboken2-module-path shiboken2_module_path) +pyside2_config(--shiboken2-generator-path shiboken2_generator_path) +pyside2_config(--pyside2-path pyside2_path) +pyside2_config(--pyside2-include-path pyside2_include_dir 1) +pyside2_config(--python-include-path python_include_dir) +pyside2_config(--shiboken2-generator-include-path shiboken_include_dir 1) +pyside2_config(--shiboken2-module-shared-libraries-cmake shiboken_shared_libraries 0) +pyside2_config(--python-link-flags-cmake python_linking_data 0) +pyside2_config(--pyside2-shared-libraries-cmake pyside2_shared_libraries 0) + +set(shiboken_path "${shiboken2_generator_path}/shiboken2${CMAKE_EXECUTABLE_SUFFIX}") +if(NOT EXISTS ${shiboken_path}) + message(FATAL_ERROR "Shiboken executable not found at path: ${shiboken_path}") +endif() + + +# ==================================== RPATH configuration ==================================== + + +# ============================================================================================= +# !!! (The section below is deployment related, so in a real world application you will want to +# take care of this properly with some custom script or tool). +# ============================================================================================= +# Enable rpaths so that the built shared libraries find their dependencies. +set(CMAKE_SKIP_BUILD_RPATH FALSE) +set(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE) +set(CMAKE_INSTALL_RPATH ${shiboken2_module_path} ${CMAKE_CURRENT_SOURCE_DIR}) +set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) +# ============================================================================================= +# !!! End of dubious section. +# ============================================================================================= + + +# =============================== CMake target - wiggly_library =============================== + + +# Get all relevant Qt include dirs, to pass them on to shiboken. +get_property(QT_CORE_INCLUDE_DIRS TARGET Qt5::Core PROPERTY INTERFACE_INCLUDE_DIRECTORIES) +get_property(QT_GUI_INCLUDE_DIRS TARGET Qt5::Gui PROPERTY INTERFACE_INCLUDE_DIRECTORIES) +get_property(QT_WIDGETS_INCLUDE_DIRS TARGET Qt5::Widgets PROPERTY INTERFACE_INCLUDE_DIRECTORIES) +set(QT_INCLUDE_DIRS ${QT_CORE_INCLUDE_DIRS} ${QT_GUI_INCLUDE_DIRS} ${QT_WIDGETS_INCLUDE_DIRS}) +set(INCLUDES "") +foreach(INCLUDE_DIR ${QT_INCLUDE_DIRS}) + list(APPEND INCLUDES "-I${INCLUDE_DIR}") +endforeach() + +# On macOS, check if Qt is a framework build. This affects how include paths should be handled. +get_target_property(QtCore_is_framework Qt5::Core FRAMEWORK) +if (QtCore_is_framework) + get_target_property(qt_core_library_location Qt5::Core LOCATION) + get_filename_component(qt_core_library_location_dir "${qt_core_library_location}" DIRECTORY) + get_filename_component(lib_dir "${qt_core_library_location_dir}/../" ABSOLUTE) + list(APPEND INCLUDES "--framework-include-paths=${lib_dir}") +endif() + +# We need to include the headers for the module bindings that we use. +set(pyside2_additional_includes "") +foreach(INCLUDE_DIR ${pyside2_include_dir}) + list(APPEND pyside2_additional_includes "${INCLUDE_DIR}/QtCore") + list(APPEND pyside2_additional_includes "${INCLUDE_DIR}/QtGui") + list(APPEND pyside2_additional_includes "${INCLUDE_DIR}/QtWidgets") +endforeach() + + +# Define the wiggly shared library for which we will create bindings. +set(${wiggly_library}_sources wigglywidget.cpp) +add_library(${wiggly_library} SHARED ${${wiggly_library}_sources}) +set_property(TARGET ${wiggly_library} PROPERTY PREFIX "") + +# Needed mostly on Windows to export symbols, and create a .lib file, otherwise the binding +# library can't link to the wiggly library. +target_compile_definitions(${wiggly_library} PRIVATE BINDINGS_BUILD) + + +# ====================== Shiboken target for generating binding C++ files ==================== + + +# Set up the options to pass to shiboken. +set(shiboken_options --generator-set=shiboken --enable-parent-ctor-heuristic + --enable-pyside-extensions --enable-return-value-heuristic --use-isnull-as-nb_nonzero + --avoid-protected-hack + ${INCLUDES} + -I${CMAKE_SOURCE_DIR} + -T${CMAKE_SOURCE_DIR} + -T${pyside2_path}/typesystems + --output-directory=${CMAKE_CURRENT_BINARY_DIR} + ) + +set(generated_sources_dependencies ${wrapped_header} ${typesystem_file}) + +# Add custom target to run shiboken to generate the binding cpp files. +add_custom_command(OUTPUT ${generated_sources} + COMMAND ${shiboken_path} + ${shiboken_options} ${wrapped_header} ${typesystem_file} + DEPENDS ${generated_sources_dependencies} + #IMPLICIT_DEPENDS CXX ${wrapped_header} + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + COMMENT "Running generator for ${typesystem_file}.") + + +# =============================== CMake target - bindings_library ============================= + + +# Set the cpp files which will be used for the bindings library. +set(${bindings_library}_sources ${generated_sources}) + +# Define and build the bindings library. +add_library(${bindings_library} SHARED ${${bindings_library}_sources}) + + +# Apply relevant include and link flags. +target_include_directories(${bindings_library} PRIVATE ${pyside2_additional_includes}) +target_include_directories(${bindings_library} PRIVATE ${pyside2_include_dir}) +target_include_directories(${bindings_library} PRIVATE ${python_include_dir}) +target_include_directories(${bindings_library} PRIVATE ${shiboken_include_dir}) + +target_link_libraries(${wiggly_library} PRIVATE Qt5::Widgets) +target_link_libraries(${bindings_library} PRIVATE Qt5::Widgets) +target_link_libraries(${bindings_library} PRIVATE ${wiggly_library}) +target_link_libraries(${bindings_library} PRIVATE ${pyside2_shared_libraries}) +target_link_libraries(${bindings_library} PRIVATE ${shiboken_shared_libraries}) + +# Adjust the name of generated module. +set_property(TARGET ${bindings_library} PROPERTY PREFIX "") +set_property(TARGET ${bindings_library} PROPERTY OUTPUT_NAME + "${bindings_library}${PYTHON_EXTENSION_SUFFIX}") +if(WIN32) + set_property(TARGET ${bindings_library} PROPERTY SUFFIX ".pyd") +endif() + +# Make sure the linker doesn't complain about not finding Python symbols on macOS. +if(APPLE) + set_target_properties(${bindings_library} PROPERTIES LINK_FLAGS "-undefined dynamic_lookup") +endif(APPLE) + +# Find and link to the python import library only on Windows. +# On Linux and macOS, the undefined symbols will get resolved by the dynamic linker +# (the symbols will be picked up in the Python executable). +if (WIN32) + list(GET python_linking_data 0 python_libdir) + list(GET python_linking_data 1 python_lib) + find_library(python_link_flags ${python_lib} PATHS ${python_libdir} HINTS ${python_libdir}) + target_link_libraries(${bindings_library} PRIVATE ${python_link_flags}) +endif() + +# ================================= Dubious deployment section ================================ + +set(windows_shiboken_shared_libraries) + +if(WIN32) + # ========================================================================================= + # !!! (The section below is deployment related, so in a real world application you will + # want to take care of this properly (this is simply to eliminate errors that users usually + # encounter. + # ========================================================================================= + # Circumvent some "#pragma comment(lib)"s in "include/pyconfig.h" which might force to link + # against a wrong python shared library. + + set(python_versions_list 3 32 33 34 35 36 37 38) + set(python_additional_link_flags "") + foreach(ver ${python_versions_list}) + set(python_additional_link_flags + "${python_additional_link_flags} /NODEFAULTLIB:\"python${ver}_d.lib\"") + set(python_additional_link_flags + "${python_additional_link_flags} /NODEFAULTLIB:\"python${ver}.lib\"") + endforeach() + + set_target_properties(${bindings_library} + PROPERTIES LINK_FLAGS "${python_additional_link_flags}") + + # Compile a list of shiboken shared libraries to be installed, so that + # the user doesn't have to set the PATH manually to point to the PySide2 package. + foreach(library_path ${shiboken_shared_libraries}) + string(REGEX REPLACE ".lib$" ".dll" library_path ${library_path}) + file(TO_CMAKE_PATH ${library_path} library_path) + list(APPEND windows_shiboken_shared_libraries "${library_path}") + endforeach() + # ========================================================================================= + # !!! End of dubious section. + # ========================================================================================= +endif() + +# ============================================================================================= +# !!! (The section below is deployment related, so in a real world application you will want to +# take care of this properly with some custom script or tool). +# ============================================================================================= +# Install the library and the bindings module into the source folder near the main.py file, so +# that the Python interpeter successfully imports the used module. +install(TARGETS ${bindings_library} ${wiggly_library} + LIBRARY DESTINATION ${CMAKE_CURRENT_SOURCE_DIR} + RUNTIME DESTINATION ${CMAKE_CURRENT_SOURCE_DIR} + ) +install(FILES ${windows_shiboken_shared_libraries} DESTINATION ${CMAKE_CURRENT_SOURCE_DIR}) +# ============================================================================================= +# !!! End of dubious section. +# ============================================================================================= diff --git a/examples/widgetbinding/README.md b/examples/widgetbinding/README.md new file mode 100644 index 000000000..f58a49627 --- /dev/null +++ b/examples/widgetbinding/README.md @@ -0,0 +1,74 @@ +# WigglyWidget + +The original Qt/C++ example can be found here: +https://doc.qt.io/qt-5/qtwidgets-widgets-wiggly-example.html + +This example shows how to interact with a custom widget from two +different ways: + + * A full Python translation from a C++ example, + * A Python binding generated from the C++ file. + + +The original example contained three different files: + * `main.cpp/h`, which was translated to `main.py`, + * `dialog.cpp/h`, which was translated to `dialog.py`, + * `wigglywidget.cpp/h`, which was translated to `wigglywidget.py`, + but also remains as is, to enable the binding generation through + Shiboken. + +In the `dialog.py` file you will find two imports that will be related +to each of the two approaches described before:: + + + # Python translated file + from wigglywidget import WigglyWidget + + # Binding module create with Shiboken + from wiggly import WigglyWidget + + +## Steps to build the bindings + +The most important files are: + * `bindings.xml`, to specify the class that we want to expose from C++ + to Python, + * `bindings.h` to include the header of the classes we want to expose + * `CMakeList.txt`, with all the instructions to build the shared libraries + (DLL, or dylib) + * `pyside2_config.py` which is located in the utils directory, one level + up, to get the path for Shiboken and PySide. + +Now create a `build/` directory, and from inside run `cmake ..` to use +the provided `CMakeLists.txt`. +To build, just run `make`, and `make install` to copy the generated files +to the main example directory to be able to run the final example: +`python main.py`. +You should be able to see two identical custom widgets, one being the +Python translation, and the other one being the C++ one. + +### Windows + +For windows it's recommended to use either `nmake`, `jom` or `ninja`, +when running cmake. + +```bash +cmake -H.. -B. -G "NMake Makefiles" -DCMAKE_BUILD_TYPE=Release # for nmake +cmake -H.. -B. -G "NMake Makefiles JOM" -DCMAKE_BUILD_TYPE=Release # for jom +cmake -H.. -B. -G Ninja -DCMAKE_BUILD_TYPE=Release # for ninja +``` + +### Linux, macOS + +Generally using `make` will be enough, but as in the Windows case, you can use +ninja to build the project. + +```bash +cmake -H.. -B. -G Ninja -DCMAKE_BUILD_TYPE=Release +``` + +## Final words + +Since this example originated by mixing the concepts of the `scriptableapplication` +and `samplebinding` examples, you can complement this README with the ones in +those directories. diff --git a/examples/widgetbinding/bindings.h b/examples/widgetbinding/bindings.h new file mode 100644 index 000000000..d59222656 --- /dev/null +++ b/examples/widgetbinding/bindings.h @@ -0,0 +1,54 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt for Python examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** 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. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef BINDINGS_H +#define BINDINGS_H +#include "wigglywidget.h" +#endif // BINDINGS_H diff --git a/examples/widgetbinding/bindings.xml b/examples/widgetbinding/bindings.xml new file mode 100644 index 000000000..07f1c89c9 --- /dev/null +++ b/examples/widgetbinding/bindings.xml @@ -0,0 +1,56 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt for Python examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** 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. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +--> +<typesystem package="wiggly"> + <load-typesystem name="typesystem_widgets.xml" generate="no"/> + <object-type name="WigglyWidget"/> +</typesystem> diff --git a/examples/widgetbinding/dialog.py b/examples/widgetbinding/dialog.py new file mode 100644 index 000000000..e52155999 --- /dev/null +++ b/examples/widgetbinding/dialog.py @@ -0,0 +1,77 @@ +############################################################################ +## +## Copyright (C) 2020 The Qt Company Ltd. +## Contact: https://www.qt.io/licensing/ +## +## This file is part of the Qt for Python examples of the Qt Toolkit. +## +## $QT_BEGIN_LICENSE:BSD$ +## 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. +## +## BSD License Usage +## Alternatively, you may use this file under the terms of the BSD license +## as follows: +## +## "Redistribution and use in source and binary forms, with or without +## modification, are permitted provided that the following conditions are +## met: +## * Redistributions of source code must retain the above copyright +## notice, this list of conditions and the following disclaimer. +## * Redistributions in binary form must reproduce the above copyright +## notice, this list of conditions and the following disclaimer in +## the documentation and/or other materials provided with the +## distribution. +## * Neither the name of The Qt Company Ltd nor the names of its +## contributors may be used to endorse or promote products derived +## from this software without specific prior written permission. +## +## +## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +## +## $QT_END_LICENSE$ +## +############################################################################ + +from PySide2.QtWidgets import QDialog, QLineEdit, QVBoxLayout + +# Python binding from the C++ widget +from wiggly import WigglyWidget as WigglyWidgetCPP + +# Python-only widget +from wigglywidget import WigglyWidget as WigglyWidgetPY + + +class Dialog(QDialog): + def __init__(self, parent=None): + super(Dialog, self).__init__(parent) + wiggly_widget_py = WigglyWidgetPY(self) + wiggly_widget_cpp = WigglyWidgetCPP(self) + lineEdit = QLineEdit(self) + + layout = QVBoxLayout(self) + layout.addWidget(wiggly_widget_py) + layout.addWidget(wiggly_widget_cpp) + layout.addWidget(lineEdit) + + lineEdit.textChanged.connect(wiggly_widget_py.setText) + lineEdit.textChanged.connect(wiggly_widget_cpp.setText) + lineEdit.setText("Hello world!") + + self.setWindowTitle("Wiggly") + self.resize(360, 145) diff --git a/examples/widgetbinding/macros.h b/examples/widgetbinding/macros.h new file mode 100644 index 000000000..224fada68 --- /dev/null +++ b/examples/widgetbinding/macros.h @@ -0,0 +1,63 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt for Python examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** 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. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef MACROS_H +#define MACROS_H + +#include <QtCore/qglobal.h> + +// Export symbols when creating .dll and .lib, and import them when using .lib. +#if BINDINGS_BUILD +# define BINDINGS_API Q_DECL_EXPORT +#else +# define BINDINGS_API Q_DECL_IMPORT +#endif + +#endif // MACROS_H diff --git a/examples/widgetbinding/main.py b/examples/widgetbinding/main.py new file mode 100644 index 000000000..556eb2638 --- /dev/null +++ b/examples/widgetbinding/main.py @@ -0,0 +1,61 @@ +############################################################################ +## +## Copyright (C) 2020 The Qt Company Ltd. +## Contact: https://www.qt.io/licensing/ +## +## This file is part of the Qt for Python examples of the Qt Toolkit. +## +## $QT_BEGIN_LICENSE:BSD$ +## 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. +## +## BSD License Usage +## Alternatively, you may use this file under the terms of the BSD license +## as follows: +## +## "Redistribution and use in source and binary forms, with or without +## modification, are permitted provided that the following conditions are +## met: +## * Redistributions of source code must retain the above copyright +## notice, this list of conditions and the following disclaimer. +## * Redistributions in binary form must reproduce the above copyright +## notice, this list of conditions and the following disclaimer in +## the documentation and/or other materials provided with the +## distribution. +## * Neither the name of The Qt Company Ltd nor the names of its +## contributors may be used to endorse or promote products derived +## from this software without specific prior written permission. +## +## +## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +## +## $QT_END_LICENSE$ +## +############################################################################ + +import sys + +from PySide2.QtWidgets import QApplication + +from dialog import Dialog + +if __name__ == "__main__": + app = QApplication() + w = Dialog() + w.show() + sys.exit(app.exec_()) diff --git a/examples/widgetbinding/wigglywidget.cpp b/examples/widgetbinding/wigglywidget.cpp new file mode 100644 index 000000000..ab549ef07 --- /dev/null +++ b/examples/widgetbinding/wigglywidget.cpp @@ -0,0 +1,111 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** 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. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "wigglywidget.h" + +#include <QFontMetrics> +#include <QPainter> +#include <QTimerEvent> + +//! [0] +WigglyWidget::WigglyWidget(QWidget *parent) + : QWidget(parent), step(0) +{ + setBackgroundRole(QPalette::Midlight); + setAutoFillBackground(true); + + QFont newFont = font(); + newFont.setPointSize(newFont.pointSize() + 20); + setFont(newFont); + + timer.start(60, this); +} +//! [0] + +//! [1] +void WigglyWidget::paintEvent(QPaintEvent * /* event */) +//! [1] //! [2] +{ + static constexpr int sineTable[16] = { + 0, 38, 71, 92, 100, 92, 71, 38, 0, -38, -71, -92, -100, -92, -71, -38 + }; + + QFontMetrics metrics(font()); + int x = (width() - metrics.horizontalAdvance(text)) / 2; + int y = (height() + metrics.ascent() - metrics.descent()) / 2; + QColor color; +//! [2] + +//! [3] + QPainter painter(this); +//! [3] //! [4] + for (int i = 0; i < text.size(); ++i) { + int index = (step + i) % 16; + color.setHsv((15 - index) * 16, 255, 191); + painter.setPen(color); + painter.drawText(x, y - ((sineTable[index] * metrics.height()) / 400), + QString(text[i])); + x += metrics.horizontalAdvance(text[i]); + } +} +//! [4] + +//! [5] +void WigglyWidget::timerEvent(QTimerEvent *event) +//! [5] //! [6] +{ + if (event->timerId() == timer.timerId()) { + ++step; + update(); + } else { + QWidget::timerEvent(event); + } +//! [6] +} diff --git a/examples/widgetbinding/wigglywidget.h b/examples/widgetbinding/wigglywidget.h new file mode 100644 index 000000000..d08db05d5 --- /dev/null +++ b/examples/widgetbinding/wigglywidget.h @@ -0,0 +1,81 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** 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. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef WIGGLYWIDGET_H +#define WIGGLYWIDGET_H + +#include "macros.h" + +#include <QWidget> +#include <QBasicTimer> + +//! [0] +class BINDINGS_API WigglyWidget : public QWidget +{ + Q_OBJECT + +public: + WigglyWidget(QWidget *parent = nullptr); + +public slots: + void setText(const QString &newText) { text = newText; } + +protected: + void paintEvent(QPaintEvent *event) override; + void timerEvent(QTimerEvent *event) override; + +private: + QBasicTimer timer; + QString text; + int step; +}; +//! [0] + +#endif diff --git a/examples/widgetbinding/wigglywidget.py b/examples/widgetbinding/wigglywidget.py new file mode 100644 index 000000000..50a061074 --- /dev/null +++ b/examples/widgetbinding/wigglywidget.py @@ -0,0 +1,97 @@ +############################################################################ +## +## Copyright (C) 2020 The Qt Company Ltd. +## Contact: https://www.qt.io/licensing/ +## +## This file is part of the Qt for Python examples of the Qt Toolkit. +## +## $QT_BEGIN_LICENSE:BSD$ +## 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. +## +## BSD License Usage +## Alternatively, you may use this file under the terms of the BSD license +## as follows: +## +## "Redistribution and use in source and binary forms, with or without +## modification, are permitted provided that the following conditions are +## met: +## * Redistributions of source code must retain the above copyright +## notice, this list of conditions and the following disclaimer. +## * Redistributions in binary form must reproduce the above copyright +## notice, this list of conditions and the following disclaimer in +## the documentation and/or other materials provided with the +## distribution. +## * Neither the name of The Qt Company Ltd nor the names of its +## contributors may be used to endorse or promote products derived +## from this software without specific prior written permission. +## +## +## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +## +## $QT_END_LICENSE$ +## +############################################################################ + +from PySide2.QtCore import QBasicTimer +from PySide2.QtGui import QColor, QFontMetrics, QPainter, QPalette +from PySide2.QtWidgets import QWidget + + +class WigglyWidget(QWidget): + def __init__(self, parent=None): + super(WigglyWidget, self).__init__(parent) + self.step = 0 + self.text = "" + self.setBackgroundRole(QPalette.Midlight) + self.setAutoFillBackground(True) + + newFont = self.font() + newFont.setPointSize(newFont.pointSize() + 20) + self.setFont(newFont) + + self.timer = QBasicTimer() + self.timer.start(60, self) + + def paintEvent(self, event): + sineTable = [0, 38, 71, 92, 100, 92, 71, 38, 0, -38, -71, -92, -100, + -92, -71, -38] + + metrics = QFontMetrics(self.font()) + x = (self.width() - metrics.horizontalAdvance(self.text)) / 2 + y = (self.height() + metrics.ascent() - metrics.descent()) / 2 + color = QColor() + + painter = QPainter(self) + for i in range(len(self.text)): + index = (self.step + i) % 16 + color.setHsv((15 - index) * 16, 255, 191) + painter.setPen(color) + painter.drawText(x, y - ((sineTable[index] * metrics.height()) / 400), + str(self.text[i])) + x += metrics.horizontalAdvance(self.text[i]) + + def timerEvent(self, event): + if event.timerId() == self.timer.timerId(): + self.step += 1 + self.update() + else: + QWidget.timerEvent(event) + + def setText(self, text): + self.text = text diff --git a/sources/pyside2/PySide2/QtCore/typesystem_core_common.xml b/sources/pyside2/PySide2/QtCore/typesystem_core_common.xml index 0cdd5e2f7..e79123398 100644 --- a/sources/pyside2/PySide2/QtCore/typesystem_core_common.xml +++ b/sources/pyside2/PySide2/QtCore/typesystem_core_common.xml @@ -656,6 +656,13 @@ </namespace-type> + <add-function signature="QEnum(PyObject*)" return-type="PyObject*"> + <inject-code class="target" position="beginning" file="../glue/qtcore.cpp" snippet="qt-qenum"/> + </add-function> + <add-function signature="QFlag(PyObject*)" return-type="PyObject*"> + <inject-code class="target" position="beginning" file="../glue/qtcore.cpp" snippet="qt-qflag"/> + </add-function> + <add-function signature="qAbs(double)" return-type="double"> <inject-code class="target" position="beginning" file="../glue/qtcore.cpp" snippet="qt-qabs"/> </add-function> @@ -1467,6 +1474,7 @@ <modify-function signature="msleep(unsigned long)" allow-thread="yes"/> <modify-function signature="sleep(unsigned long)" allow-thread="yes"/> <modify-function signature="usleep(unsigned long)" allow-thread="yes"/> + <modify-function signature="wait(QDeadlineTimer)" allow-thread="yes"/> <modify-function signature="wait(unsigned long)" allow-thread="yes"/> <modify-function signature="yieldCurrentThread()" allow-thread="yes"/> <modify-function signature="start(QThread::Priority)" allow-thread="yes"> @@ -2420,7 +2428,9 @@ </modify-function> </object-type> <object-type name="QWaitCondition"> + <modify-function signature="wait(QMutex*,QDeadlineTimer)" allow-thread="yes"/> <modify-function signature="wait(QMutex*,unsigned long)" allow-thread="yes"/> + <modify-function signature="wait(QReadWriteLock*,QDeadlineTimer)" allow-thread="yes"/> <modify-function signature="wait(QReadWriteLock*,unsigned long)" allow-thread="yes"/> </object-type> <object-type name="QFileSystemWatcher"> diff --git a/sources/pyside2/PySide2/glue/qtcore.cpp b/sources/pyside2/PySide2/glue/qtcore.cpp index 1e9d9636e..8bd2baac1 100644 --- a/sources/pyside2/PySide2/glue/qtcore.cpp +++ b/sources/pyside2/PySide2/glue/qtcore.cpp @@ -587,6 +587,14 @@ Py_END_ALLOW_THREADS PySide::runCleanupFunctions(); // @snippet moduleshutdown +// @snippet qt-qenum +%PYARG_0 = PySide::QEnum::QEnumMacro(%1, false); +// @snippet qt-qenum + +// @snippet qt-qflag +%PYARG_0 = PySide::QEnum::QEnumMacro(%1, true); +// @snippet qt-qflag + // @snippet qt-pysideinit Shiboken::Conversions::registerConverterName(SbkPySide2_QtCoreTypeConverters[SBK_QSTRING_IDX], "unicode"); Shiboken::Conversions::registerConverterName(SbkPySide2_QtCoreTypeConverters[SBK_QSTRING_IDX], "str"); diff --git a/sources/pyside2/PySide2/glue/qtuitools.cpp b/sources/pyside2/PySide2/glue/qtuitools.cpp index 668b512e4..d81f6205a 100644 --- a/sources/pyside2/PySide2/glue/qtuitools.cpp +++ b/sources/pyside2/PySide2/glue/qtuitools.cpp @@ -137,16 +137,19 @@ if (uiFileName.isEmpty()) { Py_RETURN_NONE; } -QString uicBin("uic"); -QStringList uicArgs = {"-g", "python", QString::fromUtf8(uiFileName)}; +// Use the 'pyside2-uic' wrapper instead of 'uic' +// This approach is better than rely on 'uic' since installing +// the wheels cover this case. +QString uicBin("pyside2-uic"); +QStringList uicArgs = {QString::fromUtf8(uiFileName)}; QProcess uicProcess; uicProcess.start(uicBin, uicArgs); if (!uicProcess.waitForFinished()) { - qCritical() << "Cannot run 'uic': " << uicProcess.errorString() << " - " + qCritical() << "Cannot run 'pyside2-uic': " << uicProcess.errorString() << " - " << "Exit status " << uicProcess.exitStatus() << " (" << uicProcess.exitCode() << ")\n" - << "Check if 'uic' is in PATH"; + << "Check if 'pyside2-uic' is in PATH"; Py_RETURN_NONE; } QByteArray uiFileContent = uicProcess.readAllStandardOutput(); diff --git a/sources/pyside2/doc/extras/QtCore.QEnum.rst b/sources/pyside2/doc/extras/QtCore.QEnum.rst new file mode 100644 index 000000000..a5a2e31fd --- /dev/null +++ b/sources/pyside2/doc/extras/QtCore.QEnum.rst @@ -0,0 +1,92 @@ +.. currentmodule:: PySide2.QtCore +.. _QEnum: + +QEnum/QFlag +*********** + +This class decorator is equivalent to the `Q_ENUM` macro from Qt. +The decorator is used to register an Enum to the meta-object system, +which is available via `QObject.staticMetaObject`. +The enumerator must be in a QObject derived class to be registered. + + +Example +------- + +:: + + from enum import Enum, Flag, auto + + from PySide2.QtCore import QEnum, QFlag, QObject + + class Demo(QObject): + + @QEnum + class Orientation(Enum): + North, East, South, West = range(4) + + class Color(Flag): + RED = auto() + BLUE = auto() + GREEN = auto() + WHITE = RED | BLUE | GREEN + + QFlag(Color) # identical to @QFlag usage + + +Caution: +-------- + +QEnum registers a Python Enum derived class. +QFlag treats a variation of the Python Enum, the Flag class. + +Please do not confuse that with the Qt QFlags concept. Python does +not use that concept, it has its own class hierarchy, instead. +For more details, see the `Python enum documentation <https://docs.python.org/3/library/enum.html>`_. + + +Details about Qt Flags: +----------------------- + +There are some small differences between Qt flags and Python flags. +In Qt, we have for instance these declarations: + +:: + + enum QtGui::RenderHint { Antialiasing, TextAntialiasing, SmoothPixmapTransform, + HighQualityAntialiasing, NonCosmeticDefaultPen } + flags QtGui::RenderHints + +The equivalent Python notation would look like this: + +:: + + @QFlag + class RenderHints(enum.Flag) + Antialiasing = auto() + TextAntialiasing = auto() + SmoothPixmapTransform = auto() + HighQualityAntialiasing = auto() + NonCosmeticDefaultPen = auto() + + +As another example, the Qt::AlignmentFlag flag has 'AlignmentFlag' as the enum +name, but 'Alignment' as the type name. Non flag enums have the same type and +enum names. + +:: + + enum Qt::AlignmentFlag + flags Qt::Alignment + +The Python way to specify this would be + +:: + + @QFlag + class Alignment(enum.Flag): + ... + +We are considering to map all builtin enums and flags to Python enums as well +in a later release. + diff --git a/sources/pyside2/doc/tutorials/basictutorial/style.qss b/sources/pyside2/doc/tutorials/basictutorial/style.qss new file mode 100644 index 000000000..b84b98f05 --- /dev/null +++ b/sources/pyside2/doc/tutorials/basictutorial/style.qss @@ -0,0 +1,23 @@ +QListWidget { + color: #FFFFFF; + background-color: #33373B; +} + +QListWidget::item { + height: 50px; +} + +QListWidget::item:selected { + background-color: #2ABf9E; +} + +QLabel { + background-color: #FFFFFF; + qproperty-alignment: AlignCenter; +} + +QPushButton { + background-color: #2ABf9E; + padding: 20px; + font-size: 18px; +} diff --git a/sources/pyside2/doc/tutorials/basictutorial/widgetstyling-no.png b/sources/pyside2/doc/tutorials/basictutorial/widgetstyling-no.png Binary files differnew file mode 100644 index 000000000..c30dd621b --- /dev/null +++ b/sources/pyside2/doc/tutorials/basictutorial/widgetstyling-no.png diff --git a/sources/pyside2/doc/tutorials/basictutorial/widgetstyling-simple-no.png b/sources/pyside2/doc/tutorials/basictutorial/widgetstyling-simple-no.png Binary files differnew file mode 100644 index 000000000..eb90e216d --- /dev/null +++ b/sources/pyside2/doc/tutorials/basictutorial/widgetstyling-simple-no.png diff --git a/sources/pyside2/doc/tutorials/basictutorial/widgetstyling-simple-yes.png b/sources/pyside2/doc/tutorials/basictutorial/widgetstyling-simple-yes.png Binary files differnew file mode 100644 index 000000000..5a714977e --- /dev/null +++ b/sources/pyside2/doc/tutorials/basictutorial/widgetstyling-simple-yes.png diff --git a/sources/pyside2/doc/tutorials/basictutorial/widgetstyling-yes.png b/sources/pyside2/doc/tutorials/basictutorial/widgetstyling-yes.png Binary files differnew file mode 100644 index 000000000..8ba49bd26 --- /dev/null +++ b/sources/pyside2/doc/tutorials/basictutorial/widgetstyling-yes.png diff --git a/sources/pyside2/doc/tutorials/basictutorial/widgetstyling.py b/sources/pyside2/doc/tutorials/basictutorial/widgetstyling.py new file mode 100644 index 000000000..41af464c8 --- /dev/null +++ b/sources/pyside2/doc/tutorials/basictutorial/widgetstyling.py @@ -0,0 +1,95 @@ +############################################################################# +## +## Copyright (C) 2020 The Qt Company Ltd. +## Contact: http://www.qt.io/licensing/ +## +## This file is part of the Qt for Python examples of the Qt Toolkit. +## +## $QT_BEGIN_LICENSE:BSD$ +## You may use this file under the terms of the BSD license as follows: +## +## "Redistribution and use in source and binary forms, with or without +## modification, are permitted provided that the following conditions are +## met: +## * Redistributions of source code must retain the above copyright +## notice, this list of conditions and the following disclaimer. +## * Redistributions in binary form must reproduce the above copyright +## notice, this list of conditions and the following disclaimer in +## the documentation and/or other materials provided with the +## distribution. +## * Neither the name of The Qt Company Ltd nor the names of its +## contributors may be used to endorse or promote products derived +## from this software without specific prior written permission. +## +## +## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +## +## $QT_END_LICENSE$ +## +############################################################################# + +import sys + +from PySide2.QtCore import Qt +from PySide2.QtWidgets import (QApplication, QHBoxLayout, QLabel, QListWidget, + QListWidgetItem, QPushButton, QVBoxLayout, + QWidget) + +_placeholder = """ +Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod +tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim +veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea +commodo consequat. Duis aute irure dolor in reprehenderit in voluptate +velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint +occaecat cupidatat non proident, sunt in culpa qui officia deserunt +mollit anim id est laborum +""" + + +class Widget(QWidget): + def __init__(self, parent=None): + super(Widget, self).__init__(parent) + + menu_widget = QListWidget() + for i in range(10): + item = QListWidgetItem("Item {}".format(i)) + item.setTextAlignment(Qt.AlignCenter) + menu_widget.addItem(item) + + text_widget = QLabel(_placeholder) + button = QPushButton("Something") + + content_layout = QVBoxLayout() + content_layout.addWidget(text_widget) + content_layout.addWidget(button) + main_widget = QWidget() + main_widget.setLayout(content_layout) + + layout = QHBoxLayout() + layout.addWidget(menu_widget, 1) + layout.addWidget(main_widget, 4) + self.setLayout(layout) + + +if __name__ == "__main__": + app = QApplication() + + w = Widget() + w.show() + + _style = None + with open("style.qss", "r") as f: + _style = f.read() + app.setStyleSheet(_style) + + sys.exit(app.exec_()) diff --git a/sources/pyside2/doc/tutorials/basictutorial/widgetstyling.rst b/sources/pyside2/doc/tutorials/basictutorial/widgetstyling.rst new file mode 100644 index 000000000..a79f9c898 --- /dev/null +++ b/sources/pyside2/doc/tutorials/basictutorial/widgetstyling.rst @@ -0,0 +1,169 @@ +Widget Styling +************** + +Qt Widgets application use a default theme depending on the platform. +In some cases, there are system-wide configurations that modify the Qt theme, +and applications are displayed differently. + +However, you can take care of your own widgets and provide a custom style +to each component. As an example, look at the following simple snippet: + +.. code-block:: python + + import sys + from PySide2.QtCore import Qt + from PySide2.QtWidgets import QApplication, QLabel + + if __name__ == "__main__": + app = QApplication() + w = QLabel("This is a placeholder text") + w.setAlignment(Qt.AlignCenter) + w.show() + sys.exit(app.exec_()) + +When you execute this code, you will see a simple `QLabel` aligned at the +center, and with a placeholder text. + +.. image:: widgetstyling-simple-no.png + :alt: Simple Widget with no style + +You can style your application using the CSS-like syntax. +For more information, see `Qt Style Sheets Reference`_. + +A `QLabel` can be styled differently by setting some of its CSS +properties, such as `background-color` and `font-family`, +so let's see how does the code look like with these changes: + +.. code-block:: python + + import sys + from PySide2.QtCore import Qt + from PySide2.QtWidgets import QApplication, QLabel + + if __name__ == "__main__": + app = QApplication() + w = QLabel("This is a placeholder text") + w.setAlignment(Qt.AlignCenter) + w.setStyleSheet(""" + background-color: #262626; + color: #FFFFFF; + font-family: Titillium; + font-size: 18px; + """) + w.show() + sys.exit(app.exec_()) + +Now when you run the code, notice that the `QLabel` looks different with your +custom style: + +.. image:: widgetstyling-simple-yes.png + :alt: Simple Widget with Style + + +.. note:: + + If you don't have the font `Titillium` installed, you can try with any + other you prefer. + Remember you can list your installed fonts using `QFontDatabase`, + specifically the `families()` method. + + +Styling each UI element separately like you did in the previous snippet is a +lot of work. The easier alternative for this is to use Qt Style Sheets, +which is one or more `.qss` files defining the style for the UI elements in +your application. + +More examples can be found in the `Qt Style Sheet Examples`_ documentation +page. + + +.. _`Qt Style Sheets Reference`: https://doc.qt.io/qt-5/stylesheet-reference.html +.. _`Qt Style Sheet Examples`: https://doc.qt.io/qt-5/stylesheet-examples.html + +Qt Style Sheets +=============== + +.. warning:: + + Before starting modifying your application, keep in mind that you will be + responsible for all the graphical details of the application. + Altering margins, and sizes might end up looking strange or incorrect, so you + need to be careful when altering the style. + It's recommended to create a full new Qt style to cover all the possible + corner cases. + +A `qss` file is quite similar to a CSS file, but you need to specify the Widget +component and optionally the name of the object:: + + QLabel { + background-color: red; + } + + QLabel#title { + font-size: 20px; + } + +The first style defines a `background-color` for all `QLabel` objects in your +application, whereas the later one styles the `title` object only. + +.. note:: + + You can set object names with the `setObjectName(str)` function to any Qt + object, for example: for a `label = QLabel("Test")`, you can write + `label.setObjectName("title")` + + +Once you have a `qss` file for your application, you can apply it by reading +the file and using the `QApplication.setStyleSheet(str)` function: + +.. code-block:: python + + if __name__ == "__main__": + app = QApplication() + + w = Widget() + w.show() + + with open("style.qss", "r") as f: + _style = f.read() + app.setStyleSheet(_style) + + sys.exit(app.exec_()) + +Having a general `qss` file allows you to decouple the styling aspects of +the code, without mixing it in the middle of the general functionality, and you +can simply enable it or disable it. + +Look at this new example, with more widgets components: + +.. literalinclude:: widgetstyling.py + :linenos: + :lines: 59-81 + +This displays a two column widget, with a `QListWidget` on the left and a +`QLabel` and a `QPushButton` on the right. It looks like this when you run the +code: + +.. image:: widgetstyling-no.png + :alt: Widget with no style + +If you add content to the previously described `style.qss` file, you can modify +the look-n-feel of the previous example: + +.. literalinclude:: style.qss + :linenos: + +The style changes mainly the color of the different widgets, alter the +alignment, and includes some spacing. +You can also use state-based styling on the QListWidget *items* for example, to +style them differently depending on whether they are *selected* or not. + +After applying all the styling alternatives you explored in this topic, notice +that the `QLabel` example looks a lot different now. +Try running the code to check its new look: + +.. image:: widgetstyling-yes.png + :alt: Widget with style + +You have the freedom to tune your style sheets and provide a really nice +look-n-feel to all your applications. diff --git a/sources/pyside2/doc/tutorials/index.rst b/sources/pyside2/doc/tutorials/index.rst index 598b42ca1..9739eee97 100644 --- a/sources/pyside2/doc/tutorials/index.rst +++ b/sources/pyside2/doc/tutorials/index.rst @@ -19,6 +19,7 @@ Basic tutorials basictutorial/dialog.rst basictutorial/uifiles.rst basictutorial/qrcfiles.rst + basictutorial/widgetstyling.rst Real use-cases applications --------------------------- diff --git a/sources/pyside2/libpyside/CMakeLists.txt b/sources/pyside2/libpyside/CMakeLists.txt index 11342ec71..31f68749a 100644 --- a/sources/pyside2/libpyside/CMakeLists.txt +++ b/sources/pyside2/libpyside/CMakeLists.txt @@ -43,6 +43,7 @@ set(libpyside_SRC signalmanager.cpp globalreceiverv2.cpp pysideclassinfo.cpp + pysideqenum.cpp pysidemetafunction.cpp pysidesignal.cpp pysideslot.cpp @@ -125,6 +126,7 @@ endif() set(libpyside_HEADERS dynamicqmetaobject.h pysideclassinfo.h + pysideqenum.h pysidemacros.h signalmanager.h pyside.h diff --git a/sources/pyside2/libpyside/dynamicqmetaobject.cpp b/sources/pyside2/libpyside/dynamicqmetaobject.cpp index dae9e2059..efdf33ac9 100644 --- a/sources/pyside2/libpyside/dynamicqmetaobject.cpp +++ b/sources/pyside2/libpyside/dynamicqmetaobject.cpp @@ -44,6 +44,7 @@ #include "pysideproperty.h" #include "pysideproperty_p.h" #include "pysideslot_p.h" +#include "pysideqenum.h" #include <shiboken.h> @@ -91,6 +92,10 @@ public: 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(); @@ -357,6 +362,28 @@ 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(); @@ -430,6 +457,8 @@ 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 @@ -439,7 +468,7 @@ void MetaObjectBuilderPrivate::parsePythonType(PyTypeObject *type) // existing connections. const PyObject *mro = type->tp_mro; const Py_ssize_t basesCount = PyTuple_GET_SIZE(mro); - PyTypeObject *qObjectType = Shiboken::Conversions::getPythonTypeObject("QObject*"); + PyTypeObject *qObjectType = Conversions::getPythonTypeObject("QObject*"); std::vector<PyTypeObject *> basesToCheck; // Prepend the actual type that we are parsing. @@ -470,7 +499,7 @@ void MetaObjectBuilderPrivate::parsePythonType(PyTypeObject *type) // Register signals. auto data = reinterpret_cast<PySideSignal *>(value); if (data->data->signalName.isEmpty()) - data->data->signalName = Shiboken::String::toCString(key); + 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) { @@ -489,7 +518,7 @@ void MetaObjectBuilderPrivate::parsePythonType(PyTypeObject *type) } } - Shiboken::AutoDecRef slotAttrName(Shiboken::String::fromCString(PYSIDE_SLOT_LIST_ATTR)); + 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. @@ -501,16 +530,16 @@ void MetaObjectBuilderPrivate::parsePythonType(PyTypeObject *type) while (PyDict_Next(attrs, &pos, &key, &value)) { if (Property::checkType(value)) { - const int index = m_baseObject->indexOfProperty(Shiboken::String::toCString(key)); + const int index = m_baseObject->indexOfProperty(String::toCString(key)); if (index == -1) - addProperty(Shiboken::String::toCString(key), value); + addProperty(String::toCString(key), value); } else if (PyFunction_Check(value)) { // 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(Shiboken::String::toCString(pySignature)); + QByteArray signature(String::toCString(pySignature)); // Split the slot type and its signature. QByteArray type; const int spacePos = signature.indexOf(' '); @@ -530,4 +559,29 @@ void MetaObjectBuilderPrivate::parsePythonType(PyTypeObject *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(PepMapping_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/pyside2/libpyside/dynamicqmetaobject.h b/sources/pyside2/libpyside/dynamicqmetaobject.h index 1fbe73ea4..7279d5c26 100644 --- a/sources/pyside2/libpyside/dynamicqmetaobject.h +++ b/sources/pyside2/libpyside/dynamicqmetaobject.h @@ -68,7 +68,10 @@ public: 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(); diff --git a/sources/pyside2/libpyside/pyside.cpp b/sources/pyside2/libpyside/pyside.cpp index 4a554de58..66e931164 100644 --- a/sources/pyside2/libpyside/pyside.cpp +++ b/sources/pyside2/libpyside/pyside.cpp @@ -223,8 +223,7 @@ std::size_t getSizeOfQObject(SbkObjectType *type) 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); + auto userData = new TypeUserData(reinterpret_cast<PyTypeObject *>(type), base, cppObjSize); userData->mo.update(); Shiboken::ObjectType::setTypeUserData(type, userData, Shiboken::callCppDestructor<TypeUserData>); diff --git a/sources/pyside2/libpyside/pysideqenum.cpp b/sources/pyside2/libpyside/pysideqenum.cpp new file mode 100644 index 000000000..f46b5536c --- /dev/null +++ b/sources/pyside2/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(PepMapping_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/pyside2/libpyside/pysideqenum.h b/sources/pyside2/libpyside/pysideqenum.h new file mode 100644 index 000000000..fc4e55982 --- /dev/null +++ b/sources/pyside2/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/pyside2/libpyside/pysidesignal.cpp b/sources/pyside2/libpyside/pysidesignal.cpp index 39ed1a6bc..32e1bb0c6 100644 --- a/sources/pyside2/libpyside/pysidesignal.cpp +++ b/sources/pyside2/libpyside/pysidesignal.cpp @@ -54,7 +54,6 @@ #include <utility> #define QT_SIGNAL_SENTINEL '2' -#define PyEnumMeta_Check(x) (strcmp(Py_TYPE(arg)->tp_name, "EnumMeta") == 0) namespace PySide { namespace Signal { diff --git a/sources/pyside2/tests/QtCore/qenum_test.py b/sources/pyside2/tests/QtCore/qenum_test.py index 1edb8981a..f99a893d9 100644 --- a/sources/pyside2/tests/QtCore/qenum_test.py +++ b/sources/pyside2/tests/QtCore/qenum_test.py @@ -2,7 +2,7 @@ ############################################################################# ## -## Copyright (C) 2016 The Qt Company Ltd. +## Copyright (C) 2020 The Qt Company Ltd. ## Contact: https://www.qt.io/licensing/ ## ## This file is part of the test suite of Qt for Python. @@ -40,7 +40,7 @@ sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from init_paths import init_test_paths init_test_paths(False) -from PySide2.QtCore import Qt, QIODevice +from PySide2.QtCore import Qt, QIODevice, QObject, QEnum, QFlag class TestEnum(unittest.TestCase): @@ -68,28 +68,30 @@ class TestEnum(unittest.TestCase): # Floats with self.assertRaises(TypeError): - a = k+2.0 + a = k + 2.0 with self.assertRaises(TypeError): - a = k-2.0 + a = k - 2.0 with self.assertRaises(TypeError): - a = k*2.0 + a = k * 2.0 - @unittest.skipUnless(getattr(sys, "getobjects", None), "requires debug build") + @unittest.skipUnless(getattr(sys, "getobjects", None), "requires --with-trace-refs") + @unittest.skipUnless(getattr(sys, "gettotalrefcount", None), "requires --with-pydebug") def testEnumNew_NoLeak(self): gc.collect() total = sys.gettotalrefcount() for idx in range(1000): ret = Qt.Key(42) + gc.collect() delta = sys.gettotalrefcount() - total print("delta total refcount =", delta) if abs(delta) >= 10: - all = sys.getobjects(0) - all.sort(key=lambda x: sys.getrefcount(x), reverse=True) + all = [(sys.getrefcount(x), x) for x in sys.getobjects(0)] + all.sort(key=lambda x: x[0], reverse=True) for ob in all[:10]: - print(sys.getrefcount(ob), ob) + print(ob) self.assertTrue(abs(delta) < 10) @@ -141,6 +143,105 @@ class TestEnumPickling(unittest.TestCase): else: func() +# PYSIDE-957: The QEnum macro + +try: + import enum + HAVE_ENUM = True +except ImportError: + HAVE_ENUM = False + QEnum = QFlag = lambda x: x + import types + class Enum: pass + enum = types.ModuleType("enum") + enum.Enum = enum.Flag = enum.IntEnum = enum.IntFlag = Enum + Enum.__module__ = "enum" + Enum.__members__ = {} + del Enum + enum.auto = lambda: 42 + +HAVE_FLAG = hasattr(enum, "Flag") + +@QEnum +class OuterEnum(enum.Enum): + A = 1 + B = 2 + +class SomeClass(QObject): + + @QEnum + class SomeEnum(enum.Enum): + A = 1 + B = 2 + C = 3 + + @QEnum + class OtherEnum(enum.IntEnum): + A = 1 + B = 2 + C = 3 + + class InnerClass(QObject): + + @QEnum + class InnerEnum(enum.Enum): + X = 42 + + class SomeEnum(enum.Enum): + A = 4 + B = 5 + C = 6 + + QEnum(SomeEnum) # works even without the decorator assignment + + +@unittest.skipUnless(HAVE_ENUM, "requires 'enum' module (use 'pip install enum34' for Python 2)") +class TestQEnumMacro(unittest.TestCase): + def testTopLevel(self): + self.assertEqual(type(OuterEnum).__module__, "enum") + self.assertEqual(type(OuterEnum).__name__, "EnumMeta") + self.assertEqual(len(OuterEnum.__members__), 2) + + def testSomeClass(self): + self.assertEqual(type(SomeClass.SomeEnum).__module__, "enum") + self.assertEqual(type(SomeClass.SomeEnum).__name__, "EnumMeta") + self.assertEqual(len(SomeClass.SomeEnum.__members__), 3) + with self.assertRaises(TypeError): + int(SomeClass.SomeEnum.C) == 6 + self.assertEqual(SomeClass.OtherEnum.C, 3) + + @unittest.skipIf(sys.version_info[0] < 3, "we cannot support nested classes in Python 2") + def testInnerClass(self): + self.assertEqual(SomeClass.InnerClass.InnerEnum.__qualname__, + "SomeClass.InnerClass.InnerEnum") + with self.assertRaises(TypeError): + int(SomeClass.InnerClass.InnerEnum.X) == 42 + + @unittest.skipUnless(HAVE_FLAG, "some older Python versions have no 'Flag'") + def testEnumFlag(self): + with self.assertRaises(TypeError): + class WrongFlagForEnum(QObject): + @QEnum + class Bad(enum.Flag): + pass + with self.assertRaises(TypeError): + class WrongEnuForFlag(QObject): + @QFlag + class Bad(enum.Enum): + pass + + def testIsRegistered(self): + mo = SomeClass.staticMetaObject + self.assertEqual(mo.enumeratorCount(), 2) + self.assertEqual(mo.enumerator(0).name(), "OtherEnum") + self.assertEqual(mo.enumerator(0).scope(), "SomeClass") + self.assertEqual(mo.enumerator(1).name(), "SomeEnum") + moi = SomeClass.InnerClass.staticMetaObject + self.assertEqual(moi.enumerator(0).name(), "InnerEnum") + ## Question: Should that scope not better be "SomeClass.InnerClass"? + ## But we have __qualname__ already: + self.assertEqual(moi.enumerator(0).scope(), "InnerClass") + if __name__ == '__main__': unittest.main() diff --git a/sources/shiboken2/ApiExtractor/clangparser/clangbuilder.cpp b/sources/shiboken2/ApiExtractor/clangparser/clangbuilder.cpp index 310a751e0..1eaa36540 100644 --- a/sources/shiboken2/ApiExtractor/clangparser/clangbuilder.cpp +++ b/sources/shiboken2/ApiExtractor/clangparser/clangbuilder.cpp @@ -620,27 +620,52 @@ long clang_EnumDecl_isScoped4(BaseVisitor *bv, const CXCursor &cursor) } #endif // CLANG_NO_ENUMDECL_ISSCOPED +// Resolve declaration and type of a base class + +struct TypeDeclaration +{ + CXType type; + CXCursor declaration; +}; + +static TypeDeclaration resolveBaseSpecifier(const CXCursor &cursor) +{ + Q_ASSERT(clang_getCursorKind(cursor) == CXCursor_CXXBaseSpecifier); + CXType inheritedType = clang_getCursorType(cursor); + CXCursor decl = clang_getTypeDeclaration(inheritedType); + if (inheritedType.kind != CXType_Unexposed) { + while (true) { + auto kind = clang_getCursorKind(decl); + if (kind != CXCursor_TypeAliasDecl && kind != CXCursor_TypedefDecl) + break; + inheritedType = clang_getTypedefDeclUnderlyingType(decl); + decl = clang_getTypeDeclaration(inheritedType); + } + } + return {inheritedType, decl}; +} + // Add a base class to the current class from CXCursor_CXXBaseSpecifier void BuilderPrivate::addBaseClass(const CXCursor &cursor) { + Q_ASSERT(clang_getCursorKind(cursor) == CXCursor_CXXBaseSpecifier); // Note: spelling has "struct baseClass", use type QString baseClassName; - const CXType inheritedType = clang_getCursorType(cursor); - if (inheritedType.kind == CXType_Unexposed) { + const auto decl = resolveBaseSpecifier(cursor); + if (decl.type.kind == CXType_Unexposed) { // The type is unexposed when the base class is a template type alias: // "class QItemSelection : public QList<X>" where QList is aliased to QVector. // Try to resolve via code model. - TypeInfo info = createTypeInfo(inheritedType); + TypeInfo info = createTypeInfo(decl.type); auto parentScope = m_scopeStack.at(m_scopeStack.size() - 2); // Current is class. auto resolved = TypeInfo::resolveType(info, parentScope); if (resolved != info) baseClassName = resolved.toString(); } if (baseClassName.isEmpty()) - baseClassName = getTypeName(inheritedType); + baseClassName = getTypeName(decl.type); - const CXCursor declCursor = clang_getTypeDeclaration(inheritedType); - const CursorClassHash::const_iterator it = m_cursorClassHash.constFind(declCursor); + auto it = m_cursorClassHash.constFind(decl.declaration); const CodeModel::AccessPolicy access = accessPolicy(clang_getCXXAccessSpecifier(cursor)); if (it == m_cursorClassHash.constEnd()) { // Set unqualified name. This happens in cases like "class X : public std::list<...>" diff --git a/sources/shiboken2/ApiExtractor/tests/testabstractmetaclass.cpp b/sources/shiboken2/ApiExtractor/tests/testabstractmetaclass.cpp index f2e15fdb0..f0aa4a318 100644 --- a/sources/shiboken2/ApiExtractor/tests/testabstractmetaclass.cpp +++ b/sources/shiboken2/ApiExtractor/tests/testabstractmetaclass.cpp @@ -588,4 +588,34 @@ void TestAbstractMetaClass::testIsPolymorphic() QVERIFY(!a->isPolymorphic()); } +void TestAbstractMetaClass::testClassTypedefedBaseClass() +{ + const char cppCode[] =R"CPP( +class Base { +}; + +using BaseAlias1 = Base; +using BaseAlias2 = BaseAlias1; + +class Derived : public BaseAlias2 { +}; +)CPP"; + const char xmlCode[] = R"XML( +<typesystem package='Foo'> + <object-type name='Base'/> + <object-type name='Derived'/> +</typesystem> +)XML"; + + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode)); + QVERIFY(!builder.isNull()); + AbstractMetaClassList classes = builder->classes(); + QCOMPARE(classes.count(), 2); + auto base = AbstractMetaClass::findClass(classes, QLatin1String("Base")); + QVERIFY(base); + auto derived = AbstractMetaClass::findClass(classes, QLatin1String("Derived")); + QVERIFY(derived); + QCOMPARE(derived->baseClasses().value(0), base); +} + QTEST_APPLESS_MAIN(TestAbstractMetaClass) diff --git a/sources/shiboken2/ApiExtractor/tests/testabstractmetaclass.h b/sources/shiboken2/ApiExtractor/tests/testabstractmetaclass.h index e19973625..1d9f8d8f6 100644 --- a/sources/shiboken2/ApiExtractor/tests/testabstractmetaclass.h +++ b/sources/shiboken2/ApiExtractor/tests/testabstractmetaclass.h @@ -51,6 +51,7 @@ private slots: void testAbstractClassDefaultConstructors(); void testObjectTypesMustNotHaveCopyConstructors(); void testIsPolymorphic(); + void testClassTypedefedBaseClass(); }; #endif // TESTABSTRACTMETACLASS_H diff --git a/sources/shiboken2/ApiExtractor/tests/testaddfunction.cpp b/sources/shiboken2/ApiExtractor/tests/testaddfunction.cpp index c50084b8e..ca4af9a10 100644 --- a/sources/shiboken2/ApiExtractor/tests/testaddfunction.cpp +++ b/sources/shiboken2/ApiExtractor/tests/testaddfunction.cpp @@ -283,7 +283,7 @@ void TestAddFunction::testAddFunctionAtModuleLevel() QCOMPARE(mods.size(), 1); QVERIFY(mods.first().isCodeInjection()); CodeSnip snip = mods.first().snips.first(); - QCOMPARE(snip.code(), QLatin1String("custom_code();")); + QCOMPARE(snip.code().trimmed(), QLatin1String("custom_code();")); } void TestAddFunction::testAddFunctionWithVarargs() diff --git a/sources/shiboken2/ApiExtractor/tests/testnestedtypes.cpp b/sources/shiboken2/ApiExtractor/tests/testnestedtypes.cpp index 10194eb34..e61418467 100644 --- a/sources/shiboken2/ApiExtractor/tests/testnestedtypes.cpp +++ b/sources/shiboken2/ApiExtractor/tests/testnestedtypes.cpp @@ -69,7 +69,7 @@ void TestNestedTypes::testNestedTypesModifications() QCOMPARE(ins->functions().count(), 1); QCOMPARE(ins->typeEntry()->codeSnips().count(), 1); CodeSnip snip = ins->typeEntry()->codeSnips().first(); - QCOMPARE(snip.code(), QLatin1String("custom_code1();")); + QCOMPARE(snip.code().trimmed(), QLatin1String("custom_code1();")); AbstractMetaFunction* addedFunc = ins->functions().first(); QVERIFY(addedFunc->isUserAdded()); @@ -80,7 +80,7 @@ void TestNestedTypes::testNestedTypesModifications() QCOMPARE(addedFunc->modifications().size(), 1); QVERIFY(addedFunc->modifications().first().isCodeInjection()); snip = addedFunc->modifications().first().snips.first(); - QCOMPARE(snip.code(), QLatin1String("custom_code2();")); + QCOMPARE(snip.code().trimmed(), QLatin1String("custom_code2();")); const AbstractMetaClass *sc = AbstractMetaClass::findClass(classes, QLatin1String("OuterNamespace::InnerNamespace::SomeClass")); QVERIFY(ins); diff --git a/sources/shiboken2/ApiExtractor/typesystem.cpp b/sources/shiboken2/ApiExtractor/typesystem.cpp index 920da9e10..729e6b32b 100644 --- a/sources/shiboken2/ApiExtractor/typesystem.cpp +++ b/sources/shiboken2/ApiExtractor/typesystem.cpp @@ -34,6 +34,7 @@ #include <QtCore/QSet> #include <algorithm> +#include <limits> static QString strings_Object = QLatin1String("Object"); static QString strings_String = QLatin1String("String"); @@ -212,7 +213,7 @@ QString TemplateInstance::expandCode() const if (!code.startsWith(QLatin1Char('\n'))) result += QLatin1Char('\n'); result += code; - result += QLatin1String("\n// TEMPLATE - ") + m_name + QLatin1String(" - END"); + result += QLatin1String("\n// TEMPLATE - ") + m_name + QLatin1String(" - END\n"); return result; } @@ -226,6 +227,82 @@ QString CodeSnipAbstract::code() const return res; } +void CodeSnipAbstract::addCode(const QString &code) +{ + codeList.append(CodeSnipFragment(fixSpaces(code))); +} + +template <class String> // QString, QStringRef +static inline int firstNonBlank(const String &s) +{ + const auto it = std::find_if(s.cbegin(), s.cend(), + [] (QChar c) { return !c.isSpace(); }); + return int(it - s.cbegin()); +} + +template <class String> // QString, QStringRef +static inline bool isEmpty(const String &s) +{ + return s.isEmpty() + || std::all_of(s.cbegin(), s.cend(), + [] (QChar c) { return c.isSpace(); }); +} + +QString CodeSnipAbstract::dedent(const QString &code) +{ + if (code.isEmpty()) + return code; + // Right trim if indent=0, or trim if single line + if (!code.at(0).isSpace() || !code.contains(QLatin1Char('\n'))) + return code.trimmed(); + const auto lines = code.splitRef(QLatin1Char('\n')); + int spacesToRemove = std::numeric_limits<int>::max(); + for (const auto &line : lines) { + if (!isEmpty(line)) { + const int nonSpacePos = firstNonBlank(line); + if (nonSpacePos < spacesToRemove) + spacesToRemove = nonSpacePos; + if (spacesToRemove == 0) + return code; + } + } + QString result; + for (const auto &line : lines) { + if (!isEmpty(line) && spacesToRemove < line.size()) + result += line.mid(spacesToRemove).toString(); + result += QLatin1Char('\n'); + } + return result; +} + +QString CodeSnipAbstract::fixSpaces(QString code) +{ + code.remove(QLatin1Char('\r')); + // Check for XML <tag>\n<space>bla... + if (code.startsWith(QLatin1String("\n "))) + code.remove(0, 1); + while (!code.isEmpty() && code.back().isSpace()) + code.chop(1); + code = dedent(code); + if (!code.isEmpty() && !code.endsWith(QLatin1Char('\n'))) + code.append(QLatin1Char('\n')); + return code; +} + +// Prepend a line to the code, observing indentation +void CodeSnipAbstract::prependCode(QString *code, QString firstLine) +{ + while (!code->isEmpty() && code->front() == QLatin1Char('\n')) + code->remove(0, 1); + if (!code->isEmpty() && code->front().isSpace()) { + const int indent = firstNonBlank(*code); + firstLine.prepend(QString(indent, QLatin1Char(' '))); + } + if (!firstLine.endsWith(QLatin1Char('\n'))) + firstLine += QLatin1Char('\n'); + code->prepend(firstLine); +} + QString CodeSnipFragment::code() const { return m_instance ? m_instance->expandCode() : m_code; @@ -1184,3 +1261,8 @@ TypeEntry *ObjectTypeEntry::clone() const } ObjectTypeEntry::ObjectTypeEntry(const ObjectTypeEntry &) = default; + +void DocModification::setCode(const QString &code) +{ + m_code = CodeSnipAbstract::fixSpaces(code); +} diff --git a/sources/shiboken2/ApiExtractor/typesystem.h b/sources/shiboken2/ApiExtractor/typesystem.h index 6d2d4bb44..c6995b64d 100644 --- a/sources/shiboken2/ApiExtractor/typesystem.h +++ b/sources/shiboken2/ApiExtractor/typesystem.h @@ -115,7 +115,7 @@ class CodeSnipAbstract public: QString code() const; - void addCode(const QString &code) { codeList.append(CodeSnipFragment(code)); } + void addCode(const QString &code); void addCode(const QStringRef &code) { addCode(code.toString()); } void addTemplateInstance(TemplateInstance *ti) @@ -124,6 +124,10 @@ public: } QVector<CodeSnipFragment> codeList; + + static QString fixSpaces(QString code); + static QString dedent(const QString &code); + static void prependCode(QString *code, QString firstLine); }; class CustomFunction : public CodeSnipAbstract @@ -510,8 +514,8 @@ public: explicit DocModification(TypeSystem::DocModificationMode mode, const QString& signature) : m_signature(signature), m_mode(mode) {} - void setCode(const QString& code) { m_code = code; } - void setCode(const QStringRef& code) { m_code = code.toString(); } + void setCode(const QString& code); + void setCode(const QStringRef& code) { setCode(code.toString()); } QString code() const { diff --git a/sources/shiboken2/ApiExtractor/typesystem_enums.h b/sources/shiboken2/ApiExtractor/typesystem_enums.h index 120c9417f..f6b4b6fa6 100644 --- a/sources/shiboken2/ApiExtractor/typesystem_enums.h +++ b/sources/shiboken2/ApiExtractor/typesystem_enums.h @@ -36,21 +36,9 @@ enum Language { TargetLangCode = 0x0001, NativeCode = 0x0002, ShellCode = 0x0004, - ShellDeclaration = 0x0008, - PackageInitializer = 0x0010, - DestructorFunction = 0x0020, - Constructors = 0x0040, - Interface = 0x0080, // masks - All = TargetLangCode - | NativeCode - | ShellCode - | ShellDeclaration - | PackageInitializer - | Constructors - | Interface - | DestructorFunction, + All = TargetLangCode | NativeCode | ShellCode, TargetLangAndNativeCode = TargetLangCode | NativeCode }; @@ -72,12 +60,7 @@ enum Ownership { enum CodeSnipPosition { CodeSnipPositionBeginning, CodeSnipPositionEnd, - CodeSnipPositionAfterThis, - // QtScript CodeSnipPositionDeclaration, - CodeSnipPositionPrototypeInitialization, - CodeSnipPositionConstructorInitialization, - CodeSnipPositionConstructor, CodeSnipPositionAny, CodeSnipPositionInvalid }; diff --git a/sources/shiboken2/ApiExtractor/typesystemparser.cpp b/sources/shiboken2/ApiExtractor/typesystemparser.cpp index 9fdf81821..0c4d43e76 100644 --- a/sources/shiboken2/ApiExtractor/typesystemparser.cpp +++ b/sources/shiboken2/ApiExtractor/typesystemparser.cpp @@ -149,7 +149,7 @@ static QString extractSnippet(const QString &code, const QString &snippetLabel) } else if (useLine) result += line.toString() + QLatin1Char('\n'); } - return result; + return CodeSnipAbstract::fixSpaces(result); } template <class EnumType, Qt::CaseSensitivity cs = Qt::CaseInsensitive> @@ -207,13 +207,8 @@ ENUM_LOOKUP_BEGIN(TypeSystem::Language, Qt::CaseInsensitive, languageFromAttribute, TypeSystem::NoLanguage) { {u"all", TypeSystem::All}, // sorted! - {u"constructors", TypeSystem::Constructors}, - {u"destructor-function", TypeSystem::DestructorFunction}, - {u"interface", TypeSystem::Interface}, - {u"library-initializer", TypeSystem::PackageInitializer}, {u"native", TypeSystem::NativeCode}, // em algum lugar do cpp {u"shell", TypeSystem::ShellCode}, // coloca no header, mas antes da declaracao da classe - {u"shell-declaration", TypeSystem::ShellDeclaration}, {u"target", TypeSystem::TargetLangCode} // em algum lugar do cpp }; ENUM_LOOKUP_BINARY_SEARCH() @@ -272,10 +267,7 @@ ENUM_LOOKUP_BEGIN(TypeSystem::CodeSnipPosition, Qt::CaseInsensitive, { {u"beginning", TypeSystem::CodeSnipPositionBeginning}, {u"end", TypeSystem::CodeSnipPositionEnd}, - {u"declaration", TypeSystem::CodeSnipPositionDeclaration}, - {u"prototype-initialization", TypeSystem::CodeSnipPositionPrototypeInitialization}, - {u"constructor-initialization", TypeSystem::CodeSnipPositionConstructorInitialization}, - {u"constructor", TypeSystem::CodeSnipPositionConstructor} + {u"declaration", TypeSystem::CodeSnipPositionDeclaration} }; ENUM_LOOKUP_LINEAR_SEARCH() @@ -2475,7 +2467,7 @@ bool TypeSystemParser::readFileSnippet(QXmlStreamAttributes *attributes, CodeSni "// START of custom code block [file: " << source << "]\n" << extractSnippet(QString::fromUtf8(codeFile.readAll()), snippetLabel) - << "\n// END of custom code block [file: " << source + << "// END of custom code block [file: " << source << "]\n// ========================================================================\n"; snip->addCode(content); return true; @@ -2520,19 +2512,8 @@ bool TypeSystemParser::parseInjectCode(const QXmlStreamReader &, snip.position = position; snip.language = lang; - if (snip.language == TypeSystem::Interface - && topElement.type != StackElement::InterfaceTypeEntry) { - m_error = QLatin1String("Interface code injections must be direct child of an interface type entry"); - return false; - } - if (topElement.type == StackElement::ModifyFunction || topElement.type == StackElement::AddFunction) { - if (snip.language == TypeSystem::ShellDeclaration) { - m_error = QLatin1String("no function implementation in shell declaration in which to inject code"); - return false; - } - FunctionModification &mod = m_contextStack.top()->functionMods.last(); mod.snips << snip; if (!snip.code().isEmpty()) diff --git a/sources/shiboken2/doc/typesystem_codeinjection.rst b/sources/shiboken2/doc/typesystem_codeinjection.rst index c891fd2cd..684630dd4 100644 --- a/sources/shiboken2/doc/typesystem_codeinjection.rst +++ b/sources/shiboken2/doc/typesystem_codeinjection.rst @@ -47,71 +47,74 @@ inject-code tag The following table describes the semantics of ``inject-code`` tag as used on |project|. - +---------------+------+---------+--------------------------------------------------------------+ - |Parent Tag |Class |Position |Meaning | - +===============+======+=========+==============================================================+ - |value-type, |native|beginning|Write to the beginning of a class wrapper ``.cpp`` file, right| - |object-type | | |after the ``#include`` clauses. A common use would be to write| - | | | |prototypes for custom functions whose definitions are put on a| - | | | |``native/end`` code injection. | - | | +---------+--------------------------------------------------------------+ - | | |end |Write to the end of a class wrapper ``.cpp`` file. Could be | - | | | |used to write custom/helper functions definitions for | - | | | |prototypes declared on ``native/beginning``. | - | +------+---------+--------------------------------------------------------------+ - | |target|beginning|Put custom code on the beginning of the wrapper initializer | - | | | |function (``init_CLASS(PyObject *module)``). This could be | - | | | |used to manipulate the ``PyCLASS_Type`` structure before | - | | | |registering it on Python. | - | | +---------+--------------------------------------------------------------+ - | | |end |Write the given custom code at the end of the class wrapper | - | | | |initializer function (``init_CLASS(PyObject *module)``). The | - | | | |code here will be executed after all the wrapped class | - | | | |components have been initialized. | - +---------------+------+---------+--------------------------------------------------------------+ - |modify-function|native|beginning|Code here is put on the virtual method override of a C++ | - | | | |wrapper class (the one responsible for passing C++ calls to a | - | | | |Python override, if there is any), right after the C++ | - | | | |arguments have been converted but before the Python call. | - | | +---------+--------------------------------------------------------------+ - | | |end |This code injection is put in a virtual method override on the| - | | | |C++ wrapper class, after the call to Python and before | - | | | |dereferencing the Python method and tuple of arguments. | - | +------+---------+--------------------------------------------------------------+ - | |target|beginning|This code is injected on the Python method wrapper | - | | | |(``PyCLASS_METHOD(...)``), right after the decisor have found | - | | | |which signature to call and also after the conversion of the | - | | | |arguments to be used, but before the actual call. | - | | +---------+--------------------------------------------------------------+ - | | |end |This code is injected on the Python method wrapper | - | | | |(``PyCLASS_METHOD(...)``), right after the C++ method call, | - | | | |but still inside the scope created by the overload for each | - | | | |signature. | - | +------+---------+--------------------------------------------------------------+ - | |shell |beginning|Used only for virtual functions. The code is injected when the| - | | | |function does not has a Python implementation, then the code | - | | | |is inserted before c++ call | - | | +---------+--------------------------------------------------------------+ - | | |end |Same as above, but the code is inserted after c++ call | - +---------------+------+---------+--------------------------------------------------------------+ - |typesystem |native|beginning|Write code to the beginning of the module ``.cpp`` file, right| - | | | |after the ``#include`` clauses. This position has a similar | - | | | |purpose as the ``native/beginning`` position on a wrapper | - | | | |class ``.cpp`` file, namely write function prototypes, but not| - | | | |restricted to this use. | - | | +---------+--------------------------------------------------------------+ - | | |end |Write code to the end of the module ``.cpp`` file. Usually | - | | | |implementations for function prototypes inserted at the | - | | | |beginning of the file with a ``native/beginning`` code | - | | | |injection. | - | +------+---------+--------------------------------------------------------------+ - | |target|beginning|Insert code at the start of the module initialization function| - | | | |(``initMODULENAME()``), before the calling ``Py_InitModule``. | - | | +---------+--------------------------------------------------------------+ - | | |end |Insert code at the end of the module initialization function | - | | | |(``initMODULENAME()``), but before the checking that emits a | - | | | |fatal error in case of problems importing the module. | - +---------------+------+---------+--------------------------------------------------------------+ + +---------------+------+-----------+--------------------------------------------------------------+ + |Parent Tag |Class |Position |Meaning | + +===============+======+===========+==============================================================+ + |value-type, |native|beginning |Write to the beginning of a class wrapper ``.cpp`` file, right| + |object-type | | |after the ``#include`` clauses. A common use would be to write| + | | | |prototypes for custom functions whose definitions are put on a| + | | | |``native/end`` code injection. | + | | +-----------+--------------------------------------------------------------+ + | | |end |Write to the end of a class wrapper ``.cpp`` file. Could be | + | | | |used to write custom/helper functions definitions for | + | | | |prototypes declared on ``native/beginning``. | + | +------+-----------+--------------------------------------------------------------+ + | |target|beginning |Put custom code on the beginning of the wrapper initializer | + | | | |function (``init_CLASS(PyObject *module)``). This could be | + | | | |used to manipulate the ``PyCLASS_Type`` structure before | + | | | |registering it on Python. | + | | +-----------+--------------------------------------------------------------+ + | | |end |Write the given custom code at the end of the class wrapper | + | | | |initializer function (``init_CLASS(PyObject *module)``). The | + | | | |code here will be executed after all the wrapped class | + | | | |components have been initialized. | + +---------------+------+-----------+--------------------------------------------------------------+ + |modify-function|native|beginning |Code here is put on the virtual method override of a C++ | + | | | |wrapper class (the one responsible for passing C++ calls to a | + | | | |Python override, if there is any), right after the C++ | + | | | |arguments have been converted but before the Python call. | + | | +-----------+--------------------------------------------------------------+ + | | |end |This code injection is put in a virtual method override on the| + | | | |C++ wrapper class, after the call to Python and before | + | | | |dereferencing the Python method and tuple of arguments. | + | +------+-----------+--------------------------------------------------------------+ + | |target|beginning |This code is injected on the Python method wrapper | + | | | |(``PyCLASS_METHOD(...)``), right after the decisor have found | + | | | |which signature to call and also after the conversion of the | + | | | |arguments to be used, but before the actual call. | + | | +-----------+--------------------------------------------------------------+ + | | |end |This code is injected on the Python method wrapper | + | | | |(``PyCLASS_METHOD(...)``), right after the C++ method call, | + | | | |but still inside the scope created by the overload for each | + | | | |signature. | + | +------+-----------+--------------------------------------------------------------+ + | |shell |declaration|Used only for virtual functions. This code is injected at the | + | | | |top. | + | | +-----------+--------------------------------------------------------------+ + | | |beginning |Used only for virtual functions. The code is injected when the| + | | | |function does not has a Python implementation, then the code | + | | | |is inserted before c++ call | + | | +-----------+--------------------------------------------------------------+ + | | |end |Same as above, but the code is inserted after c++ call | + +---------------+------+-----------+--------------------------------------------------------------+ + |typesystem |native|beginning |Write code to the beginning of the module ``.cpp`` file, right| + | | | |after the ``#include`` clauses. This position has a similar | + | | | |purpose as the ``native/beginning`` position on a wrapper | + | | | |class ``.cpp`` file, namely write function prototypes, but not| + | | | |restricted to this use. | + | | +-----------+--------------------------------------------------------------+ + | | |end |Write code to the end of the module ``.cpp`` file. Usually | + | | | |implementations for function prototypes inserted at the | + | | | |beginning of the file with a ``native/beginning`` code | + | | | |injection. | + | +------+-----------+--------------------------------------------------------------+ + | |target|beginning |Insert code at the start of the module initialization function| + | | | |(``initMODULENAME()``), before the calling ``Py_InitModule``. | + | | +-----------+--------------------------------------------------------------+ + | | |end |Insert code at the end of the module initialization function | + | | | |(``initMODULENAME()``), but before the checking that emits a | + | | | |fatal error in case of problems importing the module. | + +---------------+------+-----------+--------------------------------------------------------------+ Anatomy of Code Injection diff --git a/sources/shiboken2/generator/generator.cpp b/sources/shiboken2/generator/generator.cpp index 88ba1f04f..04658eff3 100644 --- a/sources/shiboken2/generator/generator.cpp +++ b/sources/shiboken2/generator/generator.cpp @@ -540,37 +540,12 @@ void Generator::replaceTemplateVariables(QString &code, const AbstractMetaFuncti QTextStream &formatCode(QTextStream &s, const QString &code, Indentor &indentor) { - // detect number of spaces before the first character - const QStringList lst(code.split(QLatin1Char('\n'))); - static const QRegularExpression nonSpaceRegex(QStringLiteral("[^\\s]")); - Q_ASSERT(nonSpaceRegex.isValid()); - int spacesToRemove = 0; - for (const QString &line : lst) { - if (!line.trimmed().isEmpty()) { - spacesToRemove = line.indexOf(nonSpaceRegex); - if (spacesToRemove == -1) - spacesToRemove = 0; - break; - } - } - - static const QRegularExpression emptyLine(QStringLiteral("^\\s*[\\r]?[\\n]?\\s*$")); - Q_ASSERT(emptyLine.isValid()); - - for (QString line : lst) { - if (!line.isEmpty() && !emptyLine.match(line).hasMatch()) { - while (line.constEnd()->isSpace()) - line.chop(1); - int limit = 0; - for(int i = 0; i < spacesToRemove; ++i) { - if (!line[i].isSpace()) - break; - limit++; - } - - s << indentor << line.remove(0, limit); - } - s << Qt::endl; + const auto lines= code.splitRef(QLatin1Char('\n')); + for (const auto &line : lines) { + // Do not indent preprocessor lines + if (!line.isEmpty() && !line.startsWith(QLatin1Char('#'))) + s << indentor; + s << line << '\n'; } return s; } diff --git a/sources/shiboken2/generator/main.cpp b/sources/shiboken2/generator/main.cpp index 476e176d3..3c9d13b48 100644 --- a/sources/shiboken2/generator/main.cpp +++ b/sources/shiboken2/generator/main.cpp @@ -42,7 +42,7 @@ #include "headergenerator.h" #include "qtdocgenerator.h" -#ifdef _WINDOWS +#ifdef Q_OS_WIN static const QChar pathSplitter = QLatin1Char(';'); #else static const QChar pathSplitter = QLatin1Char(':'); diff --git a/sources/shiboken2/generator/shiboken2/cppgenerator.cpp b/sources/shiboken2/generator/shiboken2/cppgenerator.cpp index 4c637e701..0ad4fff3b 100644 --- a/sources/shiboken2/generator/shiboken2/cppgenerator.cpp +++ b/sources/shiboken2/generator/shiboken2/cppgenerator.cpp @@ -312,6 +312,7 @@ void CppGenerator::generateClass(QTextStream &s, const GeneratorContext &classCo s << "#include <pysidesignal.h>\n" << "#include <pysideproperty.h>\n" << "#include <pyside.h>\n" + << "#include <pysideqenum.h>\n" << "#include <qapp_macro.h>\n\n" << "QT_WARNING_DISABLE_DEPRECATED\n\n"; } @@ -784,6 +785,47 @@ QString CppGenerator::getVirtualFunctionReturnTypeName(const AbstractMetaFunctio + typeEntry->qualifiedCppName() + QLatin1String(" >())->tp_name"); } +// When writing an overridden method of a wrapper class, write the part +// calling the C++ function in case no overload in Python exists. +void CppGenerator::writeVirtualMethodCppCall(QTextStream &s, + const AbstractMetaFunction *func, + const QString &funcName, + const CodeSnipList &snips, + const AbstractMetaArgument *lastArg, + const TypeEntry *retType, + const DefaultValue &defaultReturnExpr) +{ + if (!snips.isEmpty()) { + writeCodeSnips(s, snips, TypeSystem::CodeSnipPositionBeginning, + TypeSystem::ShellCode, func, lastArg); + } + + if (func->isAbstract()) { + s << INDENT << "PyErr_SetString(PyExc_NotImplementedError, \"pure virtual method '" + << func->ownerClass()->name() << '.' << funcName + << "()' not implemented.\");\n"; + s << INDENT << "return"; + if (retType) + s << ' ' << defaultReturnExpr.returnValue(); + s << ";\n"; + return; + } + + s << INDENT; + if (retType) + s << "return "; + s << "this->::" << func->implementingClass()->qualifiedCppName() << "::"; + writeFunctionCall(s, func, Generator::VirtualCall); + s << ";\n"; + if (retType) + return; + if (!snips.isEmpty()) { + writeCodeSnips(s, snips, TypeSystem::CodeSnipPositionEnd, + TypeSystem::ShellCode, func, lastArg); + } + s << INDENT << "return;\n"; +} + void CppGenerator::writeVirtualMethodNative(QTextStream &s, const AbstractMetaFunction *func, int cacheIndex) @@ -802,10 +844,10 @@ void CppGenerator::writeVirtualMethodNative(QTextStream &s, Indentation indentation(INDENT); + const FunctionModificationList &functionModifications = func->modifications(); DefaultValue defaultReturnExpr; if (retType) { - const FunctionModificationList &mods = func->modifications(); - for (const FunctionModification &mod : mods) { + for (const FunctionModification &mod : functionModifications) { for (const ArgumentModification &argMod : mod.argument_mods) { if (argMod.index == 0 && !argMod.replacedDefaultExpression.isEmpty()) { static const QRegularExpression regex(QStringLiteral("%(\\d+)")); @@ -853,16 +895,17 @@ void CppGenerator::writeVirtualMethodNative(QTextStream &s, return; } + const CodeSnipList snips = func->hasInjectedCode() + ? func->injectedCodeSnips() : CodeSnipList(); + const AbstractMetaArgument *lastArg = func->arguments().isEmpty() + ? nullptr : func->arguments().constLast(); + //Write declaration/native injected code - if (func->hasInjectedCode()) { - CodeSnipList snips = func->injectedCodeSnips(); - const AbstractMetaArgument *lastArg = func->arguments().isEmpty() ? nullptr : func->arguments().constLast(); - writeCodeSnips(s, snips, TypeSystem::CodeSnipPositionDeclaration, TypeSystem::NativeCode, func, lastArg); - s << Qt::endl; + if (!snips.isEmpty()) { + writeCodeSnips(s, snips, TypeSystem::CodeSnipPositionDeclaration, + TypeSystem::ShellCode, func, lastArg); } - // PYSIDE-803: Build a boolean cache for unused overrides. - bool multi_line = retType == nullptr; // set to true when using instrumentation if (wrapperDiagnostics()) { s << INDENT << "std::cerr << "; #ifndef Q_CC_MSVC // g++ outputs __FUNCTION__ unqualified @@ -872,22 +915,13 @@ void CppGenerator::writeVirtualMethodNative(QTextStream &s, << cacheIndex << R"( << "]=" << m_PyMethodCache[)" << cacheIndex << R"(] << '\n';)" << '\n'; } + // PYSIDE-803: Build a boolean cache for unused overrides. + const bool multi_line = retType == nullptr || !snips.isEmpty() || func->isAbstract(); s << INDENT << "if (m_PyMethodCache[" << cacheIndex << "])" << (multi_line ? " {\n" : "\n"); { Indentation indentation(INDENT); - s << INDENT; - if (retType) - s << "return "; - if (!func->isAbstract()) { - s << "this->::" << func->implementingClass()->qualifiedCppName() << "::"; - writeFunctionCall(s, func, Generator::VirtualCall); - } else { - if (retType) - s << ' ' << defaultReturnExpr.returnValue(); - } - if (!retType) - s << ";\n" << INDENT << "return"; - s << ";\n"; + writeVirtualMethodCppCall(s, func, funcName, snips, lastArg, retType, + defaultReturnExpr); } if (multi_line) s << INDENT << "}\n"; @@ -907,36 +941,12 @@ void CppGenerator::writeVirtualMethodNative(QTextStream &s, s << INDENT << "if (" << PYTHON_OVERRIDE_VAR << ".isNull()) {\n"; { Indentation indentation(INDENT); - CodeSnipList snips; - if (func->hasInjectedCode()) { - snips = func->injectedCodeSnips(); - const AbstractMetaArgument *lastArg = func->arguments().isEmpty() ? nullptr : func->arguments().constLast(); - writeCodeSnips(s, snips, TypeSystem::CodeSnipPositionBeginning, TypeSystem::ShellCode, func, lastArg); - s << Qt::endl; - } - - if (func->isAbstract()) { - s << INDENT << "PyErr_SetString(PyExc_NotImplementedError, \"pure virtual method '"; - s << func->ownerClass()->name() << '.' << funcName; - s << "()' not implemented.\");\n"; - s << INDENT << "return"; - if (retType) - s << ' ' << defaultReturnExpr.returnValue(); - } else { - s << INDENT << "gil.release();\n"; - if (useOverrideCaching(func->ownerClass())) { - s << INDENT << "m_PyMethodCache[" << cacheIndex << "] = true;\n"; - s << INDENT; - } - if (retType) - s << "return "; - s << "this->::" << func->implementingClass()->qualifiedCppName() << "::"; - writeFunctionCall(s, func, Generator::VirtualCall); - if (!retType) - s << ";\n" << INDENT << "return"; - } + s << INDENT << "gil.release();\n"; + if (useOverrideCaching(func->ownerClass())) + s << INDENT << "m_PyMethodCache[" << cacheIndex << "] = true;\n"; + writeVirtualMethodCppCall(s, func, funcName, snips, lastArg, retType, + defaultReturnExpr); } - s << ";\n"; s << INDENT << "}\n\n"; //WS writeConversionRule(s, func, TypeSystem::TargetLangCode); @@ -970,8 +980,9 @@ void CppGenerator::writeVirtualMethodNative(QTextStream &s, convert = !m_formatUnits.contains(argType->name()); } - Indentation indentation(INDENT); - ac << INDENT; + Indentor nested; + Indentation indentation(nested); + ac << nested; if (!func->conversionRule(TypeSystem::TargetLangCode, arg->argumentIndex() + 1).isEmpty()) { // Has conversion rule. ac << arg->name() + QLatin1String(CONV_RULE_OUT_VAR_SUFFIX); @@ -993,8 +1004,7 @@ void CppGenerator::writeVirtualMethodNative(QTextStream &s, bool invalidateReturn = false; QSet<int> invalidateArgs; - const FunctionModificationList &mods = func->modifications(); - for (const FunctionModification &funcMod : mods) { + for (const FunctionModification &funcMod : functionModifications) { for (const ArgumentModification &argMod : funcMod.argument_mods) { if (argMod.resetAfterUse && !invalidateArgs.contains(argMod.index)) { invalidateArgs.insert(argMod.index); @@ -1007,16 +1017,12 @@ void CppGenerator::writeVirtualMethodNative(QTextStream &s, } s << Qt::endl; - CodeSnipList snips; - if (func->hasInjectedCode()) { - snips = func->injectedCodeSnips(); - + if (!snips.isEmpty()) { if (injectedCodeUsesPySelf(func)) s << INDENT << "PyObject *pySelf = BindingManager::instance().retrieveWrapper(this);\n"; const AbstractMetaArgument *lastArg = func->arguments().isEmpty() ? nullptr : func->arguments().constLast(); writeCodeSnips(s, snips, TypeSystem::CodeSnipPositionBeginning, TypeSystem::NativeCode, func, lastArg); - s << Qt::endl; } if (!injectedCodeCallsPythonOverride(func)) { @@ -1102,8 +1108,7 @@ void CppGenerator::writeVirtualMethodNative(QTextStream &s, } - const FunctionModificationList &funcMods = func->modifications(); - for (const FunctionModification &funcMod : funcMods) { + for (const FunctionModification &funcMod : functionModifications) { for (const ArgumentModification &argMod : funcMod.argument_mods) { if (argMod.ownerships.contains(TypeSystem::NativeCode) && argMod.index == 0 && argMod.ownerships[TypeSystem::NativeCode] == TypeSystem::CppOwnership) { @@ -1217,8 +1222,9 @@ void CppGenerator::writeEnumConverterFunctions(QTextStream &s, const TypeEntry * } QString code; QTextStream c(&code); - c << INDENT << "*reinterpret_cast<" << cppTypeName << " *>(cppOut) =\n" - << INDENT << " "; + Indentor nested; + c << nested << "*reinterpret_cast<" << cppTypeName << " *>(cppOut) =\n" + << nested << " "; if (enumType->isFlags()) c << cppTypeName << "(QFlag(int(PySide::QFlags::getValue(reinterpret_cast<PySideQFlagsObject *>(pyIn)))))"; else @@ -1231,9 +1237,9 @@ void CppGenerator::writeEnumConverterFunctions(QTextStream &s, const TypeEntry * code.clear(); - c << INDENT << "const int castCppIn = int(*reinterpret_cast<const " + c << nested << "const int castCppIn = int(*reinterpret_cast<const " << cppTypeName << " *>(cppIn));\n"; - c << INDENT; + c << nested; c << "return "; if (enumType->isFlags()) { c << "reinterpret_cast<PyObject *>(PySide::QFlags::newObject(castCppIn, " @@ -1258,8 +1264,8 @@ void CppGenerator::writeEnumConverterFunctions(QTextStream &s, const TypeEntry * code.clear(); cppTypeName = getFullTypeName(flags).trimmed(); - c << INDENT << "*reinterpret_cast<" << cppTypeName << " *>(cppOut) =\n" - << INDENT << " " << cppTypeName + c << nested << "*reinterpret_cast<" << cppTypeName << " *>(cppOut) =\n" + << nested << " " << cppTypeName << "(QFlag(int(Shiboken::Enum::getValue(pyIn))));\n"; QString flagsTypeName = fixedCppTypeName(flags); @@ -1267,9 +1273,9 @@ void CppGenerator::writeEnumConverterFunctions(QTextStream &s, const TypeEntry * writeIsPythonConvertibleToCppFunction(s, typeName, flagsTypeName, pyTypeCheck); code.clear(); - c << INDENT << "Shiboken::AutoDecRef pyLong(PyNumber_Long(pyIn));\n"; - c << INDENT << "*reinterpret_cast<" << cppTypeName << " *>(cppOut) =\n" - << INDENT << " " << cppTypeName + c << nested << "Shiboken::AutoDecRef pyLong(PyNumber_Long(pyIn));\n"; + c << nested << "*reinterpret_cast<" << cppTypeName << " *>(cppOut) =\n" + << nested << " " << cppTypeName << "(QFlag(int(PyLong_AsLong(pyLong.object()))));\n"; // PYSIDE-898: Include an additional condition to detect if the type of the // enum corresponds to the object that is being evaluated. @@ -1316,7 +1322,8 @@ void CppGenerator::writeConverterFunctions(QTextStream &s, const AbstractMetaCla QString targetTypeName = metaClass->name() + QLatin1String("_PTR"); QString code; QTextStream c(&code); - c << INDENT << "Shiboken::Conversions::pythonToCppPointer(" << cpythonType << ", pyIn, cppOut);"; + Indentor nested; + c << nested << "Shiboken::Conversions::pythonToCppPointer(" << cpythonType << ", pyIn, cppOut);"; writePythonToCppFunction(s, code, sourceTypeName, targetTypeName); // "Is convertible" function for the Python object to C++ pointer conversion. @@ -1330,30 +1337,30 @@ void CppGenerator::writeConverterFunctions(QTextStream &s, const AbstractMetaCla code.clear(); if (usePySideExtensions() && metaClass->isQObject()) { - c << INDENT << "return PySide::getWrapperForQObject(reinterpret_cast<" + c << nested << "return PySide::getWrapperForQObject(reinterpret_cast<" << typeName << " *>(const_cast<void *>(cppIn)), " << cpythonType << ");\n"; } else { - c << INDENT << "auto pyOut = reinterpret_cast<PyObject *>(Shiboken::BindingManager::instance().retrieveWrapper(cppIn));\n"; - c << INDENT << "if (pyOut) {\n"; + c << nested << "auto pyOut = reinterpret_cast<PyObject *>(Shiboken::BindingManager::instance().retrieveWrapper(cppIn));\n"; + c << nested << "if (pyOut) {\n"; { - Indentation indent(INDENT); - c << INDENT << "Py_INCREF(pyOut);\n"; - c << INDENT << "return pyOut;\n"; - } - c << INDENT << "}\n"; - c << INDENT << "bool changedTypeName = false;\n" - << INDENT << "auto tCppIn = reinterpret_cast<const " << typeName << " *>(cppIn);\n" - << INDENT << "const char *typeName = typeid(*tCppIn).name();\n" - << INDENT << "auto sbkType = Shiboken::ObjectType::typeForTypeName(typeName);\n" - << INDENT << "if (sbkType && Shiboken::ObjectType::hasSpecialCastFunction(sbkType)) {\n" - << INDENT << " typeName = typeNameOf(tCppIn);\n" - << INDENT << " changedTypeName = true;\n" - << INDENT << " }\n" - << INDENT << "PyObject *result = Shiboken::Object::newObject(" << cpythonType + Indentation indent(nested); + c << nested << "Py_INCREF(pyOut);\n"; + c << nested << "return pyOut;\n"; + } + c << nested << "}\n"; + c << nested << "bool changedTypeName = false;\n" + << nested << "auto tCppIn = reinterpret_cast<const " << typeName << " *>(cppIn);\n" + << nested << "const char *typeName = typeid(*tCppIn).name();\n" + << nested << "auto sbkType = Shiboken::ObjectType::typeForTypeName(typeName);\n" + << nested << "if (sbkType && Shiboken::ObjectType::hasSpecialCastFunction(sbkType)) {\n" + << nested << " typeName = typeNameOf(tCppIn);\n" + << nested << " changedTypeName = true;\n" + << nested << "}\n" + << nested << "PyObject *result = Shiboken::Object::newObject(" << cpythonType << ", const_cast<void *>(cppIn), false, /* exactType */ changedTypeName, typeName);\n" - << INDENT << "if (changedTypeName)\n" - << INDENT << " delete [] typeName;\n" - << INDENT << "return result;"; + << nested << "if (changedTypeName)\n" + << nested << " delete [] typeName;\n" + << nested << "return result;"; } std::swap(targetTypeName, sourceTypeName); writeCppToPythonFunction(s, code, sourceTypeName, targetTypeName); @@ -1383,7 +1390,7 @@ void CppGenerator::writeConverterFunctions(QTextStream &s, const AbstractMetaCla computedWrapperName = classContext.smartPointerWrapperName(); } - c << INDENT << "return Shiboken::Object::newObject(" << cpythonType + c << nested << "return Shiboken::Object::newObject(" << cpythonType << ", new ::" << computedWrapperName << "(*reinterpret_cast<const " << typeName << " *>(cppIn)), true, true);"; writeCppToPythonFunction(s, code, sourceTypeName, targetTypeName); @@ -1406,7 +1413,7 @@ void CppGenerator::writeConverterFunctions(QTextStream &s, const AbstractMetaCla else wrappedCPtrExpression = cpythonWrapperCPtr(classContext.preciseType(), pyInVariable); - c << INDENT << "*reinterpret_cast<" << typeName << " *>(cppOut) = *" + c << nested << "*reinterpret_cast<" << typeName << " *>(cppOut) = *" << wrappedCPtrExpression << ';'; writePythonToCppFunction(s, code, sourceTypeName, targetTypeName); @@ -1475,7 +1482,7 @@ void CppGenerator::writeConverterFunctions(QTextStream &s, const AbstractMetaCla || sourceType->typeEntry()->isEnum() || sourceType->typeEntry()->isFlags()) { QTextStream pc(&toCppPreConv); - pc << INDENT << getFullTypeNameWithoutModifiers(sourceType) << " cppIn"; + pc << nested << getFullTypeNameWithoutModifiers(sourceType) << " cppIn"; writeMinimalConstructorExpression(pc, sourceType); pc << ";\n"; writeToCppConversion(pc, sourceType, nullptr, QLatin1String("pyIn"), QLatin1String("cppIn")); @@ -1703,10 +1710,10 @@ void CppGenerator::writeMethodWrapperPreamble(QTextStream &s, OverloadData &over } if (usesNamedArguments && !rfunc->isCallOperator()) - s << INDENT << "int numNamedArgs = (kwds ? PyDict_Size(kwds) : 0);\n"; + s << INDENT << "const Py_ssize_t numNamedArgs = (kwds ? PyDict_Size(kwds) : 0);\n"; if (initPythonArguments) { - s << INDENT << "int numArgs = "; + s << INDENT << "const Py_ssize_t numArgs = "; if (minArgs == 0 && maxArgs == 1 && !rfunc->isConstructor() && !pythonFunctionWrapperUsesListOfArguments(overloadData)) s << "(" << PYTHON_ARG << " == 0 ? 0 : 1);\n"; else @@ -2859,8 +2866,9 @@ void CppGenerator::writeCppToPythonFunction(QTextStream &s, const QString &code, static void replaceCppToPythonVariables(QString &code, const QString &typeName) { - code.prepend(QLatin1String("auto &cppInRef = *reinterpret_cast<") - + typeName + QLatin1String(" *>(const_cast<void *>(cppIn));\n")); + const QString line = QLatin1String("auto &cppInRef = *reinterpret_cast<") + + typeName + QLatin1String(" *>(const_cast<void *>(cppIn));"); + CodeSnipAbstract::prependCode(&code, line); code.replace(QLatin1String("%INTYPE"), typeName); code.replace(QLatin1String("%OUTTYPE"), QLatin1String("PyObject *")); code.replace(QLatin1String("%in"), QLatin1String("cppInRef")); @@ -2947,12 +2955,13 @@ void CppGenerator::writePythonToCppConversionFunctions(QTextStream &s, // Python to C++ conversion function. QString code; QTextStream c(&code); + Indentor nested; if (conversion.isEmpty()) conversion = QLatin1Char('*') + cpythonWrapperCPtr(sourceType->typeEntry(), QLatin1String("pyIn")); if (!preConversion.isEmpty()) - c << INDENT << preConversion << Qt::endl; + c << nested << preConversion << Qt::endl; const QString fullTypeName = getFullTypeName(targetType->typeEntry()); - c << INDENT << "*reinterpret_cast<" << fullTypeName << " *>(cppOut) = " + c << nested << "*reinterpret_cast<" << fullTypeName << " *>(cppOut) = " << fullTypeName << '(' << conversion << ");"; QString sourceTypeName = fixedCppTypeName(sourceType); QString targetTypeName = fixedCppTypeName(targetType); @@ -3034,11 +3043,10 @@ void CppGenerator::writePythonToCppConversionFunctions(QTextStream &s, const Abs } // Python to C++ conversion function. QString cppTypeName = getFullTypeNameWithoutModifiers(containerType); - QString code; - QTextStream c(&code); - c << INDENT << "auto &cppOutRef = *reinterpret_cast<" - << cppTypeName << " *>(cppOut);\n"; - code.append(toCppConversions.constFirst()->conversion()); + QString code = toCppConversions.constFirst()->conversion(); + const QString line = QLatin1String("auto &cppOutRef = *reinterpret_cast<") + + cppTypeName + QLatin1String(" *>(cppOut);"); + CodeSnipAbstract::prependCode(&code, line); for (int i = 0; i < containerType->instantiations().count(); ++i) { const AbstractMetaType *type = containerType->instantiations().at(i); QString typeName = getFullTypeName(type); @@ -3232,7 +3240,6 @@ void CppGenerator::writeMethodCall(QTextStream &s, const AbstractMetaFunction *f } writeCodeSnips(s, snips, TypeSystem::CodeSnipPositionBeginning, TypeSystem::TargetLangCode, func, lastArg); - s << Qt::endl; } writeConversionRule(s, func, TypeSystem::NativeCode); @@ -3523,10 +3530,8 @@ void CppGenerator::writeMethodCall(QTextStream &s, const AbstractMetaFunction *f } } - if (func->hasInjectedCode() && !func->isConstructor()) { - s << Qt::endl; + if (func->hasInjectedCode() && !func->isConstructor()) writeCodeSnips(s, snips, TypeSystem::CodeSnipPositionEnd, TypeSystem::TargetLangCode, func, lastArg); - } bool hasReturnPolicy = false; @@ -4983,8 +4988,13 @@ void CppGenerator::writeSignatureStrings(QTextStream &s, s << "// Multiple signatures have their index \"n:\" in front.\n"; s << "static const char *" << arrayName << "_SignatureStrings[] = {\n"; QString line; - while (signatureStream.readLineInto(&line)) - s << INDENT << "R\"CPP(" << line << ")CPP\",\n"; + while (signatureStream.readLineInto(&line)) { + // must anything be escaped? + if (line.contains(QLatin1Char('"')) || line.contains(QLatin1Char('\\'))) + s << INDENT << "R\"CPP(" << line << ")CPP\",\n"; + else + s << INDENT << '"' << line << "\",\n"; + } s << INDENT << NULL_PTR << "}; // Sentinel\n\n"; } @@ -5637,6 +5647,7 @@ bool CppGenerator::finishGeneration() if (usePySideExtensions()) { s << includeQDebug; s << "#include <pyside.h>\n"; + s << "#include <pysideqenum.h>\n"; s << "#include <qapp_macro.h>\n"; } @@ -5680,10 +5691,8 @@ bool CppGenerator::finishGeneration() const CodeSnipList snips = moduleEntry->codeSnips(); // module inject-code native/beginning - if (!snips.isEmpty()) { + if (!snips.isEmpty()) writeCodeSnips(s, snips, TypeSystem::CodeSnipPositionBeginning, TypeSystem::NativeCode); - s << Qt::endl; - } // cleanup staticMetaObject attribute if (usePySideExtensions()) { @@ -5814,10 +5823,8 @@ bool CppGenerator::finishGeneration() ErrorCode errorCode(QLatin1String("SBK_MODULE_INIT_ERROR")); // module inject-code target/beginning - if (!snips.isEmpty()) { + if (!snips.isEmpty()) writeCodeSnips(s, snips, TypeSystem::CodeSnipPositionBeginning, TypeSystem::TargetLangCode); - s << Qt::endl; - } for (const QString &requiredModule : requiredModules) { s << INDENT << "{\n"; @@ -5919,16 +5926,12 @@ bool CppGenerator::finishGeneration() s << INDENT << "}\n"; // module inject-code target/end - if (!snips.isEmpty()) { + if (!snips.isEmpty()) writeCodeSnips(s, snips, TypeSystem::CodeSnipPositionEnd, TypeSystem::TargetLangCode); - s << Qt::endl; - } // module inject-code native/end - if (!snips.isEmpty()) { + if (!snips.isEmpty()) writeCodeSnips(s, snips, TypeSystem::CodeSnipPositionEnd, TypeSystem::NativeCode); - s << Qt::endl; - } if (usePySideExtensions()) { for (AbstractMetaEnum *metaEnum : qAsConst(globalEnums)) diff --git a/sources/shiboken2/generator/shiboken2/cppgenerator.h b/sources/shiboken2/generator/shiboken2/cppgenerator.h index 4e995d56f..16ee412c9 100644 --- a/sources/shiboken2/generator/shiboken2/cppgenerator.h +++ b/sources/shiboken2/generator/shiboken2/cppgenerator.h @@ -60,7 +60,10 @@ private: QString getVirtualFunctionReturnTypeName(const AbstractMetaFunction *func); void writeVirtualMethodNative(QTextStream &s, const AbstractMetaFunction *func, int cacheIndex); - + void writeVirtualMethodCppCall(QTextStream &s, const AbstractMetaFunction *func, + const QString &funcName, const CodeSnipList &snips, + const AbstractMetaArgument *lastArg, const TypeEntry *retType, + const DefaultValue &defaultReturnExpr); void writeMetaObjectMethod(QTextStream &s, const GeneratorContext &classContext); void writeMetaCast(QTextStream &s, const GeneratorContext &classContext); diff --git a/sources/shiboken2/generator/shiboken2/shibokengenerator.cpp b/sources/shiboken2/generator/shiboken2/shibokengenerator.cpp index fe0d2765c..6abaef698 100644 --- a/sources/shiboken2/generator/shiboken2/shibokengenerator.cpp +++ b/sources/shiboken2/generator/shiboken2/shibokengenerator.cpp @@ -1755,7 +1755,7 @@ void ShibokenGenerator::writeClassCodeSnips(QTextStream &s, processClassCodeSnip(code, context); s << INDENT << "// Begin code injection\n"; s << code; - s << INDENT << "// End of code injection\n"; + s << INDENT << "// End of code injection\n\n"; } void ShibokenGenerator::writeCodeSnips(QTextStream &s, @@ -1769,7 +1769,7 @@ void ShibokenGenerator::writeCodeSnips(QTextStream &s, processCodeSnip(code); s << INDENT << "// Begin code injection\n"; s << code; - s << INDENT << "// End of code injection\n"; + s << INDENT << "// End of code injection\n\n"; } void ShibokenGenerator::writeCodeSnips(QTextStream &s, @@ -1996,7 +1996,7 @@ void ShibokenGenerator::writeCodeSnips(QTextStream &s, processCodeSnip(code); s << INDENT << "// Begin code injection\n"; s << code; - s << INDENT << "// End of code injection\n"; + s << INDENT << "// End of code injection\n\n"; } // Returns true if the string is an expression, diff --git a/sources/shiboken2/libshiboken/basewrapper.cpp b/sources/shiboken2/libshiboken/basewrapper.cpp index 443d25cdf..d7184569b 100644 --- a/sources/shiboken2/libshiboken/basewrapper.cpp +++ b/sources/shiboken2/libshiboken/basewrapper.cpp @@ -486,7 +486,7 @@ void SbkObjectTypeDealloc(PyObject *pyObj) } } -PyObject *SbkObjectTypeTpNew(PyTypeObject *metatype, PyObject *args, PyObject *kwds) +static PyObject *SbkObjectTypeTpNew(PyTypeObject *metatype, PyObject *args, PyObject *kwds) { // Check if all bases are new style before calling type.tp_new // Was causing gc assert errors in test_bug704.py when @@ -513,7 +513,8 @@ PyObject *SbkObjectTypeTpNew(PyTypeObject *metatype, PyObject *args, PyObject *k #ifndef IS_PY3K if (PyClass_Check(baseType)) { PyErr_Format(PyExc_TypeError, "Invalid base class used in type %s. " - "PySide only support multiple inheritance from python new style class.", metatype->tp_name); + "PySide only supports multiple inheritance from Python new style classes.", + metatype->tp_name); return 0; } #endif @@ -579,7 +580,6 @@ PyObject *SbkObjectTypeTpNew(PyTypeObject *metatype, PyObject *args, PyObject *k if (PepType_SOTP(base)->subtype_init) PepType_SOTP(base)->subtype_init(newType, args, kwds); } - return reinterpret_cast<PyObject *>(newType); } diff --git a/sources/shiboken2/libshiboken/pep384impl.cpp b/sources/shiboken2/libshiboken/pep384impl.cpp index f07cac613..5e0053e2e 100644 --- a/sources/shiboken2/libshiboken/pep384impl.cpp +++ b/sources/shiboken2/libshiboken/pep384impl.cpp @@ -668,6 +668,22 @@ PyImport_GetModule(PyObject *name) } #endif // PY_VERSION_HEX < 0x03070000 || defined(Py_LIMITED_API) + +/***************************************************************************** + * + * Python 2 incompatibilities + * + * This is incompatibly implemented as macro in Python 2. + */ +#if PY_VERSION_HEX < 0x03000000 + +PyObject *PepMapping_Items(PyObject *o) +{ + return PyObject_CallMethod(o, const_cast<char *>("items"), NULL); +} + +#endif + /***************************************************************************** * * Extra support for name mangling diff --git a/sources/shiboken2/libshiboken/pep384impl.h b/sources/shiboken2/libshiboken/pep384impl.h index 541b0e775..2bfe52254 100644 --- a/sources/shiboken2/libshiboken/pep384impl.h +++ b/sources/shiboken2/libshiboken/pep384impl.h @@ -533,6 +533,18 @@ LIBSHIBOKEN_API PyObject *PyImport_GetModule(PyObject *name); /***************************************************************************** * + * Python 2 incompatibilities + * + * This is incompatibly implemented as macro in Python 2. + */ +#if PY_VERSION_HEX < 0x03000000 +extern LIBSHIBOKEN_API PyObject *PepMapping_Items(PyObject *o); +#else +#define PepMapping_Items PyMapping_Items +#endif + +/***************************************************************************** + * * Runtime support for Python 3.8 incompatibilities * */ diff --git a/sources/shiboken2/libshiboken/sbkenum.cpp b/sources/shiboken2/libshiboken/sbkenum.cpp index f9a43845a..369b264e7 100644 --- a/sources/shiboken2/libshiboken/sbkenum.cpp +++ b/sources/shiboken2/libshiboken/sbkenum.cpp @@ -608,11 +608,16 @@ newItem(PyTypeObject *enumType, long itemValue, const char *itemName) enumObj->ob_value = itemValue; if (newValue) { - PyObject *values = PyDict_GetItem(enumType->tp_dict, Shiboken::PyName::values()); - if (!values) { - values = PyDict_New(); - PyDict_SetItem(enumType->tp_dict, Shiboken::PyName::values(), values); - Py_DECREF(values); // ^ values still alive, because setitem increfs it + auto dict = enumType->tp_dict; // Note: 'values' is borrowed + PyObject *values = PyDict_GetItemWithError(dict, Shiboken::PyName::values()); + if (values == nullptr) { + if (PyErr_Occurred()) + return nullptr; + Shiboken::AutoDecRef new_values(values = PyDict_New()); + if (values == nullptr) + return nullptr; + if (PyDict_SetItem(dict, Shiboken::PyName::values(), values) < 0) + return nullptr; } PyDict_SetItemString(values, itemName, reinterpret_cast<PyObject *>(enumObj)); } diff --git a/sources/shiboken2/libshiboken/sbkpython.h b/sources/shiboken2/libshiboken/sbkpython.h index 9dd1e712e..6755e945d 100644 --- a/sources/shiboken2/libshiboken/sbkpython.h +++ b/sources/shiboken2/libshiboken/sbkpython.h @@ -41,6 +41,7 @@ #define SBKPYTHON_H #include "sbkversion.h" +#define PyEnumMeta_Check(x) (strcmp(Py_TYPE(x)->tp_name, "EnumMeta") == 0) // Qt's "slots" macro collides with the "slots" member variables // used in some Python structs. For compilers that support push_macro, diff --git a/sources/shiboken2/libshiboken/sbkstaticstrings.cpp b/sources/shiboken2/libshiboken/sbkstaticstrings.cpp index c19665176..541d74918 100644 --- a/sources/shiboken2/libshiboken/sbkstaticstrings.cpp +++ b/sources/shiboken2/libshiboken/sbkstaticstrings.cpp @@ -55,11 +55,15 @@ namespace PyName { STATIC_STRING_IMPL(dumps, "dumps") STATIC_STRING_IMPL(loads, "loads") STATIC_STRING_IMPL(result, "result") +STATIC_STRING_IMPL(value, "value") STATIC_STRING_IMPL(values, "values") // Internal: STATIC_STRING_IMPL(classmethod, "classmethod") +STATIC_STRING_IMPL(co_name, "co_name") STATIC_STRING_IMPL(compile, "compile"); +STATIC_STRING_IMPL(f_code, "f_code") +STATIC_STRING_IMPL(f_lineno, "f_lineno") STATIC_STRING_IMPL(function, "function") STATIC_STRING_IMPL(marshal, "marshal") STATIC_STRING_IMPL(method, "method") @@ -73,6 +77,7 @@ namespace PyMagicName { STATIC_STRING_IMPL(class_, "__class__") STATIC_STRING_IMPL(ecf, "__ecf__") STATIC_STRING_IMPL(file, "__file__") +STATIC_STRING_IMPL(members, "__members__") STATIC_STRING_IMPL(module, "__module__") STATIC_STRING_IMPL(name, "__name__") STATIC_STRING_IMPL(qualname, "__qualname__") diff --git a/sources/shiboken2/libshiboken/sbkstaticstrings.h b/sources/shiboken2/libshiboken/sbkstaticstrings.h index 07d6cc60a..4078d163c 100644 --- a/sources/shiboken2/libshiboken/sbkstaticstrings.h +++ b/sources/shiboken2/libshiboken/sbkstaticstrings.h @@ -48,9 +48,13 @@ namespace Shiboken // Some often-used strings namespace PyName { +LIBSHIBOKEN_API PyObject *co_name(); LIBSHIBOKEN_API PyObject *dumps(); +LIBSHIBOKEN_API PyObject *f_code(); +LIBSHIBOKEN_API PyObject *f_lineno(); LIBSHIBOKEN_API PyObject *loads(); LIBSHIBOKEN_API PyObject *result(); +LIBSHIBOKEN_API PyObject *value(); LIBSHIBOKEN_API PyObject *values(); } // namespace PyName @@ -59,6 +63,7 @@ namespace PyMagicName LIBSHIBOKEN_API PyObject *class_(); LIBSHIBOKEN_API PyObject *ecf(); LIBSHIBOKEN_API PyObject *file(); +LIBSHIBOKEN_API PyObject *members(); LIBSHIBOKEN_API PyObject *module(); LIBSHIBOKEN_API PyObject *name(); LIBSHIBOKEN_API PyObject *qualname(); diff --git a/sources/shiboken2/tests/samplebinding/typesystem_sample.xml b/sources/shiboken2/tests/samplebinding/typesystem_sample.xml index 3cd318ceb..5754b3047 100644 --- a/sources/shiboken2/tests/samplebinding/typesystem_sample.xml +++ b/sources/shiboken2/tests/samplebinding/typesystem_sample.xml @@ -44,15 +44,15 @@ <inject-code class="native" position="beginning"> static bool Check2TupleOfNumbers(PyObject* pyIn) { - if (!PySequence_Check(pyIn) || !(PySequence_Size(pyIn) == 2)) - return false; - Shiboken::AutoDecRef pyReal(PySequence_GetItem(pyIn, 0)); - if (!SbkNumber_Check(pyReal)) - return false; - Shiboken::AutoDecRef pyImag(PySequence_GetItem(pyIn, 1)); - if (!SbkNumber_Check(pyImag)) - return false; - return true; + if (!PySequence_Check(pyIn) || !(PySequence_Size(pyIn) == 2)) + return false; + Shiboken::AutoDecRef pyReal(PySequence_GetItem(pyIn, 0)); + if (!SbkNumber_Check(pyReal)) + return false; + Shiboken::AutoDecRef pyImag(PySequence_GetItem(pyIn, 1)); + if (!SbkNumber_Check(pyImag)) + return false; + return true; } </inject-code> <primitive-type name="Complex" target-lang-api-name="PyComplex"> @@ -70,8 +70,8 @@ %out = %OUTTYPE(real, imag); </add-conversion> <add-conversion type="PySequence" check="Check2TupleOfNumbers(%in)"> - Shiboken::AutoDecRef pyReal(PySequence_GetItem(%in, 0)); - Shiboken::AutoDecRef pyImag(PySequence_GetItem(%in, 1)); + Shiboken::AutoDecRef pyReal(PySequence_GetItem(%in, 0)); + Shiboken::AutoDecRef pyImag(PySequence_GetItem(%in, 1)); double real = %CONVERTTOCPP[double](pyReal); double imag = %CONVERTTOCPP[double](pyImag); %out = %OUTTYPE(real, imag); |